//! Measure multi-VM RAM density honestly: build a Dockerfile whose RUN creates a
//! big blob, then boot N concurrent VMs from each of two artifacts and watch the
//! host RSS SLOPE (RAM added per extra VM) while every VM READS the blob:
//!
//!   A) the snapshot-layer image  — each VM restores by CoW-mmapping the SAME
//!      restore.snap (full guest-RAM dump), so read-only pages (incl. the blob in
//!      the guest tmpfs) are shared across VMs via the host page cache.
//!   B) the committed read-only squashfs image — each VM cold-boots with private
//!      anon RAM and reads the blob through virtio-blk (compressed squashfs blocks
//!      shared on the host; DECOMPRESSED pages land in private guest RAM).
//!
//! A small slope = pages shared across VMs (dense); a slope ≈ blob size = private
//! per VM. This tells us where the density actually is (and whether the squashfs
//! RAM win needs virtio-fs + DAX rather than virtio-blk).

#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn rss_mib() -> u64 {
    // VmRSS from /proc/self/status, in MiB.
    let s = std::fs::read_to_string("/proc/self/status").unwrap_or_default();
    for line in s.lines() {
        if let Some(rest) = line.strip_prefix("VmRSS:") {
            let kb: u64 = rest
                .split_whitespace()
                .next()
                .and_then(|v| v.parse().ok())
                .unwrap_or(0);
            return kb / 1024;
        }
    }
    0
}

#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn boot_n_reading_blob(image: &supermachine::Image, n: usize, label: &str) {
    use std::time::Duration;
    use supermachine::VmConfig;

    let base = rss_mib();
    eprintln!("[{label}] baseline RSS = {base} MiB");
    let mut held = Vec::new();
    for i in 0..n {
        let vm = image.start(&VmConfig::new()).expect("start");
        std::thread::sleep(Duration::from_millis(3500));
        // Fault the whole blob into the guest (read it), then keep the VM alive.
        let o = vm
            .exec_builder()
            .argv([
                "/bin/sh",
                "-c",
                "dd if=/data/blob of=/dev/null bs=1M 2>/dev/null; echo read-ok",
            ])
            .output()
            .expect("exec read");
        let ok = String::from_utf8_lossy(&o.stdout).contains("read-ok");
        held.push(vm);
        let now = rss_mib();
        eprintln!(
            "[{label}] after VM #{i} (read-ok={ok}): RSS = {now} MiB  (+{} since baseline, {} MiB/VM avg)",
            now - base,
            (now - base) / (i as u64 + 1)
        );
    }
    let total = rss_mib() - base;
    eprintln!(
        "[{label}] === {n} VMs: +{total} MiB total, ~{} MiB/VM ===",
        total / n as u64
    );
    // Drop all VMs (munmap returns the RAM) before the next scenario.
    drop(held);
    std::thread::sleep(Duration::from_millis(500));
}

#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn main() {
    use supermachine::{builder, Image};

    let n: usize = std::env::args()
        .nth(1)
        .and_then(|s| s.parse().ok())
        .unwrap_or(4);
    let mib = 96u64;

    let dockerfile = format!(
        "FROM alpine\n\
         RUN mkdir -p /data && dd if=/dev/urandom of=/data/blob bs=1M count={mib} 2>/dev/null\n"
    );
    let df = builder::parse(&dockerfile).expect("parse");
    let ctx = std::env::temp_dir().join("sm-dens-ctx");
    let _ = std::fs::create_dir_all(&ctx);
    let build_dest = std::env::temp_dir().join("sm-dens-build");
    let _ = std::fs::remove_dir_all(&build_dest);
    let commit_dir = std::env::temp_dir().join("sm-dens-commit");
    let _ = std::fs::remove_dir_all(&commit_dir);

    let base = Image::from_oci("alpine").expect("from_oci");
    eprintln!("=== build ({mib} MiB blob) ===");
    let out = builder::build_linear(&df, &base, &ctx, &build_dest).expect("build_linear");

    eprintln!("=== commit -> read-only squashfs bootable image ===");
    let (commit, bootable) =
        builder::commit_kvm_bootable(&out.image, &commit_dir).expect("commit_kvm_bootable");
    eprintln!("squashfs = {} MiB", commit.bytes / (1024 * 1024));

    eprintln!("\n##### Scenario A: snapshot-layer image (restore.snap CoW) #####");
    boot_n_reading_blob(&out.image, n, "snap-layer");

    eprintln!("\n##### Scenario B: committed read-only squashfs image #####");
    boot_n_reading_blob(&bootable, n, "committed");

    eprintln!("\n(Interpretation: lower MiB/VM = better density. A small slope means");
    eprintln!(" the blob pages are SHARED across VMs; ~{mib} MiB/VM means PRIVATE.)");
}

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