1 use std::time::Duration;
3 use crate::cli::CommandLineArgs;
4 use crate::error::Result;
7 use crate::memory::MemoryInfo;
8 use crate::process::Process;
16 memory_info: MemoryInfo,
20 args: CommandLineArgs,
24 /// Determines how much oomf should sleep
25 /// This function is essentially a copy of how earlyoom calculates its sleep time
27 /// Credits: https://github.com/rfjakob/earlyoom/blob/dea92ae67997fcb1a0664489c13d49d09d472d40/main.c#L365
29 pub fn sleep_time_ms(&self) -> Duration {
30 // Maximum expected memory fill rate as seen
31 // with `stress -m 4 --vm-bytes 4G`
32 const RAM_FILL_RATE: i64 = 6000;
33 // Maximum expected swap fill rate as seen
34 // with membomb on zRAM
35 const SWAP_FILL_RATE: i64 = 800;
37 // Maximum and minimum time to sleep, in ms.
38 const MIN_SLEEP: i64 = 100;
39 const MAX_SLEEP: i64 = 1000;
41 // TODO: make these percentages configurable by args./config. file
42 const RAM_TERMINAL_PERCENT: f64 = 10.;
43 const SWAP_TERMINAL_PERCENT: f64 = 10.;
45 let ram_headroom_kib = (self.memory_info.available_ram_percent as f64
46 - RAM_TERMINAL_PERCENT)
47 * (self.memory_info.total_ram_mb as f64 * 10.0);
48 let swap_headroom_kib = (self.memory_info.available_swap_percent as f64
49 - SWAP_TERMINAL_PERCENT)
50 * (self.memory_info.total_swap_mb as f64 * 10.0);
52 let ram_headroom_kib = i64::max(ram_headroom_kib as i64, 0);
53 let swap_headroom_kib = i64::max(swap_headroom_kib as i64, 0);
55 let time_to_sleep = ram_headroom_kib / RAM_FILL_RATE + swap_headroom_kib / SWAP_FILL_RATE;
56 let time_to_sleep = i64::min(time_to_sleep, MAX_SLEEP);
57 let time_to_sleep = i64::max(time_to_sleep, MIN_SLEEP);
59 Duration::from_millis(time_to_sleep as u64)
62 pub fn new(proc_buf: [u8; 50], mut buf: [u8; 100], args: CommandLineArgs) -> Result<Self> {
63 let memory_info = MemoryInfo::new()?;
64 let status = if memory_info.available_ram_percent <= 15 {
65 MemoryStatus::NearTerminal(memory::pressure::pressure_some_avg10(&mut buf)?)
79 fn memory_is_low(&self) -> bool {
80 let terminal_psi = self.args.cutoff_psi;
81 matches!(self.status, MemoryStatus::NearTerminal(psi) if psi >= terminal_psi)
84 fn get_victim(&mut self) -> Result<Process> {
85 kill::choose_victim(&mut self.proc_buf, &mut self.buf, &self.args)
88 fn update_memory_stats(&mut self) -> Result<()> {
89 self.memory_info = memory::MemoryInfo::new()?;
90 self.status = if self.memory_info.available_ram_percent <= 15 {
91 let psi = memory::pressure::pressure_some_avg10(&mut self.buf)?;
92 MemoryStatus::NearTerminal(psi)
99 fn free_up_memory(&mut self) -> Result<()> {
100 let victim = self.get_victim()?;
102 // TODO: is this necessary?
104 // Check for memory stats again to see if the
105 // low-memory situation was solved while
106 // we were searching for our victim
107 self.update_memory_stats()?;
108 if self.memory_is_low() {
109 if self.args.kill_pgroup {
110 kill::kill_process_group(victim)?;
112 kill::kill_and_wait(victim)?;
118 // Use the never type here whenever it reaches stable
119 #[allow(unreachable_code)]
120 pub fn poll(&mut self) -> Result<()> {
122 // Update our memory readings
123 self.update_memory_stats()?;
124 if self.memory_is_low() {
125 self.free_up_memory()?;
128 // Calculating the adaptive sleep time
129 let sleep_time = self.sleep_time_ms();
130 if self.args.verbose {
131 eprintln!("[adaptive-sleep] {}ms", sleep_time.as_millis());
134 std::thread::sleep(sleep_time);