pub const DEFAULT_MAX_LOAD: f64 = 2.0;
pub fn free_ram_gb() -> Option<f64> {
#[cfg(target_os = "macos")]
{
let out = std::process::Command::new("vm_stat").output().ok()?;
let text = String::from_utf8(out.stdout).ok()?;
return parse_vm_stat_free_gb(&text);
}
#[cfg(target_os = "linux")]
{
let text = std::fs::read_to_string("/proc/meminfo").ok()?;
let kb: f64 = text
.lines()
.find_map(|l| l.strip_prefix("MemAvailable:"))?
.split_whitespace()
.next()?
.parse()
.ok()?;
return Some(kb / 1024.0 / 1024.0);
}
#[allow(unreachable_code)]
None
}
#[cfg(any(target_os = "macos", test))]
fn parse_vm_stat_free_gb(text: &str) -> Option<f64> {
let page_size: f64 = text
.lines()
.next()?
.split("page size of ")
.nth(1)?
.split_whitespace()
.next()?
.parse()
.ok()?;
let pages = |label: &str| -> f64 {
text.lines()
.find_map(|l| l.strip_prefix(label))
.and_then(|v| v.trim().trim_end_matches('.').parse::<f64>().ok())
.unwrap_or(0.0)
};
let usable = pages("Pages free:") + pages("Pages inactive:");
Some(usable * page_size / 1024.0 / 1024.0 / 1024.0)
}
pub fn ensure_fits_in_ram(needs_gb: f64, headroom_gb: f64) -> anyhow::Result<()> {
if needs_gb <= 0.0 {
return Ok(());
}
let Some(free) = free_ram_gb() else {
return Ok(());
};
anyhow::ensure!(
free >= needs_gb + headroom_gb,
"this model needs about {needs_gb:.1} GiB and only {free:.1} GiB is free \
(plus {headroom_gb:.1} GiB headroom). It would run from swap, and a paged \
run reports a real-looking number for work the disk did. Close something, \
or pass --max-load 0 to measure anyway and accept that the number is not \
publishable."
);
Ok(())
}
pub fn load_average_1min() -> Option<f64> {
if let Ok(s) = std::fs::read_to_string("/proc/loadavg") {
return s.split_whitespace().next()?.parse().ok();
}
let out = std::process::Command::new("sysctl")
.args(["-n", "vm.loadavg"])
.output()
.ok()?;
parse_sysctl_loadavg(&String::from_utf8_lossy(&out.stdout))
}
fn parse_sysctl_loadavg(s: &str) -> Option<f64> {
s.split_whitespace().find_map(|tok| {
tok.trim_matches(|c| c == '{' || c == '}')
.parse::<f64>()
.ok()
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ThermalPressure {
Nominal,
Fair,
Serious,
Critical,
}
impl ThermalPressure {
pub fn from_ns_thermal_state(raw: i64) -> Option<Self> {
match raw {
0 => Some(ThermalPressure::Nominal),
1 => Some(ThermalPressure::Fair),
2 => Some(ThermalPressure::Serious),
3 => Some(ThermalPressure::Critical),
_ => None,
}
}
pub fn as_str(self) -> &'static str {
match self {
ThermalPressure::Nominal => "nominal",
ThermalPressure::Fair => "fair",
ThermalPressure::Serious => "serious",
ThermalPressure::Critical => "critical",
}
}
pub fn degrades_measurements(self) -> bool {
self >= ThermalPressure::Serious
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct ThermalReading {
pub pressure: Option<ThermalPressure>,
pub source: Option<&'static str>,
pub cpu_speed_limit_percent: Option<u32>,
}
impl ThermalReading {
pub fn measured(&self) -> bool {
self.pressure.is_some() || self.cpu_speed_limit_percent.is_some()
}
pub fn is_degraded(&self) -> bool {
self.pressure
.is_some_and(ThermalPressure::degrades_measurements)
|| self.cpu_speed_limit_percent.is_some_and(|p| p < 100)
}
pub fn describe(&self) -> String {
let mut parts = Vec::new();
match self.pressure {
Some(p) => parts.push(format!("thermal {}", p.as_str())),
None => parts.push("thermal ?".to_string()),
}
if let Some(p) = self.cpu_speed_limit_percent {
if p < 100 {
parts.push(format!("CPU capped to {p}%"));
}
}
parts.join(", ")
}
}
pub fn thermal_reading() -> ThermalReading {
let pressure = ns_thermal_state().and_then(ThermalPressure::from_ns_thermal_state);
ThermalReading {
pressure,
source: pressure.map(|_| "NSProcessInfo.thermalState"),
cpu_speed_limit_percent: pmset_cpu_speed_limit(),
}
}
#[cfg(target_os = "macos")]
fn ns_thermal_state() -> Option<i64> {
use std::ffi::c_void;
use std::os::raw::c_char;
#[link(name = "Foundation", kind = "framework")]
extern "C" {}
extern "C" {
fn objc_getClass(name: *const c_char) -> *mut c_void;
fn sel_registerName(name: *const c_char) -> *mut c_void;
fn objc_msgSend();
}
unsafe {
let cls = objc_getClass(c"NSProcessInfo".as_ptr());
if cls.is_null() {
return None;
}
let send_id: extern "C" fn(*mut c_void, *mut c_void) -> *mut c_void =
std::mem::transmute(objc_msgSend as *const ());
let info = send_id(cls, sel_registerName(c"processInfo".as_ptr()));
if info.is_null() {
return None;
}
let sel_thermal = sel_registerName(c"thermalState".as_ptr());
let responds: extern "C" fn(*mut c_void, *mut c_void, *mut c_void) -> bool =
std::mem::transmute(objc_msgSend as *const ());
if !responds(
info,
sel_registerName(c"respondsToSelector:".as_ptr()),
sel_thermal,
) {
return None;
}
let send_int: extern "C" fn(*mut c_void, *mut c_void) -> i64 =
std::mem::transmute(objc_msgSend as *const ());
Some(send_int(info, sel_thermal))
}
}
#[cfg(not(target_os = "macos"))]
fn ns_thermal_state() -> Option<i64> {
None
}
#[cfg(target_os = "macos")]
fn pmset_cpu_speed_limit() -> Option<u32> {
let out = std::process::Command::new("pmset")
.args(["-g", "therm"])
.output()
.ok()?;
parse_pmset_therm(&String::from_utf8_lossy(&out.stdout))
}
#[cfg(not(target_os = "macos"))]
fn pmset_cpu_speed_limit() -> Option<u32> {
None
}
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
fn parse_pmset_therm(s: &str) -> Option<u32> {
s.lines()
.find_map(|l| l.split_once("CPU_Speed_Limit"))
.and_then(|(_, rest)| rest.split('=').nth(1))
.and_then(|v| v.trim().parse().ok())
}
pub fn ensure_quiet_enough(max_load: f64) -> anyhow::Result<Option<f64>> {
let load = load_average_1min();
if max_load <= 0.0 {
return Ok(load);
}
if let Some(l) = load {
anyhow::ensure!(
l < max_load,
"host 1-minute load average is {l:.2}, above the {max_load:.2} bar: \
a timed run here is noise, not a measurement (known-good rows read \
25-45% low under load). Wait for the box to go quiet, or pass \
--max-load 0 to measure anyway and accept that the number is not \
publishable."
);
}
Ok(load)
}
pub fn wait_until_quiet_enough(max_load: f64, timeout: std::time::Duration) -> anyhow::Result<()> {
if max_load <= 0.0 {
return Ok(());
}
let start = std::time::Instant::now();
loop {
match load_average_1min() {
None => return Ok(()),
Some(l) if l < max_load => return Ok(()),
Some(l) => {
if start.elapsed() >= timeout {
anyhow::bail!(
"host 1-minute load average is still {l:.2} after waiting {}s for it \
to fall below {max_load:.2}. Something other than this suite is \
busy; a timed run here is noise, not a measurement.",
timeout.as_secs()
);
}
std::thread::sleep(std::time::Duration::from_secs(5));
}
}
}
}
pub fn ensure_cool_enough(reading: &ThermalReading, enabled: bool) -> anyhow::Result<()> {
if !enabled || !reading.is_degraded() {
return Ok(());
}
anyhow::bail!(
"host thermal state is {}: the OS is reducing sustained performance right \
now, so a timed run measures the cooling system and not the engine. Let \
the box cool down, or pass --max-load 0 to measure anyway and accept that \
the number is not publishable.",
reading.describe()
);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sysctl_loadavg_braces_are_not_mistaken_for_a_number() {
assert_eq!(parse_sysctl_loadavg("{ 1.23 2.34 3.45 }"), Some(1.23));
assert_eq!(parse_sysctl_loadavg("{1.23 2.34 3.45}"), Some(1.23));
}
#[test]
fn an_unparseable_loadavg_is_none_rather_than_zero() {
assert_eq!(parse_sysctl_loadavg(""), None);
assert_eq!(parse_sysctl_loadavg("no such oid"), None);
}
#[test]
fn free_ram_counts_reclaimable_pages_not_just_free_ones() {
let sample = "Mach Virtual Memory Statistics: (page size of 16384 bytes)\n\
Pages free: 65536.\n\
Pages active: 500000.\n\
Pages inactive: 65536.\n\
Pages speculative: 1000.\n";
let gb = parse_vm_stat_free_gb(sample).expect("a well-formed vm_stat must parse");
assert!(
(gb - 2.0).abs() < 0.01,
"expected 2 GiB from free+inactive, got {gb}"
);
}
#[test]
fn an_unknown_footprint_never_refuses() {
ensure_fits_in_ram(0.0, 2.0).expect("an unknown footprint must not refuse");
}
#[test]
fn a_model_larger_than_free_ram_is_refused_with_both_figures() {
if free_ram_gb().is_none() {
return; }
let err = ensure_fits_in_ram(1_000_000.0, 2.0)
.expect_err("a model larger than any host must be refused");
let msg = err.to_string();
assert!(
msg.contains("swap") && msg.contains("free"),
"the refusal must say it would page and how much is free, got: {msg}"
);
}
#[test]
fn pmset_reports_a_speed_limit_only_when_it_has_one() {
assert_eq!(parse_pmset_therm("CPU_Speed_Limit \t= 70\n"), Some(70));
assert_eq!(
parse_pmset_therm("Note: No thermal warning level has been recorded"),
None
);
}
#[test]
fn waiting_is_disabled_by_the_same_switch_that_disables_refusing() {
let start = std::time::Instant::now();
wait_until_quiet_enough(0.0, std::time::Duration::from_secs(30))
.expect("max_load 0 must never block");
assert!(
start.elapsed() < std::time::Duration::from_secs(1),
"max_load 0 returned only after waiting, so it did not disable the wait"
);
}
#[test]
fn an_impossible_bar_times_out_instead_of_waiting_forever() {
if load_average_1min().is_none() {
return; }
let err = wait_until_quiet_enough(0.000_001, std::time::Duration::from_millis(1))
.expect_err("a bar no host can clear must time out");
let msg = err.to_string();
assert!(
msg.contains("still") && msg.contains("busy"),
"the timeout must say what it waited for, got: {msg}"
);
}
#[test]
fn a_disabled_gate_still_reports_the_load_it_read() {
assert!(ensure_quiet_enough(0.0).is_ok());
assert!(ensure_quiet_enough(-1.0).is_ok());
}
#[test]
fn the_gate_refuses_above_the_bar_and_says_the_number() {
let refuse = |l: f64, max: f64| max > 0.0 && l >= max;
assert!(refuse(208.0, 2.0));
assert!(refuse(2.0, 2.0), "the bar is exclusive");
assert!(!refuse(1.99, 2.0));
assert!(!refuse(208.0, 0.0), "max_load 0 disables the gate");
}
#[test]
fn an_unmeasured_thermal_reading_is_not_a_nominal_one() {
let unknown = ThermalReading::default();
assert!(!unknown.measured(), "nothing was read");
assert!(!unknown.is_degraded(), "unknown must not read as throttled");
assert_eq!(unknown.describe(), "thermal ?");
let cool = ThermalReading {
pressure: Some(ThermalPressure::Nominal),
source: Some("NSProcessInfo.thermalState"),
cpu_speed_limit_percent: None,
};
assert!(cool.measured(), "nominal is a measurement, not a silence");
assert!(!cool.is_degraded());
assert_ne!(
cool, unknown,
"measured-nominal must differ from unmeasured"
);
}
#[test]
fn thermal_pressure_refuses_only_from_serious_upward() {
assert!(!ThermalPressure::Nominal.degrades_measurements());
assert!(!ThermalPressure::Fair.degrades_measurements());
assert!(ThermalPressure::Serious.degrades_measurements());
assert!(ThermalPressure::Critical.degrades_measurements());
}
#[test]
fn an_unknown_ns_thermal_level_is_not_decoded_as_nominal() {
assert_eq!(
ThermalPressure::from_ns_thermal_state(0),
Some(ThermalPressure::Nominal)
);
assert_eq!(
ThermalPressure::from_ns_thermal_state(3),
Some(ThermalPressure::Critical)
);
assert_eq!(ThermalPressure::from_ns_thermal_state(4), None);
assert_eq!(ThermalPressure::from_ns_thermal_state(-1), None);
}
#[test]
fn a_capped_cpu_is_degraded_even_without_a_pressure_level() {
let intel = ThermalReading {
pressure: None,
source: None,
cpu_speed_limit_percent: Some(70),
};
assert!(intel.measured());
assert!(intel.is_degraded());
assert!(intel.describe().contains("70%"));
let intel_ok = ThermalReading {
cpu_speed_limit_percent: Some(100),
..Default::default()
};
assert!(intel_ok.measured());
assert!(!intel_ok.is_degraded());
}
#[test]
fn the_thermal_gate_refuses_when_hot_and_never_when_unknown() {
let hot = ThermalReading {
pressure: Some(ThermalPressure::Critical),
source: Some("NSProcessInfo.thermalState"),
cpu_speed_limit_percent: None,
};
let err = ensure_cool_enough(&hot, true).unwrap_err().to_string();
assert!(
err.contains("critical"),
"the refusal names what it caught: {err}"
);
assert!(ensure_cool_enough(&hot, false).is_ok());
assert!(ensure_cool_enough(&ThermalReading::default(), true).is_ok());
assert!(ensure_cool_enough(
&ThermalReading {
pressure: Some(ThermalPressure::Fair),
source: Some("NSProcessInfo.thermalState"),
cpu_speed_limit_percent: None,
},
true
)
.is_ok());
}
#[test]
#[cfg(target_os = "macos")]
fn macos_reports_a_thermal_level_in_the_documented_range() {
let raw = ns_thermal_state().expect("macOS always answers thermalState");
assert!(
(0..=3).contains(&raw),
"NSProcessInfoThermalState out of documented range: {raw}"
);
let reading = thermal_reading();
assert!(
reading.measured(),
"a macOS host must produce a real thermal reading, not a null"
);
assert!(reading.pressure.is_some());
assert_eq!(reading.source, Some("NSProcessInfo.thermalState"));
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct HostSpec {
pub cpu: Option<String>,
pub arch: &'static str,
pub cores: Option<usize>,
pub perf_cores: Option<usize>,
pub ram_gb: Option<f64>,
pub os: Option<String>,
}
#[cfg(target_os = "macos")]
fn sysctl(key: &str) -> Option<String> {
let out = std::process::Command::new("sysctl")
.args(["-n", key])
.output()
.ok()?;
if !out.status.success() {
return None;
}
let v = String::from_utf8_lossy(&out.stdout).trim().to_string();
(!v.is_empty()).then_some(v)
}
pub fn host_spec() -> HostSpec {
let arch = std::env::consts::ARCH;
#[cfg(target_os = "macos")]
let (cpu, cores, perf_cores, ram_gb, os) = {
let cpu = sysctl("machdep.cpu.brand_string");
let cores = sysctl("hw.physicalcpu").and_then(|v| v.parse().ok());
let perf = sysctl("hw.perflevel0.physicalcpu").and_then(|v| v.parse().ok());
let ram = sysctl("hw.memsize")
.and_then(|v| v.parse::<u64>().ok())
.map(|b| b as f64 / 1024.0 / 1024.0 / 1024.0);
let os = std::process::Command::new("sw_vers")
.arg("-productVersion")
.output()
.ok()
.filter(|o| o.status.success())
.map(|o| format!("macOS {}", String::from_utf8_lossy(&o.stdout).trim()));
(cpu, cores, perf, ram, os)
};
#[cfg(target_os = "linux")]
let (cpu, cores, perf_cores, ram_gb, os) = {
let info = std::fs::read_to_string("/proc/cpuinfo").unwrap_or_default();
let cpu = info
.lines()
.find(|l| l.starts_with("model name"))
.and_then(|l| l.split_once(':'))
.map(|(_, v)| v.trim().to_string());
let mut pairs = std::collections::BTreeSet::new();
let (mut phys, mut core) = (None, None);
for line in info.lines() {
if let Some((k, v)) = line.split_once(':') {
match k.trim() {
"physical id" => phys = v.trim().parse::<u32>().ok(),
"core id" => core = v.trim().parse::<u32>().ok(),
_ => {}
}
}
if line.trim().is_empty() {
if let (Some(p), Some(c)) = (phys, core) {
pairs.insert((p, c));
}
phys = None;
core = None;
}
}
if let (Some(p), Some(c)) = (phys, core) {
pairs.insert((p, c));
}
let cores = (!pairs.is_empty()).then_some(pairs.len());
let ram_gb = std::fs::read_to_string("/proc/meminfo")
.ok()
.and_then(|m| {
m.lines()
.find(|l| l.starts_with("MemTotal:"))
.and_then(|l| l.split_whitespace().nth(1)?.parse::<f64>().ok())
})
.map(|kb| kb / 1024.0 / 1024.0);
let os = std::fs::read_to_string("/proc/sys/kernel/osrelease")
.ok()
.map(|r| format!("Linux {}", r.trim()));
(cpu, cores, None, ram_gb, os)
};
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
let (cpu, cores, perf_cores, ram_gb, os) = (None, None, None, None, None);
HostSpec {
cpu,
arch,
cores,
perf_cores,
ram_gb,
os,
}
}
pub fn host_label(spec: &HostSpec) -> String {
let mut out = spec.cpu.clone().unwrap_or_else(|| spec.arch.to_string());
if let Some(c) = spec.cores {
out.push_str(&format!(" ({c}c"));
if let Some(p) = spec.perf_cores.filter(|p| *p != c) {
out.push_str(&format!("/{p}p"));
}
out.push(')');
}
if let Some(os) = &spec.os {
out.push(' ');
out.push_str(os);
}
out
}
#[cfg(test)]
mod host_spec_tests {
use super::*;
#[test]
fn the_spec_carries_no_identifying_field() {
let spec = host_spec();
let rendered = format!("{spec:?}").to_lowercase();
if let Ok(host) = std::process::Command::new("hostname").output() {
let name = String::from_utf8_lossy(&host.stdout).trim().to_lowercase();
let stem = name.split('.').next().unwrap_or("").to_string();
if stem.len() > 3 {
assert!(
!rendered.contains(&stem),
"the host spec leaked this machine's name"
);
}
}
for (name, value) in [("USER", "user"), ("HOME", "home directory")] {
if let Ok(v) = std::env::var(name) {
let v = v.to_lowercase();
if v.len() > 3 {
assert!(!rendered.contains(&v), "the host spec leaked the {value}");
}
}
}
}
#[test]
fn the_label_describes_the_kind_of_machine_not_the_instance() {
let a = HostSpec {
cpu: Some("Apple M2 Pro".into()),
arch: "aarch64",
cores: Some(10),
perf_cores: Some(6),
ram_gb: Some(32.0),
os: Some("macOS 15.6".into()),
};
assert_eq!(host_label(&a), "Apple M2 Pro (10c/6p) macOS 15.6");
assert_eq!(host_label(&a), host_label(&a.clone()));
let b = HostSpec {
cpu: Some("AMD Ryzen 9 7950X".into()),
arch: "x86_64",
cores: Some(16),
perf_cores: Some(16),
ram_gb: Some(128.0),
os: Some("Linux 6.8.0".into()),
};
assert_eq!(host_label(&b), "AMD Ryzen 9 7950X (16c) Linux 6.8.0");
}
#[test]
fn nothing_is_guessed_when_the_platform_will_not_say() {
let bare = HostSpec {
cpu: None,
arch: "riscv64",
cores: None,
perf_cores: None,
ram_gb: None,
os: None,
};
assert_eq!(host_label(&bare), "riscv64");
}
#[test]
#[cfg(any(target_os = "macos", target_os = "linux"))]
fn a_supported_platform_reports_its_cpu_and_memory() {
let spec = host_spec();
assert!(spec.cpu.is_some(), "no CPU model on a supported platform");
assert!(spec.cores.unwrap_or(0) > 0, "no core count");
assert!(spec.ram_gb.unwrap_or(0.0) > 0.5, "no memory size");
assert!(spec.os.is_some(), "no OS version");
}
}