//! KVM warm-restore latency: cold-boot nginx, capture a MEMORY snapshot, then
//! time N restores (CoW-mmap of the snapshot RAM → resume). This is the KVM
//! analog of the HVF warm-restore number — distinct from a cold boot (which
//! re-runs the kernel from squashfs).

#[cfg(not(all(target_os = "linux", target_arch = "x86_64")))]
fn main() {
    eprintln!("_kvm_warm_bench: Linux/x86_64 (KVM) only");
}

#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
use std::time::{Duration, Instant};
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
use supermachine::{Image, VmConfig};

#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let home = std::env::var("HOME")?;
    let base = format!("{home}/.local/supermachine-snapshots/nginx_1v");
    eprintln!("=== KVM warm-restore bench (base cold image: {base}) ===");

    // Cold-boot the base image once, then capture a memory snapshot of the
    // already-running (nginx-serving) guest.
    let cold = Image::from_snapshot(&base)?;
    let vm = cold.start(&VmConfig::new())?;
    std::thread::sleep(Duration::from_secs(6)); // let nginx come up
    let snap_dir = std::env::temp_dir().join("sm-warm-bench-snap");
    let _ = std::fs::remove_dir_all(&snap_dir);
    let t_snap = Instant::now();
    let warm = vm.snapshot(&snap_dir)?; // consumes the VM, writes a kvm_snapshot
    eprintln!("snapshot capture+save: {} ms", t_snap.elapsed().as_millis());
    drop(warm);

    // Time N warm restores: load the snapshot image + start (CoW-mmap RAM, resume
    // vCPUs — no kernel boot). Measure to a live, exec-able VM.
    let n = 10;
    let mut times = Vec::with_capacity(n);
    let mut teardowns = Vec::with_capacity(n);
    for i in 0..n {
        let img = Image::from_snapshot(&snap_dir)?;
        let t0 = Instant::now();
        let vm = img.start(&VmConfig::new())?;
        let us = t0.elapsed().as_micros() as u64;
        times.push(us);
        // Real teardown each iteration — exercises the race-free single-kick
        // force-exit join (immediate_exit + SIGUSR1). A regressed teardown would
        // either stall here or take tens of ms (the old re-kick poll latency).
        let t_drop = Instant::now();
        drop(vm);
        let drop_us = t_drop.elapsed().as_micros() as u64;
        teardowns.push(drop_us);
        eprintln!(
            "  restore {i}: {:.2} ms   teardown: {:.3} ms",
            us as f64 / 1000.0,
            drop_us as f64 / 1000.0
        );
        std::thread::sleep(Duration::from_millis(150));
    }
    times.sort();
    teardowns.sort();
    let median = times[times.len() / 2];
    let p95 = times[(times.len() as f64 * 0.95) as usize % times.len()];
    let td_median = teardowns[teardowns.len() / 2];
    let td_max = *teardowns.last().unwrap();
    eprintln!(
        "\nKVM warm-restore: median {:.2} ms  p95 {:.2} ms  (n={n})",
        median as f64 / 1000.0,
        p95 as f64 / 1000.0
    );
    eprintln!(
        "KVM teardown:     median {:.3} ms  max {:.3} ms  (n={n})",
        td_median as f64 / 1000.0,
        td_max as f64 / 1000.0
    );
    Ok(())
}
