#[cfg(any(target_os = "linux", test))]
use std::fs;
#[cfg(any(target_os = "linux", test))]
use std::path::Path;
#[cfg(any(target_os = "linux", test))]
use std::sync::atomic::{AtomicBool, Ordering};
#[cfg(any(target_os = "linux", test))]
const PSI_ELEVATED_THRESHOLD: f64 = 10.0;
#[cfg(target_os = "linux")]
const PSI_PATH: &str = "/proc/pressure/io";
#[cfg(any(target_os = "linux", test))]
static PSI_UNAVAILABLE_LOGGED: AtomicBool = AtomicBool::new(false);
#[cfg(any(target_os = "linux", test))]
static PSI_UNPARSEABLE_LOGGED: AtomicBool = AtomicBool::new(false);
pub(crate) trait IoPressureProbe: Send + Sync {
fn elevated(&self) -> bool;
}
#[cfg(target_os = "linux")]
pub(crate) struct PsiProbe;
#[cfg(target_os = "linux")]
impl IoPressureProbe for PsiProbe {
fn elevated(&self) -> bool {
psi_elevated_at(Path::new(PSI_PATH))
}
}
#[cfg(any(target_os = "linux", test))]
fn psi_elevated_at(path: &Path) -> bool {
match fs::read_to_string(path) {
Ok(contents) => match parse_some_avg10(&contents) {
Some(avg10) => avg10 > PSI_ELEVATED_THRESHOLD,
None => {
log_fail_open(
&PSI_UNPARSEABLE_LOGGED,
"PSI io pressure line unparseable; treating as not elevated",
);
false
}
},
Err(_) => {
log_fail_open(
&PSI_UNAVAILABLE_LOGGED,
"/proc/pressure/io unavailable; treating pressure as not elevated",
);
false
}
}
}
#[cfg(any(target_os = "linux", test))]
fn log_fail_open(guard: &'static AtomicBool, message: &'static str) {
if !guard.swap(true, Ordering::Relaxed) {
tracing::debug!(target: "hallouminate::daemon", "{message}");
}
}
#[cfg(any(target_os = "linux", test))]
fn parse_some_avg10(contents: &str) -> Option<f64> {
contents
.lines()
.find_map(|line| line.strip_prefix("some "))?
.split_whitespace()
.find_map(|field| field.strip_prefix("avg10="))?
.parse::<f64>()
.ok()
}
#[cfg(not(target_os = "linux"))]
pub(crate) struct NoPressureSignal;
#[cfg(not(target_os = "linux"))]
impl IoPressureProbe for NoPressureSignal {
fn elevated(&self) -> bool {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_some_avg10_extracts_the_value() {
let contents = "some avg10=12.34 avg60=8.01 avg300=3.20 total=123456\nfull avg10=1.00 avg60=0.50 avg300=0.10 total=999\n";
assert_eq!(parse_some_avg10(contents), Some(12.34));
}
#[test]
fn parse_some_avg10_returns_none_when_line_missing() {
assert_eq!(parse_some_avg10("full avg10=1.00\n"), None);
}
#[test]
fn parse_some_avg10_returns_none_on_garbage() {
assert_eq!(parse_some_avg10("some avg10=not-a-number\n"), None);
}
#[test]
fn psi_elevated_at_fails_open_when_path_absent() {
assert!(!psi_elevated_at(Path::new(
"/nonexistent/hallouminate/proc/pressure/io"
)));
}
#[test]
fn psi_elevated_at_fails_open_on_unparseable_file() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("io");
std::fs::write(&path, "garbage without a some line\n").expect("write");
assert!(!psi_elevated_at(&path));
}
#[test]
fn psi_elevated_at_reports_elevated_above_threshold() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("io");
std::fs::write(
&path,
"some avg10=42.00 avg60=1.00 avg300=1.00 total=1\nfull avg10=1.00\n",
)
.expect("write");
assert!(psi_elevated_at(&path));
}
#[test]
fn psi_elevated_at_not_elevated_at_or_below_threshold() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("io");
std::fs::write(&path, "some avg10=1.23 avg60=1.00 avg300=1.00 total=1\n").expect("write");
assert!(!psi_elevated_at(&path));
}
#[test]
fn psi_elevated_at_not_elevated_exactly_at_threshold() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("io");
std::fs::write(&path, "some avg10=10.00 avg60=1.00 avg300=1.00 total=1\n").expect("write");
assert!(!psi_elevated_at(&path));
}
#[test]
fn psi_elevated_at_elevated_just_above_threshold() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("io");
std::fs::write(&path, "some avg10=10.01 avg60=1.00 avg300=1.00 total=1\n").expect("write");
assert!(psi_elevated_at(&path));
}
#[cfg(not(target_os = "linux"))]
#[test]
fn no_pressure_signal_is_never_elevated() {
assert!(!NoPressureSignal.elevated());
}
}