open-standards/wai/wai-rs/src/bench_env.rs
Transaction Science 722e269ea9 wai: bench_env, the machine record a measurement states
The CPU, cores (with performance levels where the OS reports them), OS, arch,
build profile, load averages and power source, as the OS reports them; native
only. wai_decode_bench's machine and load lines now come from it, unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 07:16:48 -04:00

114 lines
5.1 KiB
Rust

//! The machine a measurement ran on (`extensions/staged-measurement.md` §7.4, §11.1):
//! its processor, cores, operating system and architecture, the build's profile, its
//! load, and its power source, as the operating system reports them. Native only: a
//! browser reports none of these.
//!
//! Every reading is what the OS says, unparsed beyond trimming; a reading the OS does
//! not give is `None` (or "unknown"), never estimated.
/// The machine and build a measurement ran on.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MachineRecord {
/// The processor's name as the OS reports it, or `unknown CPU`.
pub cpu: String,
/// `N logical cores`, followed, where the OS reports performance levels, by each
/// level's core count and name in parentheses.
pub cores: String,
pub os: &'static str,
pub arch: &'static str,
/// `release`, or `DEBUG (numbers not meaningful)` for a build with debug assertions.
pub build: &'static str,
}
impl std::fmt::Display for MachineRecord {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}, {}; {}-{}; {} build", self.cpu, self.cores, self.os, self.arch, self.build)
}
}
fn sh(cmd: &str, args: &[&str]) -> Option<String> {
let o = std::process::Command::new(cmd).args(args).output().ok()?;
o.status.success().then(|| String::from_utf8_lossy(&o.stdout).trim().to_string())
}
/// The core types as the OS reports them, e.g. `6 Super + 12 Performance`: each macOS performance
/// level's logical-core count and its own name (`hw.perflevelN.name`), in the OS's
/// order. Names are reported as given, not inferred from the level's index, since what
/// level 0 and 1 are differs between chips. `None` where the OS does not report levels.
fn core_levels() -> Option<String> {
let n: usize = sh("sysctl", &["-n", "hw.nperflevels"])?.parse().ok()?;
let parts: Vec<String> = (0..n)
.map(|i| {
let cpus = sh("sysctl", &["-n", &format!("hw.perflevel{i}.logicalcpu")])?;
let name = sh("sysctl", &["-n", &format!("hw.perflevel{i}.name")]).unwrap_or_else(|| format!("level-{i}"));
Some(format!("{cpus} {name}"))
})
.collect::<Option<_>>()?;
(!parts.is_empty()).then(|| parts.join(" + "))
}
/// This machine and build.
pub fn machine() -> MachineRecord {
let cpu = sh("sysctl", &["-n", "machdep.cpu.brand_string"])
.or_else(|| {
std::fs::read_to_string("/proc/cpuinfo").ok()?.lines().find(|l| l.starts_with("model name")).map(|l| l.split(':').nth(1).unwrap_or("").trim().to_string())
})
.unwrap_or_else(|| "unknown CPU".into());
let levels = core_levels().map(|l| format!(" ({l})")).unwrap_or_default();
MachineRecord {
cpu,
cores: format!("{} logical cores{levels}", std::thread::available_parallelism().map(|n| n.get()).unwrap_or(0)),
os: std::env::consts::OS,
arch: std::env::consts::ARCH,
build: if cfg!(debug_assertions) { "DEBUG (numbers not meaningful)" } else { "release" },
}
}
/// The 1-, 5- and 15-minute load averages, as the OS writes them.
pub fn loadavg() -> Option<(String, String, String)> {
let line = sh("sysctl", &["-n", "vm.loadavg"])
.map(|s| s.trim_matches(|c| c == '{' || c == '}' || c == ' ').to_string())
.or_else(|| std::fs::read_to_string("/proc/loadavg").ok())?;
let mut it = line.split_whitespace().map(str::to_string);
Some((it.next()?, it.next()?, it.next()?))
}
/// Where the machine draws power from, as the OS names it: macOS's `pmset` line
/// (`AC Power`, `Battery Power`), or on Linux `AC Power` when a mains supply is
/// online and `Battery Power` when only a battery reports. `None` when the OS says
/// nothing.
pub fn power_source() -> Option<String> {
if let Some(out) = sh("pmset", &["-g", "batt"]) {
let first = out.lines().next()?;
let (_, rest) = first.split_once('\'')?;
let (name, _) = rest.split_once('\'')?;
return Some(name.to_string());
}
let dir = std::fs::read_dir("/sys/class/power_supply").ok()?;
let mut battery = false;
for e in dir.flatten() {
let p = e.path();
let kind = std::fs::read_to_string(p.join("type")).unwrap_or_default();
match kind.trim() {
"Mains" if std::fs::read_to_string(p.join("online")).is_ok_and(|s| s.trim() == "1") => return Some("AC Power".into()),
"Battery" => battery = true,
_ => {}
}
}
battery.then(|| "Battery Power".into())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_record_prints_as_the_decode_bench_printed_it() {
let m = MachineRecord { cpu: "X".into(), cores: "8 logical cores (4 A + 4 B)".into(), os: "macos", arch: "aarch64", build: "release" };
assert_eq!(m.to_string(), "X, 8 logical cores (4 A + 4 B); macos-aarch64; release build");
// On this machine: the record reads, and it names this OS and architecture.
let here = machine();
assert_eq!((here.os, here.arch), (std::env::consts::OS, std::env::consts::ARCH));
assert!(here.cores.ends_with(')') || here.cores.ends_with("cores"), "{}", here.cores);
}
}