use std::path::PathBuf;
use std::time::{Duration, Instant};
use supermachine::Image;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let cycles: usize = std::env::var("SMPARK_GATE_CYCLES")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(100);
let vcpus: u32 = std::env::var("SMPARK_GATE_VCPUS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(4);
let smpark_ko = PathBuf::from(
std::env::var("SMPARK_GATE_KO")
.unwrap_or_else(|_| "docs/design/extras/smpark/smpark.ko".to_string()),
);
if !smpark_ko.is_file() {
eprintln!(
"[gate] smpark.ko not found at {} — set SMPARK_GATE_KO or run from repo root",
smpark_ko.display()
);
std::process::exit(2);
}
eprintln!(
"[gate] baking rust:1-slim with {} vCPUs + smpark.ko staged (pipelined path)...",
vcpus
);
let bake_t0 = Instant::now();
let image = match Image::builder("rust:1-slim")
.with_name(format!("rust_1_slim_smpark_gate_{}vcpu", vcpus))
.with_memory_mib(512)
.with_vcpus(vcpus)
.with_extra_file(&smpark_ko, "/supermachine-smpark.ko")
.with_warmup_tag("smpark_gate")
.with_warmup(|_vm| Ok(()))
.build()
{
Ok(i) => i,
Err(e) => {
eprintln!("[gate] bake failed: {e}");
std::process::exit(2);
}
};
let bake_ms = bake_t0.elapsed().as_millis();
eprintln!("[gate] bake done in {} ms", bake_ms);
let fresh_restore = std::env::var("SMPARK_GATE_FRESH_RESTORE")
.ok()
.as_deref()
!= Some("0");
let max_workers: usize = std::env::var("SMPARK_GATE_MAX")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(1);
let pool = image
.pool()
.min(0)
.max(max_workers)
.restore_on_release(fresh_restore)
.acquire_timeout(Duration::from_secs(30))
.build()?;
eprintln!("[gate] pool max={max_workers}");
eprintln!(
"[gate] mode: fresh_restore={fresh_restore} (set SMPARK_GATE_FRESH_RESTORE=0 to reuse)"
);
let mut successes = 0usize;
let mut failures = 0usize;
let mut first_fail: Option<(usize, String)> = None;
let cycles_t0 = Instant::now();
for i in 0..cycles {
let t0 = Instant::now();
let outcome: Result<bool, String> = (|| -> Result<bool, String> {
let vm = pool.acquire().map_err(|e| format!("acquire: {e}"))?;
let out = vm
.exec_builder()
.argv(["sh", "-c", "echo ok && cat /proc/modules | grep -q smpark"])
.timeout(Duration::from_secs(10))
.output()
.map_err(|e| format!("exec: {e}"))?;
let stdout = String::from_utf8_lossy(&out.stdout);
let exit_code = out.status.code().unwrap_or(-1);
if exit_code != 0 || !stdout.contains("ok") {
return Err(format!(
"exit={exit_code} stdout={stdout:?} stderr={:?}",
String::from_utf8_lossy(&out.stderr)
));
}
Ok(true)
})();
let cycle_ms = t0.elapsed().as_millis();
match outcome {
Ok(_) => {
successes += 1;
eprintln!(
"[gate] cycle {:3}/{cycles} OK in {} ms (total OK={successes} FAIL={failures})",
i + 1,
cycle_ms
);
}
Err(e) => {
failures += 1;
eprintln!(
"[gate] cycle {:3}/{cycles} FAIL in {} ms: {e}",
i + 1,
cycle_ms
);
if first_fail.is_none() {
first_fail = Some((i + 1, e));
}
}
}
}
let total_ms = cycles_t0.elapsed().as_millis();
eprintln!();
eprintln!("[gate] === SUMMARY ===");
eprintln!("[gate] cycles : {cycles}");
eprintln!("[gate] successes : {successes}");
eprintln!("[gate] failures : {failures}");
eprintln!("[gate] total ms : {total_ms}");
if let Some((n, e)) = &first_fail {
eprintln!("[gate] first fail : cycle {n}: {e}");
}
if failures == 0 {
eprintln!("[gate] PASS — multi-vCPU snapshot reliability gate met");
Ok(())
} else {
eprintln!("[gate] FAIL — {failures} cycle(s) failed");
std::process::exit(1);
}
}