use super::history::HistoryEvent;
use super::journal::{JournalAction, JournalEntry};
use super::types::Sample;
use common::{Error, GuardTrigger};
pub fn pressure_line(s: &Sample) -> String {
let avail = if s.mem_available_mb == u64::MAX || s.mem_total_mb == 0 {
"available unknown".to_string()
} else {
let pct = s.mem_available_mb.saturating_mul(100) / s.mem_total_mb;
format!(
"{} MB of {} MB available ({pct}%)",
s.mem_available_mb, s.mem_total_mb
)
};
format!(
"{}: some {:.1}% full {:.1}%, {avail}",
s.source, s.some_avg10, s.full_avg10
)
}
pub fn trigger_line(t: &GuardTrigger) -> String {
format!(
"acts when pressure is high and available memory is below {}% or {} MB",
t.act_below_available_pct, t.mem_available_floor_mb
)
}
pub fn intervention_line(e: &JournalEntry) -> String {
match e.action {
JournalAction::Freeze => format!("{} frozen", e.cgroup),
JournalAction::Cap => {
let bytes = e
.our_high
.as_deref()
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(0);
format!("{} capped at {}", e.cgroup, common::format_bytes(bytes))
}
}
}
pub fn history_line(e: &HistoryEvent, now: u64) -> String {
let age = super::history::format_age(now, e.ts);
format!("{age:>9} {:<6} {} {}", e.kind.word(), e.app, e.detail)
}
pub fn config_error_path() -> String {
let user = dirs::config_dir().map(|d| d.join("rlm").join("config.yaml"));
if let Some(p) = &user {
if p.exists() {
return p.display().to_string();
}
}
let system = std::path::Path::new("/etc/rlm/config.yaml");
if system.exists() {
return system.display().to_string();
}
user.map(|p| p.display().to_string())
.unwrap_or_else(|| "/etc/rlm/config.yaml".to_string())
}
pub fn config_error_line(path: &str, e: &Error) -> String {
format!("invalid ({path}): {e}")
}
pub fn pressure_unavailable() -> &'static str {
"unavailable (neither app.slice nor system PSI could be read)"
}
#[cfg(test)]
mod tests {
use super::*;
use crate::guard::types::PsiSource;
#[test]
fn pressure_and_trigger_lines() {
let s = Sample {
some_avg10: 1.24,
full_avg10: 0.0,
mem_available_mb: 9000,
mem_total_mb: 16000,
source: PsiSource::AppSlice,
};
assert_eq!(
pressure_line(&s),
"app.slice: some 1.2% full 0.0%, 9000 MB of 16000 MB available (56%)"
);
assert_eq!(
trigger_line(&common::GuardTrigger::default()),
"acts when pressure is high and available memory is below 20% or 400 MB"
);
}
#[test]
fn unreadable_meminfo_prints_available_unknown() {
let sentinel = Sample {
some_avg10: 5.0,
full_avg10: 1.0,
mem_available_mb: u64::MAX,
mem_total_mb: 0,
source: PsiSource::System,
};
assert_eq!(
pressure_line(&sentinel),
"system: some 5.0% full 1.0%, available unknown"
);
}
#[test]
fn pressure_unavailable_names_both_sources() {
assert_eq!(
pressure_unavailable(),
"unavailable (neither app.slice nor system PSI could be read)"
);
}
#[test]
fn config_error_line_includes_path_and_message() {
let e = Error::Config("guard: bad value".into());
assert_eq!(
config_error_line("/x/config.yaml", &e),
"invalid (/x/config.yaml): config error: guard: bad value"
);
}
}