Skip to content

feat(exec-harness): declare the benchmarked command's pid - #559

Open
lvaroqui wants to merge 9 commits into
spike/cod-3440-memtrack-muslfrom
cod-3722-ignore-process-spawning-overhead-in-exec-harness-simulation
Open

lvaroqui wants to merge 9 commits into
spike/cod-3440-memtrack-muslfrom
cod-3722-ignore-process-spawning-overhead-in-exec-harness-simulation

Conversation

@lvaroqui

@lvaroqui lvaroqui commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Make exec-harness declare the pid of the command it benchmarks, so its own spawning cost can be left out of simulation results.

Since #531, exec-harness turns instrumentation on in its own process and spawns the command, which inherits that state across fork and exec. The dumped part therefore also holds the harness's cost of spawning the command and waiting for it. That is a fixed overhead on every benchmark, about 207k instructions per command locally.

Changes:

  • instrument-hooks binding: new set_executed_benchmark_for_pid(pid, uri). set_executed_benchmark keeps its behavior and delegates to it.
  • exec-harness: spawns the command and passes its pid, instead of calling Command::status(). In memory mode the pid only travels to the runner's FIFO, where nothing reads it for exec-harness.
  • Submodule bump: feat(valgrind): declare the pid a benchmark ran in instrument-hooks#32 writes desc: Benchmark pid: <pid> in the part when the pid is not the caller's. This bump also brings the thread-safe C API exports and the callgrind_toggle_collect helper.

Checked locally under the patched valgrind, with the runner's simulation flags on sh -c '/bin/true; /bin/true; :':

part: 2
desc: Spawned pid: 289126
desc: Benchmark pid: 289126
desc: Trigger: Client Request: exec_harness::true_twice

Still to do before this is ready:

Closes COD-3722

@codspeed

codspeed Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Merging this PR will degrade performance by 58.02%

⚠️ Unknown Walltime execution environment detected

Using the Walltime instrument on standard Hosted Runners will lead to inconsistent data.

For the most accurate results, we recommend using CodSpeed Macro Runners: bare-metal machines fine-tuned for performance measurement consistency.

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

#### 🎉 Hooray! `exec-harness` just leveled up to 5.4.0!

A heads-up, this is a breaking change and it might affect your current performance baseline a bit. But here's the exciting part - it's packed with new, cool features and promises improved result stability 🥳!
Curious about what's new? Visit our releases page to delve into all the awesome details about this new version.

❌ 1 regressed benchmark
✅ 30 untouched benchmarks
⏩ 6 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
❌ Simulation sleep 1 245.7 µs 585.4 µs -58.02%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing cod-3722-ignore-process-spawning-overhead-in-exec-harness-simulation (a301550) with main (8368147)2

Open in CodSpeed

Footnotes

  1. 6 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

  2. No successful run was found on spike/cod-3440-memtrack-musl (ecbfb5b) during the generation of this report, so main (8368147) was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

@lvaroqui
lvaroqui force-pushed the cod-3722-ignore-process-spawning-overhead-in-exec-harness-simulation branch 2 times, most recently from c72e991 to 3c1b378 Compare October 5, 2026 13:25
@lvaroqui
lvaroqui force-pushed the cod-3722-ignore-process-spawning-overhead-in-exec-harness-simulation branch 2 times, most recently from 1af664e to 8fb4858 Compare October 7, 2026 09:06
@lvaroqui
lvaroqui added this pull request to stack #572 October 7, 2026 09:22
@lvaroqui
lvaroqui force-pushed the cod-3722-ignore-process-spawning-overhead-in-exec-harness-simulation branch 7 times, most recently from 345f77b to b0533e3 Compare October 8, 2026 12:10
moha-bekh and others added 9 commits October 8, 2026 14:38
exec-harness injected a shared library into every benchmark to drive valgrind's
instrumentation from inside the child. That only works on a dynamically linked
executable, so statically linked benchmarks were silently unmeasurable, and it
forced the harness to ship a `.so` next to its binary.

The instrumentation is now toggled in exec-harness's own process, around the
spawn: valgrind propagates the state across `fork`/`exec`, so the child is
measured without anything being injected into it. The preload library, its
compatibility check and the build script that produced it all go away, and the
integration constants become plain consts.

Since the child no longer switches instrumentation on for itself, exec-harness
runs pass `--instr-atstart=inherit` to valgrind; with `no`, the child starts
uninstrumented and the measurement comes back empty. Entrypoint runs keep the
previous default.

BREAKING CHANGE: exec-harness no longer ships a preload library.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both binaries kept their argument parsing and dispatch in `main.rs`, where
nothing else can reach it. Move each into a `cli` module of its own crate and
leave `main.rs` as a wrapper that installs a logger and calls `run_cli`.

Nothing changes for the standalone binaries, but the runner can now link either
CLI and dispatch it in-process.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The runner downloaded `exec-harness` and `memtrack` from GitHub releases at the
start of a run, pinned by version, and `setup --mode memory` installed memtrack
with `cargo install`. That is a network round-trip on every run, a version
matrix to keep in sync, and two more artifacts to release.

