use anyhow::{Result, anyhow};
use std::ffi::c_void;
use std::mem::{size_of, zeroed};
use std::process::Command;
const HOST_VM_INFO64: libc::c_int = 4;
unsafe extern "C" {
fn host_statistics64(
host: libc::mach_port_t,
flavor: libc::c_int,
info: *mut libc::c_int,
count: *mut libc::c_uint,
) -> libc::c_int;
fn mach_host_self() -> libc::mach_port_t;
}
#[repr(C)]
#[derive(Default, Clone, Copy)]
struct XswUsage {
total: u64,
avail: u64,
used: u64,
pagesize: u32,
encrypted: i32,
}
#[derive(Debug, Clone, Default)]
pub struct Memory {
pub total: u64,
pub free: u64,
pub active: u64,
pub inactive: u64,
pub wired: u64,
pub compressed: u64,
pub purgeable: u64,
pub decompressions: u64,
}
impl Memory {
pub fn footprint(&self) -> u64 {
self.active + self.wired + self.compressed
}
pub fn reclaimable(&self) -> u64 {
self.inactive + self.purgeable
}
pub fn pressure_pct(&self) -> f64 {
if self.total == 0 {
return 0.0;
}
self.footprint() as f64 / self.total as f64 * 100.0
}
}
#[derive(Debug, Clone, Default)]
pub struct Swap {
pub total: u64,
pub used: u64,
}
impl Swap {
pub fn used_pct(&self) -> f64 {
if self.total == 0 {
return 0.0;
}
self.used as f64 / self.total as f64 * 100.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PowerMode {
Low,
Automatic,
High,
Unknown,
}
impl std::fmt::Display for PowerMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Low => "Low Power",
Self::Automatic => "Automatic",
Self::High => "High Power",
Self::Unknown => "unknown",
})
}
}
#[derive(Debug, Clone)]
pub struct Power {
pub mode: PowerMode,
pub throttled: bool,
pub thermal_note: String,
}
#[derive(Debug, Clone)]
pub struct Snapshot {
pub memory: Memory,
pub swap: Swap,
pub power: Power,
}
pub fn snapshot() -> Result<Snapshot> {
Ok(Snapshot {
memory: memory()?,
swap: swap()?,
power: power(),
})
}
fn sysctl_by_name<T: Copy + Default>(name: &str) -> Result<T> {
let cname = std::ffi::CString::new(name)?;
let mut out = T::default();
let mut len = size_of::<T>();
let rc = unsafe {
libc::sysctlbyname(
cname.as_ptr(),
&mut out as *mut T as *mut c_void,
&mut len,
std::ptr::null_mut(),
0,
)
};
if rc != 0 {
return Err(anyhow!(
"sysctl {name}: {}",
std::io::Error::last_os_error()
));
}
Ok(out)
}
pub fn memory() -> Result<Memory> {
let total: u64 = sysctl_by_name("hw.memsize")?;
let page: u64 = sysctl_by_name::<u32>("hw.pagesize")? as u64;
let mut vm: libc::vm_statistics64 = unsafe { zeroed() };
let mut count = (size_of::<libc::vm_statistics64>() / size_of::<libc::c_int>()) as libc::c_uint;
let rc = unsafe {
host_statistics64(
mach_host_self(),
HOST_VM_INFO64,
&mut vm as *mut _ as *mut libc::c_int,
&mut count,
)
};
if rc != 0 {
return Err(anyhow!("host_statistics64 failed: {rc}"));
}
Ok(Memory {
total,
free: (vm.free_count as u64 + vm.speculative_count as u64) * page,
active: vm.active_count as u64 * page,
inactive: vm.inactive_count as u64 * page,
wired: vm.wire_count as u64 * page,
compressed: vm.compressor_page_count as u64 * page,
purgeable: vm.purgeable_count as u64 * page,
decompressions: vm.decompressions as u64,
})
}
pub fn swap() -> Result<Swap> {
let u: XswUsage = sysctl_by_name("vm.swapusage")?;
Ok(Swap {
total: u.total,
used: u.used,
})
}
pub fn power() -> Power {
let mode = Command::new("pmset")
.arg("-g")
.output()
.ok()
.and_then(|o| {
String::from_utf8_lossy(&o.stdout)
.lines()
.find_map(|l| {
l.split_whitespace()
.nth(1)
.filter(|_| l.contains("powermode"))
})
.map(str::to_owned)
})
.map_or(PowerMode::Unknown, |v| match v.trim() {
"0" => PowerMode::Automatic,
"1" => PowerMode::Low,
"2" => PowerMode::High,
_ => PowerMode::Unknown,
});
let therm = Command::new("pmset")
.args(["-g", "therm"])
.output()
.map(|o| String::from_utf8_lossy(&o.stdout).into_owned())
.unwrap_or_default();
let throttled = therm.contains("CPU_Speed_Limit")
|| (therm.contains("warning level") && !therm.contains("No thermal warning"));
Power {
mode,
throttled,
thermal_note: if throttled {
therm
.lines()
.find(|l| l.contains("Limit"))
.unwrap_or("thermal limit recorded")
.trim()
.to_owned()
} else {
"no thermal or CPU-power limit recorded".to_owned()
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn memory_reads_are_coherent() {
let m = memory().expect("host_statistics64 should work on macOS");
assert!(m.total > 0);
assert!(m.footprint() <= m.total, "footprint exceeds physical RAM");
assert!(m.free <= m.total);
assert!((0.0..=100.0).contains(&m.pressure_pct()));
}
#[test]
fn swap_accounting_adds_up() {
let s = swap().expect("vm.swapusage should be readable");
assert!(s.used <= s.total);
assert!((0.0..=100.0).contains(&s.used_pct()));
}
}