Apply SpanPrototype in decorator afterStart (phase 1b) - #12037
Conversation
The builder API (extends_/init*) plus its per-mechanism microbenchmark and a pure-API test, split out from the combined span-prototype work so the abstraction lands independently of the decorator demo. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A prototype constant that is null or an empty CharSequence should be "no tag" -- matching AgentSpan.setTag and the decorators' cached-Entry path -- not a baked empty tag. Add TagMap.Entry.isEmptyValue as the single definition of an empty value (both Entry.create overloads now delegate to it), and gate SpanPrototype.Builder.initTag on it via the plain set(key, value) path so no Entry is allocated (the wrong path once tags are stored densely). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Thread a SpanPrototype through span construction: AgentTracer gains buildSpan/startSpan(SpanPrototype, operationName) (defaults seed identity only, correct for the noop tracer, with an explicit NoopTracerAPI.startSpan override). CoreTracer overrides buildSpan to seed the prototype's frozen constant tags in buildSpanContext at the precedence slot just before the builder's own tags (prototype and builder form one precedence atom; explicit builder tags win), and overrides startSpan to seed builder-free via the static CoreSpanBuilder.startSpan path (no MultiSpanBuilder allocation, mirroring startSpan(String,...)). Explicit operationName wins; null falls back to the prototype's. Intercepted constants (e.g. span.kind) seed through the interceptor so their context side-effects still fire. Prototype params @nonnull. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…formly init* BaseDecorator.afterStart sets the integration name as a side effect alongside the component tag (setIntegrationName(component)), which IntegrationAdder later serializes as _dd.integration. A prototype baking only the component tag would drop that. Add initComponentAndIntegration(component): sets the component tag AND records it as the integration name (inherited via extends_), applied via setIntegrationName at construction. Rename the builder setters to a uniform init* surface now that a component sibling exists and to convey "everything here bakes the prototype's initial state": initComponent -> initComponentOnly, instrumentationName -> initInstrumentationName(s), operationName -> initOperationName, spanType -> initSpanType. Accessors are unchanged. Renames are confined to SpanPrototype.Builder and its callers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A dd-trace-core JMH benchmark covering the full create -> (tag) -> finish lifecycle, finished against a no-op DropWriter so -prof gc isolates create/tag/finish allocation from serialization. Pairs baseline shapes (web-server 7 tags, JDBC 9 tags; setTag and builder-withTag) with prototype arms: buildSpan(SpanPrototype).start() and the builder-free startSpan(SpanPrototype). Measured (Threads(8), -f3 -wi5 -i5 -prof gc): prototype construction cuts gc.alloc.rate.norm ~-5% web (-80 B/op) / ~-10% jdbc (-120 B/op) vs baseline -- tracking the number of baked constants (fewer per-span TagMap.Entry allocations). The builder-free startSpan is deterministic (no MultiSpanBuilder); buildSpan's builder is escape-analyzed away in this shallow micro, so startSpan is the EA-independent path for production's deeper/megamorphic call sites. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. More of your lovely PRs please. Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
|
🎯 Code Coverage (details) 🔗 Commit SHA: 8e3d18f | Docs | View more details | Give us feedback! |
Bits has a CI fix ready🟢 Investigated · 🟢 Fix prepared · ⚪ Validation skipped · 🟠 Ready
View in Datadog | Reviewed commit 911fc57 · Any feedback? Reach out in #deveng-pr-agent |
🟢 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. |
…tion through it Introduce apply(SpanPrototype) as the single seam for stamping a prototype's constant initial state. It applies span type, constant tags, and integration name as fallback defaults -- only where the span has not already set them -- so it never clobbers explicit values, is order-independent, and self-neutralizes once construction has already seeded the same prototype. DDSpanContext.apply is the authoritative implementation (the context owns the tag map and will host the eventual bulk-share fast path + identity short-circuit); DDSpan.apply routes straight to it. The AgentSpan default is the best-effort fallback for non-core spans. The construction path (CoreSpanBuilder) now calls context.apply(prototype) instead of inlining the tag + integration-name seeding. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… off mocks Have BaseDecorator/ServerDecorator/ClientDecorator build a lazily-cached SpanPrototype (extension chain mirroring the decorator hierarchy) and apply it in afterStart via span.setSpanType/setAllTags/setIntegrationName, replacing the per-Entry setTag calls. Behavior-identical: setAllTags runs the same constant tags through the same interceptor path the per-tag calls used. Migrate the four afterStart specs from Spock mock-interaction assertions to a state-based harness (RecordingSpan/RecordingSpanContext accumulate applied state; ExpectedSpanState asserts the whole state at once), with three leniency modes matching Spock's polymorphic feature-method inheritance across the decorator hierarchy. Other specs (onPeerConnection/onConnection/onStatement/ beforeFinish) are unchanged. Also drop the born-dead SpanPrototype.Builder.initInstrumentationNames(String[]) overload (no caller); initInstrumentationName covers the single-name case. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
284eae7 to
911fc57
Compare
eadba23 to
c2d5fb9
Compare
…HEAD # Conflicts: # dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/BaseDecorator.java # dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/ClientDecorator.java # dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/ServerDecorator.java # dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/BaseDecoratorTest.groovy # dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java # internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java # internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/SpanPrototype.java # internal-api/src/test/java/datadog/trace/bootstrap/instrumentation/api/SpanPrototypeTest.java
No caller uses this generic setter -- tag population goes through the more specific component()/spanKind()/language() builder methods.
What Does This Do
Wires the
SpanPrototypefrom #11894 into the decorator base classes, and modernizes the affected tests.Production —
BaseDecorator/ServerDecorator/ClientDecoratornow build a lazily-cachedSpanPrototypewhose extension chain mirrors the decorator hierarchy (super.buildSpanPrototype()→.extends_(...)→ add this level's constants).afterStartapplies it viaspan.setSpanType/span.setAllTags(prototype.tags())/spanContext().setIntegrationName(...), replacing the previous N separatespan.setTag(TagMap.Entry)calls.This is behavior-identical:
setAllTagsruns the same constant tags through the same tag-interceptor path the per-tag calls used today; it's a consolidation, not a new code path. Bulk-share (skipping per-tag interception) is deliberately deferred to the dense-store / tag-registry work.Tests — the four
afterStartspecs move off Spock mock-interaction assertions to a state-based harness:RecordingSpan/RecordingSpanContextaccumulate the applied state (extends the no-opImmutableSpan, so only the ~7 mutatorsafterStarttouches are overridden).ExpectedSpanStatebuilds the expected state per level and asserts it in one shot, with three leniency modes (identity / exact / allow-extra-tags) matching Spock's polymorphic feature-method inheritance down the decorator hierarchy.Other specs (
onPeerConnection/onConnection/onStatement/beforeFinish) are unchanged and still use mocks.Motivation
Replace
BaseDecorator.afterStart's per-tagsetTagstamping with a single baked-onceSpanPrototypeapplied via a fast bulk copy, and move theafterStartspecs off brittle mock-interaction assertions onto a state-based harness that survives the consolidation.Additional Notes
Stacked on #11894 (
dougqh/span-prototype-api). Review/merge that first; this PR's base retargets tomasteronce #11894 lands.Drops the born-dead
SpanPrototype.Builder.initInstrumentationNames(String[])overload (no caller;initInstrumentationNamecovers the single-name case) — shows as a 1-line deletion against the #11894 base.Test plan:
:dd-java-agent:agent-bootstrap:test— green (afterStart specs exercise the newsetAllTagspath):dd-java-agent:agent-bootstrap:spotbugsMain,spotlessJavaCheck— greenafterStartis a consolidation of existing per-tag work)🤖 Generated with Claude Code