#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MemInfo {
pub total_bytes: u64,
pub available_bytes: u64,
}
impl MemInfo {
pub fn total_gb(&self) -> u64 {
self.total_bytes / (1024 * 1024 * 1024)
}
}
pub fn mem_info() -> Option<MemInfo> {
parse_meminfo(&std::fs::read_to_string("/proc/meminfo").ok()?)
}
fn parse_meminfo(text: &str) -> Option<MemInfo> {
let mut total = None;
let mut available = None;
for line in text.lines() {
if let Some(rest) = line.strip_prefix("MemTotal:") {
total = parse_meminfo_kb(rest);
} else if let Some(rest) = line.strip_prefix("MemAvailable:") {
available = parse_meminfo_kb(rest);
}
if total.is_some() && available.is_some() {
break;
}
}
Some(MemInfo {
total_bytes: total? * 1024,
available_bytes: available? * 1024,
})
}
fn parse_meminfo_kb(rest: &str) -> Option<u64> {
rest.trim().strip_suffix("kB")?.trim().parse().ok()
}
pub fn cpu_package_count() -> Option<usize> {
let dir = std::fs::read_dir("/sys/devices/system/cpu").ok()?;
let mut ids: Vec<u32> = Vec::new();
for entry in dir.flatten() {
let name = entry.file_name();
let Some(rest) = name.to_str().and_then(|s| s.strip_prefix("cpu")) else {
continue;
};
if rest.is_empty() || !rest.bytes().all(|b| b.is_ascii_digit()) {
continue;
}
let Ok(id) = std::fs::read_to_string(entry.path().join("topology/physical_package_id"))
else {
continue;
};
let Ok(id) = id.trim().parse::<u32>() else {
continue;
};
if !ids.contains(&id) {
ids.push(id);
}
}
(!ids.is_empty()).then_some(ids.len())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_meminfo_format() {
let text = "MemTotal: 131781120 kB\n\
MemFree: 8123456 kB\n\
MemAvailable: 98765432 kB\n\
Buffers: 123456 kB\n";
let m = parse_meminfo(text).unwrap();
assert_eq!(m.total_bytes, 131_781_120 * 1024);
assert_eq!(m.available_bytes, 98_765_432 * 1024);
assert_eq!(m.total_gb(), 125);
}
#[test]
fn missing_memavailable_is_none_not_a_guess() {
assert!(parse_meminfo("MemTotal: 100 kB\nMemFree: 50 kB\n").is_none());
assert!(parse_meminfo("").is_none());
}
#[test]
fn reads_live_host() {
if let Some(m) = mem_info() {
assert!(m.total_bytes > 0);
assert!(m.available_bytes <= m.total_bytes);
}
}
#[test]
fn package_count_is_at_least_one_where_topology_is_exposed() {
if let Some(n) = cpu_package_count() {
assert!(n >= 1);
}
}
}