use std::time::{Duration, Instant};
use serde_json::{json, Map, Value};
use supermachine::Image;
const PLATFORM: &str = if cfg!(all(target_os = "macos", target_arch = "aarch64")) {
"hvf"
} else if cfg!(all(target_os = "linux", target_arch = "x86_64")) {
"kvm"
} else {
"other"
};
fn home() -> String {
std::env::var("HOME").unwrap_or_else(|_| "/root".into())
}
fn snap_path() -> String {
format!("{}/.local/supermachine-snapshots/rust_1_slim", home())
}
fn median(mut v: Vec<f64>) -> f64 {
v.sort_by(|a, b| a.partial_cmp(b).unwrap());
if v.is_empty() {
return 0.0;
}
v[v.len() / 2]
}
struct Metric {
name: &'static str,
value: f64,
unit: &'static str,
lower_is_better: bool,
}
fn measure_restore(snap: &str, n: usize) -> Option<f64> {
let mut times = Vec::with_capacity(n);
for _ in 0..n {
let image = Image::from_snapshot(snap).ok()?;
let t0 = Instant::now();
let pool = image
.pool()
.min(1)
.max(1)
.idle_timeout(Duration::MAX)
.restore_on_release(false)
.build()
.ok()?;
let _vm = pool.acquire().ok()?;
times.push(t0.elapsed().as_secs_f64() * 1000.0);
}
Some(median(times))
}
fn measure_exec_roundtrip(snap: &str, n: usize) -> Option<f64> {
let image = Image::from_snapshot(snap).ok()?;
let pool = image
.pool()
.min(1)
.max(1)
.idle_timeout(Duration::MAX)
.restore_on_release(false)
.build()
.ok()?;
let vm = pool.acquire().ok()?;
let run = || {
vm.exec_builder()
.argv(["true"].iter().copied())
.timeout(Duration::from_secs(10))
.output()
};
for _ in 0..3 {
run().ok()?;
}
let mut times = Vec::with_capacity(n);
for _ in 0..n {
let t0 = Instant::now();
let out = run().ok()?;
if !out.success() {
return None;
}
times.push(t0.elapsed().as_secs_f64() * 1000.0);
}
Some(median(times))
}
fn measure_throughput(snap: &str, mib: usize) -> Option<f64> {
let image = Image::from_snapshot(snap).ok()?;
let pool = image
.pool()
.min(1)
.max(1)
.idle_timeout(Duration::MAX)
.restore_on_release(false)
.build()
.ok()?;
let vm = pool.acquire().ok()?;
let bytes = mib * 1024 * 1024;
let cmd = format!("head -c {bytes} /dev/zero");
let _ = vm
.exec_builder()
.argv(["sh", "-c", &cmd].iter().copied())
.timeout(Duration::from_secs(60))
.output()
.ok()?;
let mut rates = Vec::with_capacity(3);
for _ in 0..3 {
let t0 = Instant::now();
let out = vm
.exec_builder()
.argv(["sh", "-c", &cmd].iter().copied())
.timeout(Duration::from_secs(60))
.output()
.ok()?;
let secs = t0.elapsed().as_secs_f64();
if out.stdout.len() != bytes || secs <= 0.0 {
return None;
}
rates.push((mib as f64) / secs);
}
Some(median(rates))
}
fn main() {
let gate = std::env::var("SM_PERF_GATE").unwrap_or_else(|_| "fail".into());
let update = std::env::var("SM_PERF_UPDATE").ok().as_deref() == Some("1");
let snap = snap_path();
if PLATFORM == "other" {
println!("[perf-gate] unsupported platform — skipping");
return;
}
if !std::path::Path::new(&snap).exists() {
println!("[perf-gate] no rust_1_slim snapshot at {snap} — skipping (exit 0)");
return;
}
println!("[perf-gate] platform={PLATFORM} gate={gate} update={update}");
let mut metrics: Vec<Metric> = Vec::new();
if let Some(v) = measure_restore(&snap, 5) {
metrics.push(Metric {
name: "restore.first_acquire",
value: v,
unit: "ms",
lower_is_better: true,
});
}
if let Some(v) = measure_exec_roundtrip(&snap, 21) {
metrics.push(Metric {
name: "exec.roundtrip",
value: v,
unit: "ms",
lower_is_better: true,
});
}
if let Some(v) = measure_throughput(&snap, 64) {
metrics.push(Metric {
name: "vsock.throughput",
value: v,
unit: "MiB/s",
lower_is_better: false,
});
}
if metrics.is_empty() {
eprintln!("[perf-gate] no metrics measured (workload failed) — FAIL");
std::process::exit(1);
}
let manifest = env!("CARGO_MANIFEST_DIR");
let baseline_path = format!("{manifest}/perf/perf-baseline.json");
let mut baseline: Value = std::fs::read_to_string(&baseline_path)
.ok()
.and_then(|s| serde_json::from_str(&s).ok())
.unwrap_or_else(|| json!({}));
let plat_base = baseline.get(PLATFORM).cloned().unwrap_or_else(|| json!({}));
let mut hard_fail = false;
let mut soft_fail = false;
let mut results = Map::new();
for m in &metrics {
let entry = plat_base.get(m.name);
let reference = entry.and_then(|e| e["reference"].as_f64());
let budget = entry.and_then(|e| e["budget"].as_f64());
let tolerance = entry.and_then(|e| e["tolerance"].as_f64()).unwrap_or(2.0);
let ratio = reference.map(|r| {
if m.lower_is_better {
m.value / r
} else {
r / m.value
}
});
let over_budget = budget.is_some_and(|b| {
if m.lower_is_better {
m.value > b
} else {
m.value < b
}
});
let over_soft = ratio.is_some_and(|r| r > tolerance);
let status = if over_budget {
hard_fail = true;
"hard-regression"
} else if over_soft {
soft_fail = true;
"soft-regression"
} else if reference.is_none() {
"untracked"
} else {
"ok"
};
let ratio_s = ratio.map(|r| format!("{r:.2}x")).unwrap_or("-".into());
println!(
" {:<24} {:>10.2} {:<6} ref={:<8} budget={:<8} ratio={:<6} {status}",
m.name,
m.value,
m.unit,
reference.map(|r| format!("{r:.2}")).unwrap_or("-".into()),
budget.map(|b| format!("{b:.0}")).unwrap_or("-".into()),
ratio_s,
);
results.insert(
m.name.to_string(),
json!({
"value": m.value, "unit": m.unit, "reference": reference,
"budget": budget, "ratio": ratio, "status": status,
}),
);
}
let _ = std::fs::write(
format!("{manifest}/perf/perf-results-{PLATFORM}.json"),
serde_json::to_string_pretty(&json!({
"platform": PLATFORM,
"metrics": Value::Object(results),
}))
.unwrap(),
);
if update {
let mut plat = plat_base.as_object().cloned().unwrap_or_default();
for m in &metrics {
let prev = plat.get(m.name).cloned().unwrap_or_else(|| json!({}));
let budget = prev["budget"].as_f64().unwrap_or_else(|| {
if m.lower_is_better {
m.value * 6.0
} else {
m.value / 4.0
}
});
let tolerance = prev["tolerance"].as_f64().unwrap_or(2.0);
plat.insert(
m.name.to_string(),
json!({
"budget": budget, "reference": m.value,
"tolerance": tolerance, "unit": m.unit,
"lowerIsBetter": m.lower_is_better,
}),
);
}
if !baseline.is_object() {
baseline = json!({});
}
baseline[PLATFORM] = Value::Object(plat);
std::fs::create_dir_all(format!("{manifest}/perf")).ok();
std::fs::write(
&baseline_path,
serde_json::to_string_pretty(&baseline).unwrap(),
)
.unwrap();
println!("[perf-gate] reseeded {PLATFORM} baseline in {baseline_path}");
return;
}
let fail = match gate.as_str() {
"off" | "warn" => false,
"strict" => hard_fail || soft_fail,
_ => hard_fail, };
if soft_fail && !fail {
println!("[perf-gate] soft regression(s) — WARN (set SM_PERF_GATE=strict to fail)");
}
if fail {
eprintln!("[perf-gate] regression gate FAILED (mode={gate})");
std::process::exit(1);
}
println!("[perf-gate] OK");
}