use metrale_bench::hardware::equivalence::{HardwareFingerprint, driver_major};
use metrale_bench::hardware::{Hardware, HardwareState};
use super::metralectl::{NodeInfo, NodeRow};
#[derive(Clone, Debug, PartialEq)]
pub struct Node {
pub addr: String,
pub name: String,
pub node_id: String,
pub signer: String,
pub hardware: HardwareFingerprint,
pub free_fraction: Option<f64>,
pub built: bool,
pub local: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Rejection {
pub addr: String,
pub why: String,
}
impl std::fmt::Display for Rejection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: {}", self.addr, self.why)
}
}
pub fn fingerprint_of(info: &NodeInfo) -> HardwareFingerprint {
let t = info.thermal.as_ref();
HardwareFingerprint {
gpu: info
.gpu
.as_ref()
.map(|g| g.name.clone())
.unwrap_or_default(),
driver_major: info
.gpu
.as_ref()
.and_then(|g| driver_major(&g.driver_version)),
sm_clock_max_mhz: t.and_then(|t| t.sm_clock_max_mhz),
mem_total_kb: t.and_then(|t| t.mem_total_kb),
thermal_alert: t.and_then(|t| t.throttle_thermal),
hottest_chassis_c: t.and_then(|t| {
t.chassis_temps_c
.iter()
.copied()
.fold(None, |m: Option<f64>, x| Some(m.map_or(x, |m| m.max(x))))
}),
postcheck_valid: None,
}
}
pub struct Wanted<'a> {
pub hardware: &'a str,
pub committed_signers: &'a [String],
pub anchor: &'a str,
pub min_free_fraction: f64,
}
pub fn admit(row: &NodeRow, wanted: &Wanted) -> Result<Node, Rejection> {
let reject = |why: String| Rejection {
addr: row.node.clone(),
why,
};
let Some(info) = &row.info else {
return Err(reject(match &row.error {
Some(e) => e.to_string(),
None => "no report and no error — metralectl said nothing about it".into(),
}));
};
let mut why = Vec::new();
if !info.bench_enabled {
why.push(format!(
"bench is off there ({})",
info.disabled_reason.as_deref().unwrap_or("no reason given")
));
}
match info.hardware_class.as_deref() {
Some(c) if c == wanted.hardware => {}
Some(c) => why.push(format!(
"its box class is {c}, this campaign certifies {}",
wanted.hardware
)),
None => why.push("it reports no box class".into()),
}
match &info.signer_fp {
Some(fp) if wanted.committed_signers.iter().any(|s| s == fp) => {}
Some(fp) => why.push(format!(
"its signer {fp} is not committed in .github/record-signers/ (commit {fp}.pub first)"
)),
None => why.push("it reports no signing identity (no METRALE_HOME identity there)".into()),
}
if info.busy {
why.push(format!(
"it is busy ({})",
info.busy_reason.as_deref().unwrap_or("no reason given")
));
}
if info.queued > 0 {
why.push(format!("it already has {} job(s) queued", info.queued));
}
let free = info.host_free_fraction.value();
match free {
Some(f) if f < wanted.min_free_fraction => why.push(format!(
"only {:.0} % of host memory is free; a gate needs {:.0} %",
f * 100.0,
wanted.min_free_fraction * 100.0
)),
Some(_) => {}
None => why.push("it cannot report free memory".into()),
}
if let Some(d) = info.disk_free_bytes.value()
&& (d as u64) < info.min_free_disk_bytes
{
why.push(format!(
"only {} MiB free on its bench cache; it requires {} MiB",
(d as u64) >> 20,
info.min_free_disk_bytes >> 20
));
}
let hw = fingerprint_of(info);
if hw.gpu.is_empty() {
why.push("it reports no GPU".into());
}
if !why.is_empty() {
return Err(reject(why.join("; ")));
}
Ok(Node {
addr: row.node.clone(),
name: info.name.clone(),
node_id: info.node.clone(),
signer: info.signer_fp.clone().unwrap_or_default(),
hardware: hw,
free_fraction: free,
built: info.built_shas.iter().any(|b| b.sha == wanted.anchor),
local: false,
})
}
pub fn local(signer: &str, hardware: &Hardware, state: &HardwareState) -> Node {
Node {
addr: "local".into(),
name: state
.machine
.hostname
.clone()
.unwrap_or_else(|| "local".into()),
node_id: String::new(),
signer: signer.to_owned(),
hardware: HardwareFingerprint::from_live(hardware, state),
free_fraction: match (state.mem_available_kb, state.mem_total_kb) {
(Some(a), Some(t)) if t > 0 => Some(a as f64 / t as f64),
_ => None,
},
built: true,
local: true,
}
}
#[cfg(test)]
#[path = "node_tests.rs"]
mod node_tests;