Skip to content

Introduce SpanPrototype: baked-once constant span-tag descriptor#11828

Draft
dougqh wants to merge 3 commits into
masterfrom
dougqh/span-prototype
Draft

Introduce SpanPrototype: baked-once constant span-tag descriptor#11828
dougqh wants to merge 3 commits into
masterfrom
dougqh/span-prototype

Conversation

@dougqh

@dougqh dougqh commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Draft — increment 1 of a multi-step change. Abstraction + bake only, purely additive. Opening early for design review of the shape before the core-path wiring.

What this is

SpanPrototype is a baked-once, frozen descriptor of a span's constant initial tags — the per-decorator constants (component / span.kind / language / analytics rate) that BaseDecorator.afterStart otherwise stamps one entry at a time, per span.

The goal (across increments) is to move that constant stamping to span construction as a single seeded copy, so afterStart's N setTag calls collapse. It's the biggest per-span structural CPU lever in the decorator layer, and it doubles as a pit of success: writing buildPrototype(tags) is the same tags.set(key, value) you'd write on the span, except it runs once — the cheap-to-write path becomes the cheap-to-run path.

Increment 1 (this PR) — abstraction + bake, additive

  • SpanPrototype (internal-api): a frozen TagMap holder (of / tags / NONE). Rides the existing TagMap API, so it's independent of the deeper TagMap/dense-store rework — the internal seed can get faster later without changing this surface.
  • BaseDecorator.prototype(): baked once, lazily cached (same static-init caution as componentEntry()).
  • buildPrototype(TagMap) contribute-chain: mirrors the afterStart super-chain (Base = component + analytics; Server = + span.kind + language; Client = + span.kind), but runs once at bake rather than per span. This is where the type/integration layering happens.
  • Purely additive: afterStart is byte-for-byte unchanged and prototype() is not yet wired into span creation. Zero behavior change.
  • SpanPrototypeTest (JUnit 5) verifies the composition (server prototype carries component + span.kind=server + language; client carries component + span.kind=client; baked once).

Planned next increments (for review context — NOT in this PR)

  1. Wire into construction, API-first. Public startSpan(SpanPrototype, …) / buildSpan(SpanPrototype, …) (additive overloads; existing signatures delegate with SpanPrototype.NONE, so no call-site sweep). Internally the prototype threads through the builder exactly like tagLedger and is applied at buildSpanContext right after defaultSpanTags via setAllTags — layered under per-span withTags. afterStart's constant stamping is gated off (decorator-owned startSpan skips it) for migrated integrations; NONE keeps everyone else on today's path. One server integration migrated + micro-measured.
  2. Optional deepening (pure internal, same API): setAllTags(putAll) → construction copy() for the true clone-at-birth copy-down; add constant span fields (spanType, integration name) to the prototype so afterStart fully collapses.

Design notes

🤖 Generated with Claude Code

…n only)

SpanPrototype is a baked-once, frozen descriptor of a span's constant initial
tags — the per-decorator constants afterStart stamps one entry at a time.
BaseDecorator.prototype() composes them across the hierarchy via a
buildPrototype(TagMap) contribute-chain (mirroring the afterStart super-chain,
but run once at bake) and caches lazily (same static-init caution as
componentEntry()).

Purely additive: afterStart is untouched and prototype() is not yet wired into
span creation — this is just the provider surface + the bake. The setAllTags
seed hook (and gating afterStart's constants behind prototype != NONE) is the
next increment. Rides the existing TagMap API, so it's independent of the
deeper TagMap rework.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dougqh dougqh added the tag: no release notes Changes to exclude from release notes label Jul 1, 2026
* scope — the concept earns its extensibility by being simple and well-placed, not by pre-built
* slots.
*/
public final class SpanPrototype {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm debating the shape of the SpanPrototype API.
I think what I want is a builder like API which has the notion of extension.

So something like...
SpanPrototype.builder().
extends_(baseProto).
initKind(...).
initTag(key, value).
initTag(entry).
build();

As much as possible, I don't want instrumentation devs to see TagMap or TagMap.Entry directly.

…xposed)

Per review: instrumentation authors should never see TagMap/TagMap.Entry. Moves
SpanPrototype to bootstrap.instrumentation.api (next to Tags) and replaces the
buildPrototype(TagMap) contribute-chain — which leaked TagMap to decorators —
with a builder:

  SpanPrototype.builder()
    .extends_(base)                 // inherit a SpanType base's identity + tags
    .instrumentationName(...).operationName(...).spanType(...)
    .initKind(...).initComponent(...).initTag(key, value)
    .initTag(entry)                 // advanced/internal escape (cached/metric entries)
    .build();

SpanPrototype now carries span identity (instrumentation name / operation name /
span type) alongside the frozen constant TagMap, matching the "prototype
generates the span" model. Concrete class (no abstract/impl split); frozen
internal TagMap (no TagMap.Prototype for now). BaseDecorator composes via a
prototypeBuilder() super-chain (TagMap-free); extends_ is the primitive for the
explicit SpanType-base pattern, not yet used by the class-hierarchy bake.

Still additive: afterStart untouched, prototype() dormant. SpanPrototypeTest 4/4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dougqh dougqh force-pushed the dougqh/span-prototype branch from c14c643 to a630809 Compare July 1, 2026 19:51
@datadog-datadog-prod-us1

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

Copy link
Copy Markdown
Contributor

🎯 Code Coverage (details)
Patch Coverage: 24.56%
Overall Coverage: 54.94% (-2.02%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 54b4386 | Docs | Datadog PR Page | Give us feedback!

@dd-octo-sts

dd-octo-sts Bot commented Jul 1, 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.84 s 14.56 s [+0.9%; +2.9%] (maybe worse)
startup:insecure-bank:tracing:Agent 13.59 s 13.71 s [-1.8%; -0.0%] (maybe better)
startup:petclinic:appsec:Agent 17.50 s 17.26 s [+0.4%; +2.4%] (maybe worse)
startup:petclinic:iast:Agent 16.64 s 17.40 s [-8.5%; -0.2%] (maybe better)
startup:petclinic:profiling:Agent 17.33 s 17.39 s [-1.5%; +0.8%] (no difference)
startup:petclinic:sca:Agent 17.44 s 17.33 s [-0.3%; +1.6%] (no difference)
startup:petclinic:tracing:Agent 16.16 s 16.66 s [-7.5%; +1.5%] (no difference)

Commit: 54b43863 · 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.

Isolates the SpanPrototype mechanism's value — the constant-tag application a span
pays at start — three ways, holding the tag set identical:
- oldPerSpanStamps: fresh map + N set(entry) (what afterStart does per span)
- newBulkApply: fresh map + one putAll of the baked prototype
- newConstructionSeed: copy() of the frozen prototype (clone-at-birth; previews
  the construction-seed increment 2 unlocks)

The SpanPrototype counterpart to the extractor-side per-mechanism benchmarks
(TagProjectionBenchmark, DatabaseClientConnectionBenchmark). @fork(3) @threads(8),
run with -prof gc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tag: no release notes Changes to exclude from release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant