1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
//! 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(())
}