Speed up procfs scanning
[bustd.git] / src / monitor.rs
blobb8900773dfbf0655749fd21d01a77eaa735706a3
1 use std::time::Duration;
3 use crate::cli::CommandLineArgs;
4 use crate::error::Result;
5 use crate::kill;
6 use crate::memory;
7 use crate::memory::MemoryInfo;
8 use crate::process::Process;
10 enum MemoryStatus {
11     NearTerminal(f32),
12     Okay,
15 pub struct Monitor {
16     memory_info: MemoryInfo,
17     proc_buf: [u8; 50],
18     buf: [u8; 100],
19     status: MemoryStatus,
20     args: CommandLineArgs,
23 impl Monitor {
24     /// Determines how much oomf should sleep
25     /// This function is essentially a copy of how earlyoom calculates its sleep time
26     ///
27     /// Credits: https://github.com/rfjakob/earlyoom/blob/dea92ae67997fcb1a0664489c13d49d09d472d40/main.c#L365
28     /// MIT Licensed
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)
60     }
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)?)
66         } else {
67             MemoryStatus::Okay
68         };
70         Ok(Self {
71             memory_info,
72             proc_buf,
73             buf,
74             status,
75             args,
76         })
77     }
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)
82     }
84     fn get_victim(&mut self) -> Result<Process> {
85         kill::choose_victim(&mut self.proc_buf, &mut self.buf, &self.args)
86     }
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)
93         } else {
94             MemoryStatus::Okay
95         };
96         Ok(())
97     }
99     fn free_up_memory(&mut self) -> Result<()> {
100         let victim = self.get_victim()?;
102         // TODO: is this necessary?
103         //
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)?;
111             } else {
112                 kill::kill_and_wait(victim)?;
113             }
114         }
115         Ok(())
116     }
118     // Use the never type here whenever it reaches stable
119     #[allow(unreachable_code)]
120     pub fn poll(&mut self) -> Result<()> {
121         loop {
122             // Update our memory readings
123             self.update_memory_stats()?;
124             if self.memory_is_low() {
125                 self.free_up_memory()?;
126             }
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());
132             }
134             std::thread::sleep(sleep_time);
135         }
136         Ok(())
137     }