Skip to content

feat: collect the OTLP data exported by the benchmarks - #589

Open
lvaroqui wants to merge 4 commits into
mainfrom
cod-3863-featrunner-collect-otlp-with-rotel-and-upload-it-as-parquet
Open

lvaroqui wants to merge 4 commits into
mainfrom
cod-3863-featrunner-collect-otlp-with-rotel-and-upload-it-as-parquet

Conversation

@lvaroqui

@lvaroqui lvaroqui commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Add codspeed run --mode telemetry, an experimental mode that collects the OpenTelemetry spans and logs the benchmarks export, and uploads them with the results.

It is a separate pass rather than a flag on the walltime run: nothing has to keep the export out of the measured runs, and each benchmark runs once instead of once per walltime iteration. Combine it with a measuring mode, e.g. --mode telemetry,walltime.

Analysis executor. The mode runs on a new generic analysis executor: it runs the benchmarks once each without a profiler, answering the integration as an analysis mode, with analysis instruments hooked onto it. An instrument sets up before the process is spawned, receives the integration's start and stop commands, and stops once the process exits. The executor saves the benchmark markers as ExecutionTimestamps.msgpack, and fails when no benchmark ran unless --allow-empty is set. Uploads carry executor: "analysis" and the enabled instruments, so later analysis instruments only add an instrument. CODSPEED_RUNNER_MODE is telemetry in this mode, which cargo codspeed run reads to pick its telemetry build.

Telemetry instrument.

  • It starts an OTLP/HTTP receiver on a free localhost port and passes its URL to the benchmark in CODSPEED_OTEL_ENDPOINT. The receiver accepts protobuf and JSON exports, plain or gzipped, on /v1/{traces,logs,metrics}. Each trace and log request is decompressed and written to disk before it is acknowledged, so everything an exited process sent is kept. Metrics are acknowledged and dropped.
  • Once the process exits, the requests are decoded into otel/spans.msgpack.zst and otel/logs.msgpack.zst in the profile folder, one row per span or log, formatted like the OpenTelemetry Collector's ClickHouse exporter. A request that fails to decode is skipped with a warning, and a file is only written when it has rows. The output of a previous run in a reused --profile-folder is removed first.
  • The codspeed.monotonic_start_ns/codspeed.monotonic_end_ns span attributes and the codspeed.monotonic_time_ns log attribute, set by the integrations on the monotonic clock of the benchmark markers, move to their own row fields. With the markers, they tell which benchmark emitted each span or log. A warning counts the rows that lack them.

File format. runner_shared::telemetry defines the files, shared by the runner and the parsers: a zstd stream (16 MB window, long-distance matching, since rows repeat their resource attributes far apart) of positional MessagePack records. Records are versioned like UnwindData, with v1 fixtures that must keep decoding. TelemetryReader decodes a file from any Read as its bytes arrive.

Testing. tests/telemetry.rs builds the divan telemetry bench of a codspeed-rust checkout next to this repository, runs it through codspeed run --mode telemetry, and checks that every span and log falls within the rounds of the benchmark that emitted it, keeps its severity and call site, and that the benchmark without telemetry has no rows. CI has no codspeed-rust checkout, so it is ignored by default: cargo test --test telemetry -- --ignored. Set CODSPEED_OTEL_TEST_PROFILE_FOLDER to keep the profile folder for inspection. scripts/run-telemetry-example.sh runs the same example with --mode telemetry,walltime and uploads it.

Closes COD-3863

@lvaroqui
lvaroqui added this pull request to stack #590 October 16, 2026 12:56
@codspeed

codspeed Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

2 warnings

⚠️ Walltime results may be unreliable

Some benchmarks used the Walltime instrument outside CodSpeed Macro Runners, which can make the results inconsistent.

Learn more about Macro Runners

See affected benchmarks

⚠️ 8 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

✅ 41 untouched benchmarks
⏩ 6 skipped benchmarks1


Comparing cod-3863-featrunner-collect-otlp-with-rotel-and-upload-it-as-parquet (c40417d) with main (d9612e3)

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. ↩

