use crate::bench_guard;
use crate::host_state::{self, ThermalReading};
use std::path::Path;
#[derive(Clone, Copy)]
pub struct HostState {
pub load_start: Option<f64>,
pub load_end: Option<f64>,
pub thermal_start: ThermalReading,
pub thermal_end: ThermalReading,
}
pub fn preflight_host(max_load: f64) -> anyhow::Result<(Option<f64>, ThermalReading)> {
let load_start = host_state::ensure_quiet_enough(max_load)?;
let thermal_start = host_state::thermal_reading();
host_state::ensure_cool_enough(&thermal_start, max_load > 0.0)?;
Ok((load_start, thermal_start))
}
pub fn ensure_weights_fit(max_load: f64, weights: &Path, extra_gb: f64) -> anyhow::Result<()> {
if max_load <= 0.0 {
return Ok(());
}
if let Ok(meta) = std::fs::metadata(weights) {
let weights_gb = meta.len() as f64 / 1024.0 / 1024.0 / 1024.0;
host_state::ensure_fits_in_ram(weights_gb + extra_gb, 2.0)?;
}
Ok(())
}
pub struct HostAfter {
pub load_end: Option<f64>,
pub thermal_end: ThermalReading,
pub engine_env: Vec<(String, String)>,
}
pub fn report_host_after(
tool: &str,
load_start: Option<f64>,
thermal_start: &ThermalReading,
) -> HostAfter {
let load_end = host_state::load_average_1min();
let thermal_end = host_state::thermal_reading();
eprintln!(
"{tool}: host 1-min load {} -> {}, {} -> {}",
fmt_load(load_start),
fmt_load(load_end),
thermal_start.describe(),
thermal_end.describe(),
);
if !thermal_start.is_degraded() && thermal_end.is_degraded() {
eprintln!(
"{tool}: WARNING -- the host became thermally limited during this \
run ({}); the later repetitions did not run under the same conditions \
as the first",
thermal_end.describe()
);
}
let engine_env = bench_guard::nondefault_engine_env(std::env::vars());
if !engine_env.is_empty() {
eprintln!(
"{tool}: non-default engine env in effect: {}",
engine_env
.iter()
.map(|(k, v)| format!("{k}={v}"))
.collect::<Vec<_>>()
.join(" ")
);
}
HostAfter {
load_end,
thermal_end,
engine_env,
}
}
fn thermal_json(r: &ThermalReading) -> serde_json::Value {
serde_json::json!({
"measured": r.measured(),
"pressure": r.pressure.map(|p| p.as_str()),
"source": r.source,
"cpu_speed_limit_percent": r.cpu_speed_limit_percent,
"degraded": r.measured().then(|| r.is_degraded()),
})
}
pub fn fmt_load(l: Option<f64>) -> String {
l.map(|l| format!("{l:.2}"))
.unwrap_or_else(|| "?".to_string())
}
pub fn backend_label_agrees(label: &str, active: &str) -> bool {
label.eq_ignore_ascii_case(active)
}
fn accelerator_name(backend: &str) -> Option<String> {
match backend {
#[cfg(feature = "cuda")]
"CUDA" => frink_cuda::gpu::probe().and_then(|i| i.first_device_name),
#[cfg(feature = "metal")]
"Metal" => frink_metal::gpu::probe(),
_ => None,
}
}
pub fn ensure_label_matches_backend(label: &str, active: &str) -> anyhow::Result<()> {
anyhow::ensure!(
backend_label_agrees(label, active),
"refusing to write a receipt labelled `{label}` for a run that executed on \
{active}. The label is what the ledger publishes; the active backend is what \
ran (#126)."
);
Ok(())
}
pub fn receipt_common(
label: &str,
backend_active: &str,
threads: usize,
load_s: f64,
host: HostState,
engine_env: &[(String, String)],
) -> anyhow::Result<serde_json::Map<String, serde_json::Value>> {
let host_spec_json = {
let spec = host_state::host_spec();
serde_json::json!({
"label": host_state::host_label(&spec),
"cpu": spec.cpu,
"arch": spec.arch,
"cores": spec.cores,
"perf_cores": spec.perf_cores,
"ram_gb": spec.ram_gb.map(|g| (g * 10.0).round() / 10.0),
"os": spec.os,
})
};
ensure_label_matches_backend(label, backend_active)?;
let accelerator = accelerator_name(backend_active);
if backend_active != "CPU" && accelerator.is_none() {
anyhow::bail!(
"refusing to write a `{label}` receipt that does not name the accelerator it \
ran on. A GPU gap is meaningless without the card: the ledger groups rows by \
host, and two different GPUs in one host section are two different machines."
);
}
let serde_json::Value::Object(map) = serde_json::json!({
"backend": label,
"backend_active": backend_active,
"threads": threads,
"warmup_reps": bench_guard::WARMUP_REPS,
"load_s": load_s,
"host_load_1min_start": host.load_start,
"host_load_1min_end": host.load_end,
"host_thermal_start": thermal_json(&host.thermal_start),
"host_thermal_end": thermal_json(&host.thermal_end),
"quiet_host": host.load_start.map(|l| l < host_state::DEFAULT_MAX_LOAD),
"host_spec": host_spec_json,
"accelerator": accelerator,
"engine_env": engine_env
.iter()
.map(|(k, v)| (k.clone(), serde_json::Value::String(v.clone())))
.collect::<serde_json::Map<_, _>>(),
"frink_version": env!("CARGO_PKG_VERSION"),
}) else {
unreachable!("json! with braces is an object");
};
Ok(map)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_cpu_label_does_not_describe_a_metal_run() {
assert!(backend_label_agrees("cpu", "CPU"));
assert!(backend_label_agrees("metal", "Metal"));
assert!(backend_label_agrees("cuda", "CUDA"));
assert!(
!backend_label_agrees("cpu", "Metal"),
"this exact pair is what all 13 published cpu receipts recorded"
);
assert!(!backend_label_agrees("metal", "CPU"));
assert!(!backend_label_agrees("cuda", "Metal"));
}
#[test]
fn the_early_label_check_refuses_the_published_mismatch() {
let err = ensure_label_matches_backend("cpu", "Metal")
.unwrap_err()
.to_string();
assert!(err.contains("labelled `cpu`"), "{err}");
assert!(err.contains("executed on Metal"), "{err}");
assert!(ensure_label_matches_backend("cpu", "CPU").is_ok());
}
#[test]
fn the_cpu_backend_names_no_accelerator() {
assert_eq!(accelerator_name("CPU"), None);
}
fn unmeasured_host() -> HostState {
HostState {
load_start: None,
load_end: None,
thermal_start: host_state::ThermalReading::default(),
thermal_end: host_state::ThermalReading::default(),
}
}
#[test]
fn a_receipt_labelled_for_another_backend_is_not_written() {
let err = receipt_common("cpu", "Metal", 4, 1.0, unmeasured_host(), &[])
.map(|_| ())
.unwrap_err()
.to_string();
assert!(err.contains("#126"), "{err}");
}
#[test]
fn a_gpu_receipt_without_an_accelerator_name_is_not_written() {
let err = receipt_common("metal", "Metal", 4, 1.0, unmeasured_host(), &[])
.map(|_| ())
.unwrap_err()
.to_string();
assert!(err.contains("does not name the accelerator"), "{err}");
}
#[test]
fn a_cpu_receipt_carries_the_shared_fields_and_no_accelerator() {
let map = receipt_common("cpu", "CPU", 4, 1.5, unmeasured_host(), &[]).unwrap();
assert_eq!(map["backend"], "cpu");
assert_eq!(map["backend_active"], "CPU");
assert_eq!(map["threads"], 4);
assert_eq!(map["accelerator"], serde_json::Value::Null);
assert_eq!(map["quiet_host"], serde_json::Value::Null);
assert_eq!(map["host_thermal_start"]["measured"], false);
assert_eq!(map["warmup_reps"], bench_guard::WARMUP_REPS);
}
#[test]
fn the_ram_check_is_waived_with_the_load_check() {
let me = std::env::current_exe().expect("the test binary exists");
assert!(ensure_weights_fit(0.0, &me, 1e9).is_ok());
if host_state::free_ram_gb().is_some() {
let err = ensure_weights_fit(2.0, &me, 1e9).unwrap_err().to_string();
assert!(err.contains("would run from swap"), "{err}");
}
}
}