Introduce SpanPrototype: baked-once constant span-tag descriptor#11828
Introduce SpanPrototype: baked-once constant span-tag descriptor#11828dougqh wants to merge 3 commits into
Conversation
…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>
| * scope — the concept earns its extensibility by being simple and well-placed, not by pre-built | ||
| * slots. | ||
| */ | ||
| public final class SpanPrototype { |
There was a problem hiding this comment.
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>
c14c643 to
a630809
Compare
|
🎯 Code Coverage (details) 🔗 Commit SHA: 54b4386 | Docs | Datadog PR Page | Give us feedback! |
🟢 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. |
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>
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
SpanPrototypeis a baked-once, frozen descriptor of a span's constant initial tags — the per-decorator constants (component/span.kind/language/ analytics rate) thatBaseDecorator.afterStartotherwise 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 NsetTagcalls collapse. It's the biggest per-span structural CPU lever in the decorator layer, and it doubles as a pit of success: writingbuildPrototype(tags)is the sametags.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 frozenTagMapholder (of/tags/NONE). Rides the existingTagMapAPI, 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 ascomponentEntry()).buildPrototype(TagMap)contribute-chain: mirrors theafterStartsuper-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.afterStartis byte-for-byte unchanged andprototype()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)
startSpan(SpanPrototype, …)/buildSpan(SpanPrototype, …)(additive overloads; existing signatures delegate withSpanPrototype.NONE, so no call-site sweep). Internally the prototype threads through the builder exactly liketagLedgerand is applied atbuildSpanContextright afterdefaultSpanTagsviasetAllTags— layered under per-spanwithTags.afterStart's constant stamping is gated off (decorator-ownedstartSpanskips it) for migrated integrations;NONEkeeps everyone else on today's path. One server integration migrated + micro-measured.setAllTags(putAll)→ constructioncopy()for the true clone-at-birth copy-down; add constant span fields (spanType, integration name) to the prototype soafterStartfully collapses.Design notes
🤖 Generated with Claude Code