Repository navigation
Conversation
…feature Add an opt-in `otel` feature to `codspeed` and `codspeed-divan-compat`. When `CODSPEED_OTEL_ENDPOINT` is set, `divan::main()` installs a global `tracing` subscriber that exports the spans and events emitted by benchmarks over OTLP/HTTP, filtered by `CODSPEED_OTEL_FILTER` (defaults to `info`), and flushes them when the run ends. Spans from the exporter's own HTTP stack are always filtered out to avoid an export loop. Benchmarks that already set a global subscriber are left untouched; they can compose `divan::otel::layer()` into their own subscriber instead. Closes COD-3862 Co-Authored-By: Claude Opus 5.5 <[email protected]>
Merging this PR will regress 18 benchmarks
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ❌ | Walltime | generate_combinations[9] |
5.7 µs | 8.1 µs | -29.93% |
| ❌ | Walltime | bench_array2[10] |
5 ns | 7 ns | -28.57% |
| ❌ | Walltime | bench_array1[10] |
5 ns | 7 ns | -28.57% |
| ❌ | Walltime | div |
3 ns | 4 ns | -25% |
| ❌ | CPU Simulation | recursive_memoized[BTreeMap<u64, u64>, 0] |
717.2 ns | 828.3 ns | -13.41% |
| ❌ | Walltime | init_array[1000] |
1.5 µs | 1.7 µs | -10.46% |
| ❌ | Walltime | permutations[6] |
68.5 µs | 76.4 µs | -10.38% |
| ❌ | CPU Simulation | a |
568.5 ns | 624.1 ns | -8.9% |
| ❌ | CPU Simulation | a |
568.5 ns | 624.1 ns | -8.9% |
| ❌ |
CPU Simulation | iter_manual_simple |
839.4 ns | 894.9 ns | -6.21% |
| ❌ | CPU Simulation | process_items_with_counter |
1.9 µs | 2 µs | -5.52% |
| ❌ | Walltime | bench_array2[42] |
35 ns | 37 ns | -5.41% |
| ❌ | Walltime | bench_array1[42] |
35 ns | 37 ns | -5.41% |
| ❌ | Walltime | init_array[42] |
35 ns | 37 ns | -5.41% |
| ❌ | Walltime | Iterative[21] |
21 ns | 22 ns | -4.55% |
| ❌ | Walltime | from_elem_decimal[1024] |
213 ns | 223 ns | -4.48% |
| ❌ | Walltime | slice_into_vec_with_bytes |
53 ns | 55 ns | -3.64% |
| ❌ | CPU Simulation | string_copy_with_bytes_counter |
1.6 µs | 1.7 µs | -3.22% |
| ⚡ | Walltime | iterative[0] |
4 ns | 1 ns | ×4 |
| ⚡ | Walltime | print_env_hello |
4.9 µs | 1.9 µs | ×2.5 |
| ... | ... | ... | ... | ... | ... |
ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing cod-3862-featrust-otel-cargo-feature-in-codspeed-divan-compat (5468bd4) with main (76685ee)
Footnotes
-
215 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. ↩
…sions `reqwest` pulls `idna_adapter` 1.2.2 and the `icu_*` 2.x crates, which require rustc 1.88. Resolve them with the MSRV-aware fallback resolver so the lockfile builds on rustc 1.85. `sharded-slab` declares `lazy_static = "1"` but fails to build with 1.0.x, which breaks the minimal-versions check. Raise the floor with an optional direct `lazy_static = "1.1"` dependency enabled by the `otel` feature. Refs COD-3862 Co-Authored-By: Claude Opus 5.5 <[email protected]>
Spans exported with the otel feature carried nothing tying them to a benchmark, so a suite with several benchmarks could not be told apart. Add `benchmark_scope::enter`/`exit`, which the integrations call around each benchmark, outside the measured section. A span processor sets the `codspeed.benchmark.uri` attribute on every span started inside the scope. The scope is process-wide, so spans of threaded benchmarks are labelled too. Without the otel feature, both calls do nothing. The divan walltime harness now computes the benchmark URI before running it instead of after. Refs COD-3862 Co-Authored-By: Claude Opus 5.5 <[email protected]>
The batch span processor exports on a 5s timer and whenever 512 spans are queued, so exports could run inside the measured section, and it dropped every span past a 2048-span queue. Replace it with a processor holding the ended spans in memory, exported when the benchmark scope exits, after the measured section and before the benchmark is reported as executed, and on shutdown. Exports are sent in requests of 4096 spans. Past 1,000,000 held spans, spans are dropped with a warning, to bound the memory of a benchmark emitting spans on every iteration. Refs COD-3862 Co-Authored-By: Claude Opus 5.5 <[email protected]>
Events were only recorded as span events, so a receiver got no logs signal and events emitted outside any span were lost. Export them as log records to `/v1/logs` instead, held until the benchmark ends like spans, labeled with the running benchmark URI and carrying the trace and span IDs of the span they are emitted in. Spans no longer carry the events, which would otherwise be exported twice. Refs COD-3862 Co-Authored-By: Claude Opus 5.5 <[email protected]>
Unable to generate the flamegraphsThe performance report has correctly been generated, but there was an internal error while generating the flamegraphs for this run. We're working on fixing the issue. Feel free to contact us on Discord or at [email protected] if the issue persists. |
Bump instrument-hooks for `instrument_hooks_monotonic_offset`, the offset that shifts the monotonic clock of the benchmark markers to read as wall clock times, cached for the process. Expose it as `InstrumentHooks::monotonic_offset`, 0 without instrument hooks. In analysis mode, the hooks also send it to the runner. Refs COD-3862 Co-Authored-By: Claude Opus 5.5 <[email protected]>
Spans and log records carried wall clock times, which cannot be lined up exactly with the benchmark markers taken on the monotonic clock. With instrument hooks, timestamp log records with the monotonic clock shifted by `InstrumentHooks::monotonic_offset`, and convert span start, end and event times to it when the span ends. The hooks send the offset to the runner, which subtracts it to get back the clock of the markers, while standard tools still read wall clock times. Without instrument hooks, times stay wall clock times. Refs COD-3862 Co-Authored-By: Claude Opus 5.5 <[email protected]>
…elemetry mode `tracing` spans and events weigh on the measurements of the code they instrument. Every build for the simulation, walltime and memory modes now passes `--cfg=codspeed_tracing_off`, behind which the `codspeed` crate depends on `tracing` with `max_level_off`: features unification compiles every span and event of the build out, dependencies included. Unlike `--features tracing/max_level_off`, it doesn't require each selected package to depend on `tracing` directly. `--keep-tracing` opts out, e.g. to benchmark a `tracing` subscriber. The new `telemetry` measurement mode builds the benchmarks to collect the telemetry they emit instead of measuring them: they run once like in simulation mode (`--cfg=codspeed`), keep `tracing`, and land in `target/codspeed/telemetry`. It can't be combined with another mode, nor with `--keep-tracing`, which it implies. `release_max_level_*` features still take precedence over `max_level_off` in measured builds, since debug assertions are off. Refs COD-3862 Co-Authored-By: Claude Opus 5.5 <[email protected]>
Telemetry is now collected in a run of its own (the `telemetry` mode of `cargo codspeed`), whose performance is not measured, so spans and log records no longer need to be held until each benchmark exits. They go through the SDK batch processors instead, from a background thread, with a queue of 65536 rather than the default 2048 so that a benchmark emitting on every iteration doesn't overflow it. The monotonic clock timestamps are kept, by wrapping the batch processors. `otel::init` and `otel::layer` return `None` when `tracing` is compiled out, as in measured builds: nothing could be exported, and `EnvFilter` would warn that its directives are disabled statically. Drops the `futures-executor` dependency, only used by the held exports. Refs COD-3862 Co-Authored-By: Claude Opus 5.5 <[email protected]>
2765419 to
5468bd4
Compare
The OpenTelemetry log bridge sets the event name of every record to the name of its `tracing` callsite, which `tracing` generates as `event <file>:<line>` for an event given no `name:`, and `log event` for a record of the `log` crate. In the OpenTelemetry logs data model a record with an event name is an event, so every log line was exported as a named event. A log processor now clears these generated names, so that only the events named at their callsite keep one. It compares the name with the record's own location, leaving any other name as is. The bridge's `experimental_metadata_attributes` feature is enabled for that location: the `code.filepath`, `code.lineno` and `code.namespace` attributes, which keep it exported once the name is cleared. Refs COD-3862 Co-Authored-By: Claude Opus 5.5 <[email protected]>
Add an opt-in
otelfeature tocodspeedandcodspeed-divan-compatthat exports thetracingspans and events emitted by benchmarks over OTLP/HTTP.With the feature enabled,
divan::main()installs a globaltracingsubscriber whenCODSPEED_OTEL_ENDPOINTis set, and flushes the spans when the run ends.CODSPEED_OTEL_FILTERtakesEnvFilterdirectives to select what is exported, and defaults toinfo. Nothing changes when the variable is unset or the feature is off; all the OpenTelemetry dependencies are optional.Benchmarks that already set their own global subscriber are left untouched. They can add
divan::otel::layer()to it instead, as documented in the README.Worth a careful look:
hyper,reqwest,opentelemetry_*, …) are always filtered out. Exporting them would create more spans, in an endless loop.benches/otel_example.rsemits nested spans and events, and adebugspan that the default filter drops. Point it at any OTLP/HTTP receiver to check the output:Closes COD-3862