supermachine 0.6.1

Run any OCI/Docker image as a hardware-isolated microVM on macOS HVF (Linux KVM and Windows WHP in progress). Single library API, zero flags for the common case, sub-100 ms cold-restore from snapshot.
//! Generate a flame graph from supermachine's tracing spans.
//!
//! Wires the `tracing-flame` subscriber to write a folded-stack
//! file. After the run, convert it to SVG with `inferno-flamegraph`:
//!
//! ```sh
//! cargo install inferno
//! cargo run --release --example _flame_graph
//! inferno-flamegraph < /tmp/supermachine-flame.folded > /tmp/flame.svg
//! open /tmp/flame.svg
//! ```
//!
//! The flame graph shows where wall-clock time is spent across
//! the bake / acquire / snapshot / exec phases. Useful for
//! integrators wanting to see where their workload's
//! supermachine-side time goes (vs the rustc-side or Anthropic-API-
//! side time, which is in their own code).
//!
//! What the spans look like:
//!
//!   supermachine.bake_pipelined (image=rust:1-slim, …)
//!     ├─ supermachine.acquire (during warmup callback)
//!     │   ├─ supermachine.exec (argv0=rustc, argc=4)
//!     │   └─ supermachine.exec (argv0=/tmp/seed, argc=1)
//!     └─ … snapshot capture / save phases ...
//!
//!   supermachine.pool.acquire (memory_mib=2048, vcpus=1)
//!   supermachine.exec (argv0=sh, argc=3)
//!   supermachine.snapshot (dest_dir=/tmp/...)

use std::io::Read;
use std::time::Duration;
use supermachine::Image;
use tracing_flame::FlameLayer;
use tracing_subscriber::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let flame_path = std::env::var("SUPERMACHINE_FLAME_OUT")
        .unwrap_or_else(|_| "/tmp/supermachine-flame.folded".to_owned());
    eprintln!("[flame] writing folded stacks → {flame_path}");

    let (flame_layer, _guard) = FlameLayer::with_file(&flame_path)?;
    tracing_subscriber::registry()
        .with(flame_layer)
        .init();

    // Bake — emits `supermachine.bake_pipelined` span and nested
    // acquire/exec spans from the warmup callback.
    let image = Image::builder("rust:1-slim")
        .with_name("rust_1_slim_flame")
        .with_memory_mib(2048)
        .with_warmup(|vm| {
            vm.exec_builder()
                .stage_file(
                    "/tmp/seed.rs",
                    b"fn main() { println!(\"flame\"); }".to_vec(),
                )
                .argv(["rustc", "-O", "/tmp/seed.rs", "-o", "/tmp/seed"])
                .chain(["/tmp/seed"])
                .timeout(Duration::from_secs(60))
                .output()?;
            Ok(())
        })
        .with_warmup_tag("flame_v1")
        .build()?;

    // Pool build + a few acquire/exec cycles — emits
    // `supermachine.pool.acquire` and `supermachine.exec` spans
    // per cycle.
    let pool = image
        .pool()
        .min(2)
        .max(2)
        .restore_on_release(false)
        .build()?;

    for i in 0..5 {
        let vm = pool.acquire()?;
        let mut child = vm.exec(["sh", "-c", &format!("echo cycle-{i}")])?;
        let mut buf = String::new();
        if let Some(mut stdout) = child.stdout() {
            let _ = stdout.read_to_string(&mut buf);
        }
        let _ = buf; // discard; we just want the spans
        let _ = child.wait();
    }

    // Drop the _guard explicitly so the folded file is flushed
    // before main exits. The subscriber writes incrementally;
    // missing flush leaves a truncated file.
    drop(_guard);

    eprintln!(
        "[flame] done. Convert with:\n  inferno-flamegraph < {flame_path} > /tmp/flame.svg"
    );
    Ok(())
}