Base automatically changed from cod-3863-chore-avoid-blocking-in-runner to main October 16, 2026 13:07
@lvaroqui
lvaroqui force-pushed the cod-3863-featrunner-collect-otlp-with-rotel-and-upload-it-as-parquet branch 3 times, most recently from 33db967 to cf0d278 Compare October 20, 2026 14:33
@lvaroqui
lvaroqui force-pushed the cod-3863-featrunner-collect-otlp-with-rotel-and-upload-it-as-parquet branch from cf0d278 to 7058ea3 Compare October 23, 2026 13:16
@lvaroqui
lvaroqui force-pushed the cod-3863-featrunner-collect-otlp-with-rotel-and-upload-it-as-parquet branch from 7058ea3 to 5c8bbe0 Compare October 23, 2026 16:35
@lvaroqui
lvaroqui marked this pull request as ready for review October 23, 2026 16:36
@greptile-apps

greptile-apps Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The change affects runs that enable telemetry mode and the profile data they upload.

The harness release and download pin need updating before telemetry works for plain commands.

Fix All in Claude CodeFindings

  1. P1 Plain telemetry commands fail ▶
Fix with agent prompt
### Issue 1
crates/exec-harness/src/lib.rs:72
Plain `Exec` targets still use the downloaded `exec-harness` version `1.4.1`. `AnalysisExecutor` passes `CODSPEED_RUNNER_MODE=telemetry`, but that released harness only accepts the old modes, so it exits before running the command. Adding support in this workspace does not update the downloaded binary.

Release the updated harness and bump its version and installer pin.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Adds experimental --mode telemetry through a new analysis executor.

  • Telemetry runs save benchmark spans and logs with their timings.
  • Telemetry rows use a shared, versioned format for writing and reading.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Telemetry mode] --> B[Analysis executor]
  B --> C[Benchmark process]
  C -->|OTLP HTTP| D[Local receiver]
  C -->|Benchmark markers| E[Execution timestamps]
  D --> F[Stored requests]
  F --> G[Compressed span and log files]
  E --> H[Analysis upload]
  G --> H
Loading

Reviews (2) · Last reviewed commit: "fix(otel): drop the telemetry output of ..." · Reviewed by Greptile

Comment thread src/instruments/otel/receiver.rs
Comment thread src/instruments/otel/files.rs
Comment thread tests/telemetry.rs Outdated
Add the files holding the OpenTelemetry spans and logs the benchmarks
emit, shared by the runner that writes them and the parsers that read
them: `otel/spans.msgpack.zst` and `otel/logs.msgpack.zst` in the
profile folder.

Each file is a zstd stream of positional MessagePack records, one per
span or log, like the memtrack artifacts. Rows are flattened for the
tables they are loaded into: ids as hex, attribute values as text, and
the times of a span or log on the monotonic clock of the benchmark
markers, when the benchmark recorded them, in their own fields.
zstd runs with a 16 MB window and
long-distance matching, since rows repeat their resource attributes far
apart.

Versions follow `UnwindData`: each record is wrapped in a versioned enum
(`SpanRecord`, `LogRecord`), `SpanRow` and `LogRow` alias the current
version, and older versions are upgraded through `From`. Version 1
fixtures must keep decoding.

`TelemetryReader` decodes a file from any `Read`, such as an archive
entry, as its bytes arrive. Unlike a truncated memtrack stream, a file
cut in the middle of a row ends with an error.

Refs COD-3863
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lvaroqui
lvaroqui force-pushed the cod-3863-featrunner-collect-otlp-with-rotel-and-upload-it-as-parquet branch from 5c8bbe0 to f33f3d6 Compare October 26, 2026 13:32
@lvaroqui
lvaroqui requested review from not-matthias and removed request for not-matthias October 26, 2026 14:09
@lvaroqui
lvaroqui force-pushed the cod-3863-featrunner-collect-otlp-with-rotel-and-upload-it-as-parquet branch from d870617 to c5290f2 Compare October 26, 2026 14:29
lvaroqui and others added 3 commits October 26, 2026 15:55
Add `codspeed run --mode telemetry`, an experimental mode collecting the
OpenTelemetry spans and logs the benchmarks export. It is a separate
pass, so nothing has to keep the export out of the measured runs, and
the benchmarks run once each instead of once per walltime iteration.