Link both crates instead and expose them as hidden `codspeed exec-harness` and
`codspeed memtrack` subcommands, re-executing the current binary where the
runner used to invoke the downloaded tool. They are dispatched before any runner
setup: the re-exec happens in the benchmark's working directory, where an
unrelated `codspeed.yaml` would otherwise abort the measurement.

The binary installer, the memory setup no-op and the memtrack tool status go
away.

BREAKING CHANGE: `exec-harness` and `memtrack` are no longer downloaded or
installed separately; the runner binary carries them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`exec-harness` and `memtrack` were released as their own artifacts. Nothing
downloads them any more, so they stop being release units: their apt build
dependencies move to the runner, which now builds the vendored libbpf and
elfutils, and memtrack is depended on with its default features so the bundled
subcommand carries the tracker.

The released Linux artifacts are musl, and memtrack could not be built for them:
`libbpf-sys` vendors elfutils, whose `configure` looks for `argp`, `obstack` and
`fts`, none of which musl ships, and Debian's `musl-gcc` runs with `-nostdinc`,
so the kernel UAPI headers libbpf needs are out of reach. The recipe lives in
the cargo config so a plain `cargo build --target <arch>-unknown-linux-musl`
works: seed the autoconf cache for the three checks, add a declarations-only
`argp.h` stub on `CPATH`, add the UAPI header paths back through the per-target
`CFLAGS`, and link `-lgcc` on aarch64 for libbpf's outline-atomic helpers.
`close_range` goes through the raw syscall, since `libc` only declares it for
glibc.

CI builds both musl targets on native runners and asserts the artifact is
static with `readelf`. The bundled subcommands no longer take `--version`, and the
memtrack benchmarks call memtrack through the runner instead of installing it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
memtrack runs as a subcommand of this binary, so the allocator set in its own
`main.rs` no longer applies to it. Set mimalloc on the runner binary instead,
which covers memtrack and the rest of the runner alike.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With memtrack bundled, `setcap` targeted the runner binary itself, so every
`codspeed` invocation ran in glibc's secure-execution mode: `LD_*`, `TMPDIR`
and similar variables were stripped before the runner could forward them to
the benchmark, in every mode.

Grant the capabilities to a copy of the binary in `~/.cache`, keyed by its ELF
build id, and run `codspeed memtrack` from it. The copy is installed in one
sudo call, since `~/.cache` can be root-owned, and copies of other builds are
pruned once they are a day old. The runner keeps the user's environment and no
longer holds the eBPF capabilities itself.

The memtrack benchmarks call `codspeed memtrack` from PATH, so CI grants that
binary its capabilities directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The job only needs to prove that both musl targets build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both ship inside the `codspeed` binary, so their own versions no longer
mean anything. Move the version to `[workspace.package]` and have the
runner, `exec-harness` and `memtrack` inherit it. The integration version
`exec-harness` reports goes from 1.3.0 to the runner's.

They still declare binaries, so opt them out of dist: sharing the runner's
version, a `v*` tag would otherwise release them alongside it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add set_executed_benchmark_for_pid, which passes an explicit pid to
instrument_hooks_set_executed_benchmark instead of the calling process'
own. set_executed_benchmark keeps its behavior and delegates to it.

Bump instrument-hooks, whose valgrind instrument now writes a
"Benchmark pid: <pid>" desc line in the dump part when that pid is not
the calling process'. This also brings thread-safe C API exports and the
callgrind_toggle_collect helper.

Refs COD-3722
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lvaroqui
lvaroqui force-pushed the cod-3722-ignore-process-spawning-overhead-in-exec-harness-simulation branch from b0533e3 to a301550 Compare October 8, 2026 13:34
@lvaroqui
lvaroqui marked this pull request as ready for review October 9, 2026 08:27
@lvaroqui
lvaroqui requested a review from not-matthias October 9, 2026 08:27
@greptile-apps

greptile-apps Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[High impact] The reviewed changes appear safe to merge; no actionable defect was found.

Summary

The harness now declares the PID of the command it benchmarks. This lets later readers distinguish the command’s work from the harness’s work.

  • Adds set_executed_benchmark_for_pid while preserving the existing method.
  • Updates the hooks and Valgrind pins, and allows the runner to run under Valgrind without its file capabilities.
  • Adds a test that checks the declared PID has a separate profile.
  • lvaroqui explicitly acknowledged that backend support for excluding the harness’s cost is not released yet and that the hooks submodule needs repointing after its upstream PR merges. These known follow-ups are not findings.

Diagram

sequenceDiagram
    participant H as Exec harness
    participant C as Benchmark command
    participant I as Instrument hooks
    participant V as Valgrind
    H->>I: Start benchmark
    H->>C: Spawn command and save PID
    C-->>H: Exit status
    H->>I: Stop benchmark
    H->>I: Declare command PID and URI
    I->>V: Add benchmark PID description
    I->>V: Dump benchmark part
Loading

Reviews (1) · Last reviewed commit: "feat(instrument-hooks): declare benchmar..." · Reviewed by Greptile

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we shouldn't bump valgrind-codspeed in this PR, but rather do it when we're releasing the new runner (e.g. to make it easier revertable).

It shouldn't be needed, as you can use newer versions as an "experimental version". So locally or in CI you can just build from source or install the .deb

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants