Skip to main content

superfluid_daemon/
pressure.rs

1//! OS memory-pressure sources.
2
3use std::sync::atomic::{AtomicU8, Ordering};
4use std::sync::Arc;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum PressureLevel {
8    Normal,
9    Warning,
10    Critical,
11}
12
13pub trait PressureSource {
14    fn level(&mut self) -> PressureLevel;
15}
16
17#[derive(Clone, Default)]
18pub struct ManualPressure {
19    level: Arc<AtomicU8>,
20}
21
22impl ManualPressure {
23    pub fn new() -> ManualPressure {
24        ManualPressure::default()
25    }
26    pub fn set(&self, level: PressureLevel) {
27        self.level.store(
28            match level {
29                PressureLevel::Normal => 0,
30                PressureLevel::Warning => 1,
31                PressureLevel::Critical => 2,
32            },
33            Ordering::Release,
34        );
35    }
36}
37
38impl PressureSource for ManualPressure {
39    fn level(&mut self) -> PressureLevel {
40        match self.level.load(Ordering::Acquire) {
41            0 => PressureLevel::Normal,
42            1 => PressureLevel::Warning,
43            _ => PressureLevel::Critical,
44        }
45    }
46}
47
48#[cfg(target_os = "macos")]
49pub struct MacOsPressure;
50
51#[cfg(target_os = "macos")]
52impl PressureSource for MacOsPressure {
53    fn level(&mut self) -> PressureLevel {
54        let name = c"kern.memorystatus_vm_pressure_level";
55        let mut value: i32 = 0;
56        let mut len = std::mem::size_of::<i32>();
57        // SAFETY: a well-formed NUL-terminated name, an out buffer of
58        // the declared size, no new value.
59        let rc = unsafe {
60            libc::sysctlbyname(
61                name.as_ptr(),
62                &mut value as *mut i32 as *mut libc::c_void,
63                &mut len,
64                std::ptr::null_mut(),
65                0,
66            )
67        };
68        if rc != 0 {
69            return PressureLevel::Normal;
70        }
71        match value {
72            4 => PressureLevel::Critical,
73            2 => PressureLevel::Warning,
74            _ => PressureLevel::Normal,
75        }
76    }
77}
78
79#[cfg(target_os = "linux")]
80pub struct LinuxPsiPressure {
81    pub some_warning_pct: f32,
82    pub full_critical_pct: f32,
83}
84
85#[cfg(target_os = "linux")]
86impl Default for LinuxPsiPressure {
87    fn default() -> Self {
88        LinuxPsiPressure {
89            some_warning_pct: 10.0,
90            full_critical_pct: 5.0,
91        }
92    }
93}
94
95#[cfg(target_os = "linux")]
96impl PressureSource for LinuxPsiPressure {
97    fn level(&mut self) -> PressureLevel {
98        let Ok(text) = std::fs::read_to_string("/proc/pressure/memory") else {
99            return PressureLevel::Normal;
100        };
101        let avg10 = |line: &str| -> f32 {
102            line.split_whitespace()
103                .find_map(|kv| kv.strip_prefix("avg10="))
104                .and_then(|v| v.parse().ok())
105                .unwrap_or(0.0)
106        };
107        let mut level = PressureLevel::Normal;
108        for line in text.lines() {
109            if line.starts_with("full") && avg10(line) >= self.full_critical_pct {
110                return PressureLevel::Critical;
111            }
112            if line.starts_with("some") && avg10(line) >= self.some_warning_pct {
113                level = PressureLevel::Warning;
114            }
115        }
116        level
117    }
118}
119
120pub fn os_default() -> Option<Box<dyn PressureSource + Send>> {
121    #[cfg(target_os = "macos")]
122    {
123        Some(Box::new(MacOsPressure))
124    }
125    #[cfg(target_os = "linux")]
126    {
127        Some(Box::new(LinuxPsiPressure::default()))
128    }
129    #[cfg(not(any(target_os = "macos", target_os = "linux")))]
130    {
131        None
132    }
133}
134
135pub enum PressureConfig {
136    None,
137    Os,
138    Manual(ManualPressure),
139}
140
141impl PressureConfig {
142    pub(crate) fn take_source(&mut self) -> Option<Box<dyn PressureSource + Send>> {
143        match self {
144            PressureConfig::None => None,
145            PressureConfig::Os => os_default(),
146            PressureConfig::Manual(m) => Some(Box::new(m.clone())),
147        }
148    }
149}
150
151impl std::fmt::Debug for PressureConfig {
152    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153        match self {
154            PressureConfig::None => f.write_str("None"),
155            PressureConfig::Os => f.write_str("Os"),
156            PressureConfig::Manual(_) => f.write_str("Manual"),
157        }
158    }
159}
160
161impl Clone for PressureConfig {
162    fn clone(&self) -> Self {
163        match self {
164            PressureConfig::None => PressureConfig::None,
165            PressureConfig::Os => PressureConfig::Os,
166            PressureConfig::Manual(m) => PressureConfig::Manual(m.clone()),
167        }
168    }
169}