Skip to content

Apply SpanPrototype in decorator afterStart (phase 1b) - #12037

Draft
dougqh wants to merge 9 commits into
dougqh/span-prototype-apifrom
dougqh/decorator-base-prototype
Draft

Apply SpanPrototype in decorator afterStart (phase 1b)#12037
dougqh wants to merge 9 commits into
dougqh/span-prototype-apifrom
dougqh/decorator-base-prototype

Conversation

@dougqh

@dougqh dougqh commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

What Does This Do

Wires the SpanPrototype from #11894 into the decorator base classes, and modernizes the affected tests.

ProductionBaseDecorator / ServerDecorator / ClientDecorator now build a lazily-cached SpanPrototype whose extension chain mirrors the decorator hierarchy (super.buildSpanPrototype().extends_(...) → add this level's constants). afterStart applies it via span.setSpanType / span.setAllTags(prototype.tags()) / spanContext().setIntegrationName(...), replacing the previous N separate span.setTag(TagMap.Entry) calls.

This is behavior-identical: setAllTags runs 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 afterStart specs move off Spock mock-interaction assertions to a state-based harness:

  • RecordingSpan / RecordingSpanContext accumulate the applied state (extends the no-op ImmutableSpan, so only the ~7 mutators afterStart touches are overridden).
  • ExpectedSpanState builds 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-tag setTag stamping with a single baked-once SpanPrototype applied via a fast bulk copy, and move the afterStart specs 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 to master once #11894 lands.

Drops the born-dead SpanPrototype.Builder.initInstrumentationNames(String[]) overload (no caller; initInstrumentationName covers 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 new setAllTags path)
  • :dd-java-agent:agent-bootstrap:spotbugsMain, spotlessJavaCheck — green
  • techdebt + perf-review over the branch diff — clean (no hot-path regression; afterStart is a consolidation of existing per-tag work)

🤖 Generated with Claude Code

dougqh and others added 5 commits July 22, 2026 08:11
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>
@dougqh

dougqh commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@dougqh dougqh added comp: core Tracer core tag: ai generated Largely based on code generated by an AI or LLM tag: no release notes Changes to exclude from release notes type: refactoring labels Jul 22, 2026
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: 284eae7d8a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

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

@datadog-datadog-prod-us1

datadog-datadog-prod-us1 Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

🎯 Code Coverage (details)
Patch Coverage: 94.74%
Overall Coverage: 58.38% (+1.70%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 8e3d18f | Docs | View more details | Give us feedback!

@datadog-datadog-prod-us1

datadog-datadog-prod-us1 Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Bits has a CI fix ready

🟢 Investigated · 🟢 Fix prepared · ⚪ Validation skipped · 🟠 Ready

SpanPrototype.Builder did not meet the coverage threshold because several initialization paths were untested. Added focused coverage for identity initialization, null bases and entries, and populated and empty tag inputs in internal-api/src/test/java/datadog/trace/bootstrap/instrumentation/api/SpanPrototypeTest.java.

Commit fix to this PR


View in Datadog | Reviewed commit 911fc57 · Any feedback? Reach out in #deveng-pr-agent

@dd-octo-sts

dd-octo-sts Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

🟢 Java Benchmark SLOs — All performance SLOs passed

Suite Status
Startup 🟢 pass

SLO thresholds are defined here based on automatically generated metrics. A warning is raised when results are within 5% of the threshold.

PR vs. master results
Scenario Candidate master Δ (95% CI of mean)
startup:insecure-bank:iast:Agent 14.82 s 14.72 s [-0.1%; +1.6%] (no difference)
startup:insecure-bank:tracing:Agent 13.57 s 13.74 s [-1.8%; -0.6%] (maybe better)
startup:petclinic:appsec:Agent 17.43 s 17.18 s [+0.7%; +2.1%] (maybe worse)
startup:petclinic:iast:Agent 17.31 s 17.51 s [-2.0%; -0.3%] (maybe better)
startup:petclinic:profiling:Agent 17.36 s 17.41 s [-1.6%; +1.0%] (no difference)
startup:petclinic:sca:Agent 16.56 s 17.33 s [-8.5%; -0.3%] (maybe better)
startup:petclinic:tracing:Agent 16.44 s 16.63 s [-2.1%; -0.1%] (maybe better)

Commit: 8e3d18fb · CI Pipeline · Benchmarking Platform UI


Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion.

dougqh and others added 2 commits July 22, 2026 17:53
…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>
@dougqh
dougqh force-pushed the dougqh/decorator-base-prototype branch from 284eae7 to 911fc57 Compare July 22, 2026 21:59
@dougqh dougqh changed the title Apply SpanPrototype in decorator afterStart Apply SpanPrototype in decorator afterStart (phase 1b) Jul 23, 2026
@dougqh
dougqh force-pushed the dougqh/span-prototype-api branch from eadba23 to c2d5fb9 Compare July 29, 2026 12:17
dougqh added 2 commits August 28, 2026 21:53
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp: core Tracer core tag: ai generated Largely based on code generated by an AI or LLM tag: no release notes Changes to exclude from release notes type: refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant