Repository navigation
Precompute decision maker tag values and defer building the OpenTelemetry tracestate - #12981
Conversation
Every keep decision built "-" + samplingMechanism and then validated, hashed and looked it up in TagValue's cache. The mechanisms are a small fixed set, so build their TagValues once and index by mechanism, falling back to TagValue.from for anything beyond the table. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Measures the per-trace propagation-tag bookkeeping done on a root sampling decision, both in a bare start/sample/finish span and on fresh PropagationTags with no span around it, for the default, rule and manual keep paths. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
More details
The precomputed decision-maker values preserve existing formatting, the negative-mechanism guard, and fallback behavior for mechanisms outside the table.
🤖 Bits Code Review · Commit 4ddb5d9
🟢 Java Benchmark SLOs — All performance SLOs passed
PR vs. master results
Commit: Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion. |
|
/merge |
|
View all feedbacks in Devflow UI.
The expected merge time in
Build pipeline has failing jobs for 701e3e7: What to do next?
DetailsSince those jobs are not marked as being allowed to fail, the pipeline will most likely fail. |
…ported (#12982) * Defer building the OpenTelemetry tracestate for fresh probability decisions Every root probability decision built an OtelTraceState inside the sampling lock, though only W3C injection and OTLP export read it. Install a ProbabilitySamplingState that keeps the trace id bits and rate, and builds the ot member on first read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Clarify why lazy otel state readers agree on one instance Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add benchmarks for the lazy otel state's inject, export and service-shape cost SamplingDecisionBenchmark gains arms that follow a probability decision with the Datadog header, the W3C tracestate header and the OTLP tracestate, so the deferred OtelTraceState is measured where it's built or skipped. ServiceShapePropagationBenchmark measures head, leaf and intermediate services with a default-configured tracer. It only uses API unchanged since v1.66.0, so it can be built onto release tags to put a cost on #12397. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Rename ServiceShapePropagationBenchmark to PropagationScenariosBenchmark Describe it as a scenario benchmark: each arm is a whole request driven through the tracer's public API, rather than one method in isolation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Test that racing readers get one OpenTelemetry state instance Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
/merge |
|
View all feedbacks in Devflow UI.
The expected merge time in
|
What Does This Do
PTags.installSamplingStatebuilt the_dd.p.dmvalue withTagValue.from("-" + samplingMechanism)on every keep decision. That allocated aString, then ran validation, hashing and a lookup inTagValue's 128-entry cache. All of this happens insidesamplingStateLock.This PR builds the
TagValues for mechanisms 0–15 once, at class init, and indexes them by mechanism. Larger mechanisms still go throughTagValue.from. The negative/UNKNOWNguard is unchanged.Motivation
This is the first of several small follow-ups on per-trace sampling overhead in propagation tags. The eager
OtelTraceStatefrom #12397 is the second, below. The per-PTagslock object and a race in the single-entry Knuth sampling-rate cache will come as separate PRs.The concat itself is older than #12397. #12397 moved it inside the sampling lock.
Benchmark
This PR adds
SamplingDecisionBenchmark:span*arms run a bare root span through start → sample → finish.ptags*arms make the decision on freshPropagationTagswith no span around it.Before =
master'sPTagsFactory; after = this branch. Both runs used-f 1 -wi 3 -w 2s -i 5 -r 3s -t 8 -prof gcon JDK 17 / M-series laptop. B/op is the primary signal. Throughput comes from a single fork and is directional only.ptagsPriorityKeepptagsProbabilityKeepspanDefaultSamplerKeepspanRuleSamplerKeepspanManualKeepspanDefaultSamplerKeepB/op varied run to run (±94), so treat its Δ as approximate.spanManualKeepalso varies between runs: a later run of the same post-change jar measured 888 B/op, not 840. Its Δ could be −48 rather than −96. Theptags*arms gave the same values in every run.ptags*arms still allocate 150–220 B per trace, forPTagsitself, its lock object,SamplingStateand the eagerOtelTraceState. Those are the targets of the follow-up PRs.Additional Notes
TagValue's shared 128-entry cache.DecisionMakerTagValueTestcovers values inside and beyond the table, and instance reuse.datadog.trace.core.propagation.*,DDSpanContextPropagationTagsTestandCoreSpanBuilderTestpass locally.Also included: deferred OpenTelemetry tracestate (from #12982)
What Does This Do
#12397 made every root probability decision build an
OtelTraceState, inside the sampling lock. Only W3Ctracestateinjection and OTLP export ever read it.This PR defers building it for the common case: a fresh probability decision with no inherited
ottracestate member, not rejected by the rate limiter.tryUpdateProbabilitySamplingDecisioninstalls a newProbabilitySamplingState. It keeps only the trace id low-order bits and the rate, and builds theotmember on the firstgetOtelTraceState().OtelTraceStatekeeping its fields final. The CAS runs once per injected or exported trace, uncontended.PropagationTags.SamplingStateis no longerfinal.preservesInheritedOtelPositionnow reads through the getter.installSamplingStateintonextDecisionMakerTagValue, so the new path can share it.Every other path is unchanged:
otmemberforceKeepMotivation
This is part of the follow-ups to #12397's per-trace sampling overhead.
It builds on the decision-maker table above: moving the decision-maker computation into its own method would otherwise make the
"-" + mechanismconcat escape, adding 24 B/op to priority-only decisions.Benchmark
Decision and header paths:
SamplingDecisionBenchmarkBefore = the decision-maker change alone; after = both changes.
-prof gc, B/op.ptagsProbabilityKeep(decision only)ptagsProbabilityKeepDatadogHeaderptagsProbabilityKeepW3CHeaderptagsProbabilityKeepOtlpTracestateptagsPriorityKeepspanDefaultSamplerKeepspanRuleSamplerKeepspanManualKeepOtelTraceStateis still built, plus 16 B for the two inputsProbabilitySamplingStatecarries.-XX:-DoEscapeAnalysisthe gap is +16 B (1080 → 1096) at equal throughput. The rest is a scalar-replacement win the old code got in this direct-call arm and this code doesn't. It only applies to OTLP export with rule sampling: under OTLP export the default sampler isParentBasedAlwaysOnSampler, a priority-only decision that never creates aProbabilitySamplingState. Real export goes throughOtlpTraceJson.writeSpan, so whether the JIT effect carries over there isn't measured.Scenarios:
PropagationScenariosBenchmarkA scenario benchmark: each arm is a whole request driven through the tracer's public API, with a default-configured tracer (
datadog,tracecontext,baggage):headNoPropagation,headPropagation: a root that decides, with or without an outbound injectleaf,intermediate: extract an upstream decision, with or without relaying itIt's here to put this PR in context of #12397's overall cost. It only uses API unchanged since v1.66.0, so it builds onto both commits.
#12397 alone (
79a1710fcd^vs79a1710fcd, B/op):This PR on that benchmark (default sampler, v1.66.0 → master → this branch):
-f 3: Δ from −104 to +39 B, within ±50–150 B error. The expected +16 B is below resolution.otmember), which this PR doesn't touch. That's tracked separately.B/op on the larger scenario arms varies by ±100 B between forks, so treat small deltas there as noise.
Additional Notes
ProbabilitySamplingStateTestcovers:forceKeepstarts from the deferred statedd-trace-coretest suite passes, apart from:TracerConnectionReliabilityTestcases (no Docker locally)DDAgentWriterCombinedTestandPendingTraceBufferTest, which failed under full-suite load and pass when run on their own🤖 Generated with Claude Code