ktstr/vmm/
wprof.rs

1//! wprof tracer configuration shipped into auto-repro VMs.
2//!
3//! [wprof](https://github.com/anakryiko/wprof) is a BSD-3-Clause
4//! BPF-based system-wide tracer/profiler that emits Perfetto-format
5//! `.pb` traces. ktstr ships the binary at `/bin/wprof` in the guest
6//! initramfs when a test enables wprof capture, and the guest init
7//! invokes it during auto-repro to produce a trace alongside the
8//! standard failure-dump output.
9//!
10//! The wprof bytes live in the `cargo-ktstr` binary (built by
11//! `build.rs`, embedded via `include_bytes!` in
12//! `src/bin/cargo_ktstr/blobs.rs`). At cargo-ktstr startup the
13//! `install_env` hook extracts the bytes to a tempfile and exports
14//! `KTSTR_WPROF_PATH=<path>`; [`WprofConfig::from_env`] reads that
15//! env var and loads the file for the builder.
16
17use anyhow::Result;
18
19/// Minimum guest memory (MiB) for test entries that enable wprof.
20///
21/// Derivation: `WprofConfig::default_args` requests
22/// `--ringbuf-size=256000 --ringbuf-cnt=8`. wprof rounds the
23/// 256000 KB request up to the next power of two (262144 KB =
24/// 256 MiB) per ringbuf, times 8 ringbufs = 2048 MiB BPF arena.
25/// On guests below this, wprof OOM-kills mid-run before it can
26/// emit the Perfetto `.pb` trace, producing a truncated artifact
27/// and a confused test author.
28///
29/// Applied as a floor by
30/// `crate::test_support::runtime::derive_test_memory_mib` —
31/// the single derivation site shared by the entry-derived
32/// topology path AND every dispatch site that constructs a
33/// `crate::test_support::topo::TopoOverride` from CLI / preset
34/// topology (so e.g. `cargo ktstr test --ktstr-topo NnNlNcNt` and
35/// gauntlet preset runs honor the floor identically). Both
36/// primary and auto-repro VMs route through the same derivation
37/// so the floor propagates to both; identical topology between
38/// the two VMs is also required for stall reproducibility.
39///
40/// The floor applies when the derived memory
41/// `max(cpus*64, 256, entry.memory_mib)` falls below 2048 MiB
42/// AND `entry.wprof` is true. An operator-supplied
43/// `crate::test_support::topo::TopoOverride` with explicit
44/// `memory_mib` is honored verbatim per the override-is-verbatim
45/// contract — a warn-level log fires when the override conflicts
46/// with the floor, but the operator's choice wins. Shell-mode VMs
47/// (`cargo ktstr shell --kernel ...`) bypass this floor entirely;
48/// the operator sets memory size via shell-mode CLI args.
49///
50/// Tracks `WprofConfig::default_args`: a future change to
51/// `--ringbuf-size` or `--ringbuf-cnt` invalidates the 2048
52/// derivation and this const should move with the args.
53pub const WPROF_MIN_MEMORY_MIB: u32 = 2048;
54
55/// Apply the wprof memory floor to a raw memory size.
56///
57/// Returns `WPROF_MIN_MEMORY_MIB` when `wprof` is true and `raw_mib`
58/// falls below the floor; otherwise returns `raw_mib` unchanged.
59/// Pure function — no logging, no side effects — so it can be
60/// called from multiple resolution paths without duplicating the
61/// floor formula.
62///
63/// Single source of truth shared by
64/// `crate::test_support::runtime::derive_test_memory_mib` (the
65/// test-launch path used by `cargo ktstr test`) AND by
66/// `cargo ktstr shell --test <NAME>`'s router (which constructs a
67/// shell VM matching the named test's topology). A future change
68/// to the floor formula updates here once; both call sites pick
69/// it up. Inline copies of the `raw < WPROF_MIN_MEMORY_MIB`
70/// conditional are a regression per the
71/// derive_test_memory_mib/attach_wprof_if_requested precedent.
72pub fn apply_wprof_memory_floor(raw_mib: u32, wprof: bool) -> u32 {
73    if wprof && raw_mib < WPROF_MIN_MEMORY_MIB {
74        WPROF_MIN_MEMORY_MIB
75    } else {
76        raw_mib
77    }
78}
79
80/// wprof invocation args + binary path. Passed to
81/// [`crate::vmm::KtstrVmBuilder::wprof`] when a test (or
82/// `cargo ktstr shell`) wants the wprof tracer available inside
83/// the guest VM.
84///
85/// The binary is sourced from a host path (typically the tempfile
86/// path that `cargo-ktstr` exports via `KTSTR_WPROF_PATH`). The
87/// library packs it at `bin/wprof` in the initramfs using the
88/// existing include_files mechanism, which performs `DT_NEEDED`
89/// resolution and pulls every shared library wprof links against
90/// (libelf, libz, etc.) so the binary actually runs inside the
91/// guest.
92///
93/// The args render to the kernel cmdline as
94/// `KTSTR_WPROF_ARGS=<args joined with ASCII Unit Separator `\x1F`>`
95/// (see [`WprofConfig::args_cmdline`]); guest init parses them and
96/// invokes `/bin/wprof` with those args during auto-repro.
97#[derive(Debug, Clone)]
98pub struct WprofConfig {
99    /// Host filesystem path to the wprof binary. The library opens
100    /// this path to read the ELF (for `DT_NEEDED` resolution) and
101    /// to copy the bytes into the initramfs. Usually populated from
102    /// [`WprofConfig::from_env`] which reads `KTSTR_WPROF_PATH`.
103    pub host_path: std::path::PathBuf,
104    /// CLI args appended after `/bin/wprof` when guest init spawns
105    /// the tracer. Defaults to [`WprofConfig::default_args`].
106    pub args: Vec<String>,
107}
108
109impl WprofConfig {
110    /// Construct a [`WprofConfig`] using the env-var-published
111    /// wprof binary (set by `cargo-ktstr` at startup) and the
112    /// project default args. Returns an error if `KTSTR_WPROF_PATH`
113    /// is unset.
114    pub fn from_env() -> Result<Self> {
115        Ok(Self {
116            host_path: crate::vmm::blobs::load_wprof_path()?,
117            args: Self::default_args(),
118        })
119    }
120
121    /// Default wprof args used by the auto-repro pipeline:
122    /// `-d 500 -e sched --ringbuf-size=256000 --ringbuf-cnt=8`.
123    ///
124    /// Capture window: 500 ms of sched-event tracing. Ringbuf
125    /// sizing tuned for the scheduler-event volume expected from
126    /// ktstr's typical workload shapes. Override per-test by
127    /// constructing a [`WprofConfig`] with a custom `args` vec.
128    pub fn default_args() -> Vec<String> {
129        [
130            "-d",
131            "500",
132            "-e",
133            "sched",
134            "--ringbuf-size=256000",
135            "--ringbuf-cnt=8",
136        ]
137        .into_iter()
138        .map(String::from)
139        .collect()
140    }
141
142    /// Render the args as a delimited string suitable for passing on
143    /// the kernel cmdline (`KTSTR_WPROF_ARGS=...`). The kernel
144    /// cmdline tokenizer splits on whitespace and `/proc/cmdline`
145    /// parsers (including the guest's `cmdline_val` via
146    /// `split_whitespace`) cannot recover spaces inside a value, so
147    /// joining args with a literal space would truncate everything
148    /// after the first arg. Use ASCII Unit Separator (`\x1F`, the
149    /// delimiter character codepoint reserved by ANSI X3.4 for this
150    /// exact purpose) — non-whitespace, won't appear in real wprof
151    /// args (which are flags, paths, integers, comma-separated
152    /// feature lists), and the kernel passes it through to
153    /// `/proc/cmdline` byte-for-byte.
154    pub fn args_cmdline(&self) -> String {
155        self.args.join("\x1f")
156    }
157}