Repository navigation
Conversation
Merging this PR will not alter performance2 warnings
Comparing Footnotes
|
33db967 to
cf0d278
Compare
cf0d278 to
7058ea3
Compare
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>
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 otel -- --ignored`. `scripts/run-otel-example.sh` runs the same example and uploads it. Refs COD-3863 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7058ea3 to
5c8bbe0
Compare
[Medium impact] The change adds an opt-in telemetry collection and upload path for benchmark runs. Fix stale telemetry in reused profile folders before merging.
|
| let body = if is_gzipped { | ||
| let mut decoded = Vec::new(); | ||
| if let Err(error) = GzipDecoder::new(body.as_ref()) | ||
| .read_to_end(&mut decoded) | ||
| .await |
There was a problem hiding this comment.
Gzip can exhaust runner memory
MAX_REQUEST_BODY_BYTES caps the compressed body, but read_to_end expands it into an unbounded Vec before any OTLP decoding. A local client can send a small, highly compressed body that makes the runner run out of memory. Cap the decompressed size too and reject requests that exceed it.
How this was verified: The localhost receiver accepts gzip bodies and expands them into an unbounded buffer before checking their OTLP contents.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/instruments/otel/receiver.rs
Line: 236-240
Comment:
**Gzip can exhaust runner memory**
`MAX_REQUEST_BODY_BYTES` caps the compressed body, but `read_to_end` expands it into an unbounded `Vec` before any OTLP decoding. A local client can send a small, highly compressed body that makes the runner run out of memory. Cap the decompressed size too and reject requests that exceed it.
**How this was verified:** The localhost receiver accepts gzip bodies and expands them into an unbounded buffer before checking their OTLP contents.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| fn write(&mut self, rows: Vec<R>) -> Result<()> { | ||
| if rows.is_empty() { | ||
| return Ok(()); | ||
| } | ||
| let writer = match &mut self.writer { | ||
| Some(writer) => writer, | ||
| None => self.writer.insert(TelemetryWriter::create(&self.path)?), |
There was a problem hiding this comment.
Reusing --profile-folder can upload telemetry from the previous run. LazyWriter only opens a file when new rows arrive, so a run with no logs leaves the old otel/logs.msgpack.zst untouched; the same happens for spans. The uploader then archives the whole folder alongside the new benchmark timestamps. Remove the previous telemetry output before collecting a new run, and add a test that reuses a folder with fewer signals.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/instruments/otel/files.rs
Line: 106-112
Comment:
**Old telemetry enters new runs**
Reusing `--profile-folder` can upload telemetry from the previous run. `LazyWriter` only opens a file when new rows arrive, so a run with no logs leaves the old `otel/logs.msgpack.zst` untouched; the same happens for spans. The uploader then archives the whole folder alongside the new benchmark timestamps. Remove the previous telemetry output before collecting a new run, and add a test that reuses a folder with fewer signals.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| //! checkout, written as telemetry files that line up with the benchmark markers. | ||
| //! | ||
| //! Needs codspeed-rust checked out next to this repository. Run with | ||
| //! `cargo test --test otel -- --ignored`. |
There was a problem hiding this comment.
Test command names missing target
The documented command still names otel, but this file is now tests/telemetry.rs and there is no otel test target. Change it to cargo test --test telemetry -- --ignored so contributors can run the ignored end-to-end test.
| //! `cargo test --test otel -- --ignored`. | |
| //! `cargo test --test telemetry -- --ignored`. |
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/telemetry.rs
Line: 5
Comment:
**Test command names missing target**
The documented command still names `otel`, but this file is now `tests/telemetry.rs` and there is no `otel` test target. Change it to `cargo test --test telemetry -- --ignored` so contributors can run the ignored end-to-end test.
```suggestion
//! `cargo test --test telemetry -- --ignored`.
```
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Add an experimental
--experimental-otelflag (CODSPEED_EXPERIMENTAL_OTEL) that collects the OpenTelemetry data the benchmarks export, and uploads it with the results.When the flag is set, each run part 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 of traces, logs and metrics, plain or gzipped, on/v1/{traces,logs,metrics}.While the benchmark runs, the receiver only writes each request body to disk as received, so it adds no compression work next to the measured process. A request is stored before it is acknowledged, so everything the process sent before exiting is kept. Once the process exits, the requests are decompressed and packed into a single
otel.tar.zstin the profile folder. Compressing them as one stream with long-distance matching takes advantage of the content that repeats across export batches. Nothing is written when the benchmark exports nothing.To put the spans and logs on the same clock as the benchmark markers, instrument-hooks is bumped for
instrument_hooks_monotonic_offset: integrations timestamp with the monotonic clock shifted to read as wall clock, and record the offset in thecodspeed.clock.monotonic_offset_nsresource attribute. The bindings expose it asInstrumentHooks::monotonic_offsetandMONOTONIC_OFFSET_ATTRIBUTE.tests/otel.rsruns the divanotel_exampleend to end throughcodspeed run --experimental-otelin walltime mode, and checks that every span and log record, shifted back by its resource's offset, falls within the benchmark markers. It needs a codspeed-rust checkout next to this repository, so it is ignored by default:cargo test --test otel -- --ignored. SetCODSPEED_OTEL_TEST_PROFILE_FOLDERto keep the profile folder for inspection.Stacked on #587.
Closes COD-3863