The mode runs on a new analysis executor: it runs the benchmarks once
each without a profiler, answering the integration as an analysis mode,
with analysis instruments hooked onto it. An instrument sets up before
the process is spawned, gets the integration's start and stop profiler
commands, and stops once the process exits, with the benchmark markers.
The executor saves those markers as `ExecutionTimestamps.msgpack` in the
profile folder, and fails when no benchmark ran, unless `--allow-empty`
is set. Uploads carry `executor: "analysis"` and the enabled
instruments, so later analysis instruments only add an instrument.

The telemetry mode enables the telemetry instrument:

- It starts an OTLP/HTTP receiver on a free localhost port, passed to
  the benchmark in CODSPEED_OTEL_ENDPOINT. The receiver accepts
  protobuf and JSON exports, plain or gzipped, and writes each trace
  and log request to disk as received. Metrics are acknowledged and
  dropped.
- Once the process exits, the requests are decoded and written as the
  runner-shared telemetry files, one row per span or log, formatted
  like the OpenTelemetry Collector's ClickHouse exporter. A request that
  fails to decode is skipped with a warning.
- The `codspeed.monotonic_start_ns` and `codspeed.monotonic_end_ns`
  attributes of a span and the `codspeed.monotonic_time_ns` attribute of
  a log, their times on the monotonic clock of the benchmark markers,
  move to their own fields of the rows: along with the markers, they tell
  the benchmark that emitted each span or log. A warning counts the spans
  and logs without a start or time.

`CODSPEED_RUNNER_MODE` is `telemetry` in this mode, which
`cargo codspeed run` reads to pick its telemetry build. exec-harness
runs it like memory mode.

Refs COD-3863
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Build the divan `telemetry` bench of a codspeed-rust checkout next to
this repository with `cargo codspeed build -m telemetry`, run it through
`codspeed run --mode telemetry`, and read back its telemetry files. Its
benchmarks emit `tracing` spans and events, a `tracing` event outside of
any span, records of the `log` crate, and nothing at all. Each span and
log is timestamped within the run on the wall clock, and falls within
the rounds of the benchmark that emitted it by its time on the monotonic
clock. Records keep their severity and call site, the ones below `info`
are filtered out, and the benchmark without telemetry has no rows.

The test is ignored by default since CI has no codspeed-rust checkout.
Run it with `cargo test --test telemetry -- --ignored`.
`scripts/run-telemetry-example.sh` runs the same example and uploads it.

Refs COD-3863
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… folder

A profile folder passed with `--profile-folder` is reused as-is. The spans and logs files are
only created when rows arrive, so a run exporting no logs kept the logs file of the previous
run, which was uploaded with the new results. Remove the previous files before writing.

The receiver also removes request files left by an interrupted run before listening: their
sequence numbers restart at zero, so they were decoded along with the new requests.

Refs COD-3863
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lvaroqui
lvaroqui force-pushed the cod-3863-featrunner-collect-otlp-with-rotel-and-upload-it-as-parquet branch from c5290f2 to c40417d Compare October 26, 2026 15:00
@lvaroqui
lvaroqui requested a review from not-matthias October 26, 2026 15:03
match measurement_mode {
MeasurementMode::Walltime => walltime::perform(commands),
MeasurementMode::Memory => analysis::perform(commands),
MeasurementMode::Memory | MeasurementMode::Telemetry => analysis::perform(commands),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Plain telemetry commands fail

Plain Exec targets still use the downloaded exec-harness version 1.4.1. AnalysisExecutor passes CODSPEED_RUNNER_MODE=telemetry, but that released harness only accepts the old modes, so it exits before running the command. Adding support in this workspace does not update the downloaded binary.

Release the updated harness and bump its version and installer pin.

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/exec-harness/src/lib.rs
Line: 72

Comment:
**Plain telemetry commands fail**

Plain `Exec` targets still use the downloaded `exec-harness` version `1.4.1`. `AnalysisExecutor` passes `CODSPEED_RUNNER_MODE=telemetry`, but that released harness only accepts the old modes, so it exits before running the command. Adding support in this workspace does not update the downloaded binary.

Release the updated harness and bump its version and installer pin.

**Knowledge Base Used:**
- [Benchmark execution engine](https://app.greptile.com/codspeed/-/custom-context/knowledge-base/codspeedhq/codspeed/-/docs/benchmark-execution.md)
- [Execution harness and instrumentation bindings](https://app.greptile.com/codspeed/-/custom-context/knowledge-base/codspeedhq/codspeed/-/docs/execution-harness-and-instrumentation.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

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.

1 participant