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 { 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()); } }