pub const REFAULT_CONSECUTIVE_SAMPLES: u32 = 2;
pub const ARM_AFTER_HEALTHY_SAMPLES: u32 = 3;
pub const SETTLE_CAP_SAMPLES: u32 = 60;
pub const PSI_WINDOW_US: u32 = 1_000_000;
pub const PSI_DEFAULT_FULL_STALL_US: u32 = 100_000;
#[must_use]
pub fn psi_stall_threshold_us(requested: u64) -> u32 {
if requested == 0 {
return PSI_DEFAULT_FULL_STALL_US;
}
u32::try_from(requested)
.unwrap_or(PSI_WINDOW_US)
.min(PSI_WINDOW_US)
}
#[derive(Debug, Clone, Copy)]
pub struct PressureThresholds {
pub min_available_bytes: u64,
pub max_refault_rate: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PressureSignal {
LowAvailable,
RefaultSpike,
}
#[derive(Debug)]
pub struct PressureDetector {
thresholds: PressureThresholds,
armed: bool,
samples_seen: u32,
healthy_streak: u32,
refault_hits: u32,
}
impl PressureDetector {
#[must_use]
pub fn new(thresholds: PressureThresholds) -> Self {
Self {
thresholds,
armed: false,
samples_seen: 0,
healthy_streak: 0,
refault_hits: 0,
}
}
#[must_use]
pub fn is_armed(&self) -> bool {
self.armed
}
pub fn evaluate(&mut self, available_bytes: u64, refault_rate: u64) -> Option<PressureSignal> {
let floor = self.thresholds.min_available_bytes;
let refault = self.thresholds.max_refault_rate;
self.samples_seen = self.samples_seen.saturating_add(1);
if !self.armed {
if available_bytes >= floor {
self.healthy_streak += 1;
if self.healthy_streak >= ARM_AFTER_HEALTHY_SAMPLES {
self.armed = true;
}
} else {
self.healthy_streak = 0;
}
if !self.armed {
if self.samples_seen >= SETTLE_CAP_SAMPLES && available_bytes < floor {
return Some(PressureSignal::LowAvailable);
}
return None;
}
return None;
}
if refault == 0 || refault_rate < refault {
self.refault_hits = 0;
} else {
self.refault_hits += 1;
}
if floor > 0 && available_bytes < floor {
return Some(PressureSignal::LowAvailable);
}
if refault > 0 && self.refault_hits >= REFAULT_CONSECUTIVE_SAMPLES {
return Some(PressureSignal::RefaultSpike);
}
None
}
}
#[must_use]
pub fn parse_mem_available(meminfo: &str) -> Option<u64> {
let line = meminfo.lines().find(|l| l.starts_with("MemAvailable:"))?;
let kb: u64 = line.split_whitespace().nth(1)?.parse().ok()?;
Some(kb * 1024)
}
#[must_use]
pub fn parse_workingset_refaults(vmstat: &str) -> Option<u64> {
let mut split_total: Option<u64> = None;
let mut combined: Option<u64> = None;
for line in vmstat.lines() {
let mut parts = line.split_whitespace();
let (Some(name), Some(value)) = (parts.next(), parts.next()) else {
continue;
};
match name {
"workingset_refault_anon" | "workingset_refault_file" => {
let value: u64 = value.parse().ok()?;
split_total = Some(split_total.unwrap_or(0).saturating_add(value));
}
"workingset_refault" => combined = value.parse().ok(),
_ => {}
}
}
split_total.or(combined)
}
#[cfg(test)]
mod tests {
use super::*;
const MIB: u64 = 1024 * 1024;
fn thresholds(min_available: u64, max_refault: u64) -> PressureThresholds {
PressureThresholds {
min_available_bytes: min_available,
max_refault_rate: max_refault,
}
}
fn armed(min_available: u64, max_refault: u64) -> PressureDetector {
let mut d = PressureDetector::new(thresholds(min_available, max_refault));
for _ in 0..ARM_AFTER_HEALTHY_SAMPLES {
assert_eq!(d.evaluate(u64::MAX, 0), None);
}
assert!(d.is_armed(), "three healthy samples must arm the detector");
d
}
#[test]
fn meminfo_parses_mem_available_kb() {
let meminfo = "MemTotal: 16384000 kB\nMemFree: 262144 kB\nMemAvailable: 524288 kB\nBuffers: 1024 kB\n";
assert_eq!(parse_mem_available(meminfo), Some(524_288 * 1024));
}
#[test]
fn meminfo_without_mem_available_is_none() {
let meminfo = "MemTotal: 16384000 kB\nMemFree: 262144 kB\n";
assert_eq!(parse_mem_available(meminfo), None);
}
#[test]
fn meminfo_malformed_number_is_none() {
assert_eq!(parse_mem_available("MemAvailable: lots kB\n"), None);
}
#[test]
fn vmstat_sums_file_and_anon_refaults() {
let vmstat = "nr_free_pages 100\nworkingset_refault_anon 1500\nworkingset_refault_file 2500\npgfault 999\n";
assert_eq!(parse_workingset_refaults(vmstat), Some(4000));
}
#[test]
fn vmstat_falls_back_to_combined_counter() {
let vmstat = "nr_free_pages 100\nworkingset_refault 4200\npgfault 999\n";
assert_eq!(parse_workingset_refaults(vmstat), Some(4200));
}
#[test]
fn vmstat_without_refault_counters_is_none() {
assert_eq!(parse_workingset_refaults("nr_free_pages 100\n"), None);
}
#[test]
fn low_available_trips_immediately_once_armed() {
let mut d = armed(128 * MIB, 0);
assert_eq!(d.evaluate(64 * MIB, 0), Some(PressureSignal::LowAvailable));
}
#[test]
fn available_above_floor_does_not_trip() {
let mut d = armed(128 * MIB, 0);
assert_eq!(d.evaluate(256 * MIB, 0), None);
}
#[test]
fn refault_spike_requires_consecutive_samples() {
let mut d = armed(0, 2000);
assert_eq!(d.evaluate(u64::MAX, 5000), None, "first hit must not trip");
assert_eq!(
d.evaluate(u64::MAX, 5000),
Some(PressureSignal::RefaultSpike),
"second consecutive hit trips"
);
}
#[test]
fn refault_dip_resets_consecutive_count() {
let mut d = armed(0, 2000);
assert_eq!(d.evaluate(u64::MAX, 5000), None);
assert_eq!(d.evaluate(u64::MAX, 100), None, "dip resets the streak");
assert_eq!(d.evaluate(u64::MAX, 5000), None, "streak restarts");
assert_eq!(
d.evaluate(u64::MAX, 5000),
Some(PressureSignal::RefaultSpike)
);
}
#[test]
fn zero_refault_threshold_disables_refault_signal() {
let mut d = armed(0, 0);
assert_eq!(d.evaluate(u64::MAX, u64::MAX), None);
}
#[test]
fn low_available_wins_over_refault_spike() {
let mut d = armed(128 * MIB, 2000);
assert_eq!(d.evaluate(u64::MAX, 5000), None);
assert_eq!(
d.evaluate(64 * MIB, 5000),
Some(PressureSignal::LowAvailable)
);
}
#[test]
fn psi_threshold_zero_uses_default() {
assert_eq!(psi_stall_threshold_us(0), PSI_DEFAULT_FULL_STALL_US);
}
#[test]
fn psi_threshold_clamps_to_window() {
assert_eq!(
psi_stall_threshold_us(u64::from(PSI_WINDOW_US) * 10),
PSI_WINDOW_US
);
assert_eq!(psi_stall_threshold_us(u64::MAX), PSI_WINDOW_US);
}
#[test]
fn psi_threshold_passes_sane_values() {
assert_eq!(psi_stall_threshold_us(150_000), 150_000);
}
#[test]
fn settling_ignores_post_inflation_refault_burst() {
let mut d = PressureDetector::new(thresholds(128 * MIB, 2000));
assert_eq!(d.evaluate(256 * MIB, 50_000), None);
assert_eq!(d.evaluate(256 * MIB, 50_000), None);
assert_eq!(d.evaluate(256 * MIB, 50_000), None);
assert!(d.is_armed());
assert_eq!(d.evaluate(256 * MIB, 50_000), None);
assert_eq!(
d.evaluate(256 * MIB, 50_000),
Some(PressureSignal::RefaultSpike)
);
}
#[test]
fn settling_tolerates_arbitrarily_long_inflation() {
let mut d = PressureDetector::new(thresholds(128 * MIB, 0));
for _ in 0..(SETTLE_CAP_SAMPLES - 1) {
assert_eq!(d.evaluate(0, 0), None, "inflation must not trip the floor");
}
assert!(!d.is_armed());
for _ in 0..ARM_AFTER_HEALTHY_SAMPLES {
assert_eq!(d.evaluate(256 * MIB, 0), None);
}
assert!(d.is_armed());
assert_eq!(d.evaluate(64 * MIB, 0), Some(PressureSignal::LowAvailable));
}
#[test]
fn never_settling_trips_at_the_cap() {
let mut d = PressureDetector::new(thresholds(128 * MIB, 0));
for _ in 0..(SETTLE_CAP_SAMPLES - 1) {
assert_eq!(d.evaluate(0, 0), None);
}
assert_eq!(d.evaluate(0, 0), Some(PressureSignal::LowAvailable));
}
#[test]
fn settling_healthy_streak_resets_on_dip() {
let mut d = PressureDetector::new(thresholds(128 * MIB, 0));
assert_eq!(d.evaluate(256 * MIB, 0), None);
assert_eq!(d.evaluate(256 * MIB, 0), None);
assert_eq!(d.evaluate(0, 0), None, "dip resets the healthy streak");
assert!(!d.is_armed());
for _ in 0..ARM_AFTER_HEALTHY_SAMPLES {
assert_eq!(d.evaluate(256 * MIB, 0), None);
}
assert!(d.is_armed());
}
}