nova-shell/stats-daemon/src/mem.rs

112 lines
2.9 KiB
Rust

use std::fs;
use std::io::Write;
pub struct MemInfo {
pub percent: u64,
pub used_gb: f64,
pub total_gb: f64,
pub avail_gb: f64,
pub cached_gb: f64,
pub buffers_gb: f64,
}
pub fn parse_meminfo(input: &str) -> Option<MemInfo> {
let mut total = 0u64;
let mut avail = 0u64;
let mut buffers = 0u64;
let mut cached = 0u64;
let mut sreclaimable = 0u64;
for line in input.lines() {
let mut parts = line.splitn(2, ':');
let key = parts.next().unwrap_or("").trim();
let val: u64 = parts
.next()
.unwrap_or("")
.split_whitespace()
.next()
.unwrap_or("")
.parse()
.unwrap_or(0);
match key {
"MemTotal" => total = val,
"MemAvailable" => avail = val,
"Buffers" => buffers = val,
"Cached" => cached = val,
"SReclaimable" => sreclaimable = val,
_ => {}
}
}
if total == 0 {
return None;
}
let used = total.saturating_sub(avail);
let cached_total = cached + sreclaimable;
let gb = |kb: u64| kb as f64 / 1_048_576.0;
Some(MemInfo {
percent: used * 100 / total,
used_gb: gb(used),
total_gb: gb(total),
avail_gb: gb(avail),
cached_gb: gb(cached_total),
buffers_gb: gb(buffers),
})
}
pub fn emit_mem(out: &mut impl Write) {
let content = fs::read_to_string("/proc/meminfo").unwrap_or_default();
if let Some(m) = parse_meminfo(&content) {
let _ = writeln!(
out,
"{{\"type\":\"mem\",\"percent\":{},\"used_gb\":{:.3},\"total_gb\":{:.3},\"avail_gb\":{:.3},\"cached_gb\":{:.3},\"buffers_gb\":{:.3}}}",
m.percent, m.used_gb, m.total_gb, m.avail_gb, m.cached_gb, m.buffers_gb,
);
}
}
#[cfg(test)]
mod tests {
use super::*;
const MEMINFO_SAMPLE: &str = "\
MemTotal: 16384000 kB
MemFree: 2048000 kB
MemAvailable: 4096000 kB
Buffers: 512000 kB
Cached: 3072000 kB
SReclaimable: 512000 kB
SwapTotal: 8192000 kB
SwapFree: 8192000 kB";
#[test]
fn parse_meminfo_percent() {
let m = parse_meminfo(MEMINFO_SAMPLE).unwrap();
assert_eq!(m.percent, 75);
}
#[test]
fn parse_meminfo_total_gb() {
let m = parse_meminfo(MEMINFO_SAMPLE).unwrap();
assert!((m.total_gb - 15.625).abs() < 0.001);
}
#[test]
fn parse_meminfo_cached_includes_sreclaimable() {
let m = parse_meminfo(MEMINFO_SAMPLE).unwrap();
let expected = 3_584_000.0 / 1_048_576.0;
assert!((m.cached_gb - expected).abs() < 0.001);
}
#[test]
fn parse_meminfo_zero_total_returns_none() {
assert!(parse_meminfo("MemFree: 1000 kB\n").is_none());
}
#[test]
fn parse_meminfo_empty_returns_none() {
assert!(parse_meminfo("").is_none());
}
}