Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
30aee55
Give the dense store's write paths one reference store
dougqh Oct 17, 2026
8c906a4
Presize a span's dense store from a per-operation sizing hint
dougqh Oct 17, 2026
44115d2
Seed a span prototype's known tags in bulk
dougqh Oct 17, 2026
8a0ab35
Keep the sizing hint on the dense store, not the span
dougqh Oct 17, 2026
ef5dadc
Size prototype spans from hints cached on the prototype
dougqh Oct 18, 2026
8423cac
Add singleSpanBuilder(SpanPrototype)
dougqh Oct 18, 2026
1b538a3
Overwrite a prototype's known tags in bulk after start
dougqh Oct 18, 2026
94faf01
Address techdebt and perf review findings
dougqh Oct 18, 2026
d25edde
Make the stores' forEach public and seed custom prototype tags from them
dougqh Oct 18, 2026
7c4fbdc
Merge tagmap-split-stores (master's #12882 slot coloring) into protot…
dougqh Oct 19, 2026
f79ebde
Merge tagmap-split-stores (contract tests) into prototype-fast-path
dougqh Oct 19, 2026
a02601f
Merge commit '24a64df710' into dougqh/prototype-fast-path
dougqh Oct 20, 2026
619db97
Merge branch 'dougqh/tagmap-split-stores' into dougqh/prototype-fast-…
dougqh Oct 20, 2026
129b43e
Merge branch 'dougqh/tagmap-split-stores' into dougqh/prototype-fast-…
dougqh Oct 20, 2026
01b727d
Merge branch 'dougqh/tagmap-split-stores' into dougqh/prototype-fast-…
dougqh Oct 20, 2026
0c628d3
Turn the dense tag store on by default
dougqh Oct 20, 2026
bc9e38d
Merge branch 'dougqh/tagmap-split-stores' into dougqh/prototype-fast-…
dougqh Oct 21, 2026
bc1f444
Feed the sizing hints in TraceShapeBenchmark, as serialization would
dougqh Oct 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,40 @@ public void webServerSpanViaPrototype() {
span.finish();
}

/**
* Web-server-shaped span the way decorators build it today: started by name, then the decorator's
* prototype stamped over it after start ({@code BaseDecorator.afterStart} -> {@code
* applyOverwriting}). The baseline the construction-time prototype paths have to beat.
*/
@Benchmark
public void webServerSpanRetrofit() {
AgentSpan span = tracer.startSpan(INSTRUMENTATION_NAME, SERVER_OPERATION_NAME);
span.applyOverwriting(webProto);
span.setTag(Tags.HTTP_METHOD, HTTP_METHOD_VALUE);
span.setTag(Tags.HTTP_ROUTE, HTTP_ROUTE_VALUE);
span.setTag(Tags.HTTP_URL, HTTP_URL_VALUE);
span.setTag(Tags.HTTP_STATUS, HTTP_STATUS_VALUE);
span.setTag(Tags.PEER_PORT, PEER_PORT_VALUE);
span.finish();
}

/**
* JDBC-client-shaped span the way decorators build it today: started by name, then the prototype
* stamped over it after start; see {@link #webServerSpanRetrofit}.
*/
@Benchmark
public void jdbcClientSpanRetrofit() {
AgentSpan span = tracer.startSpan(INSTRUMENTATION_NAME, JDBC_OPERATION_NAME);
span.applyOverwriting(jdbcProto);
span.setTag(Tags.DB_INSTANCE, DB_INSTANCE_VALUE);
span.setTag(Tags.DB_USER, DB_USER_VALUE);
span.setTag(Tags.DB_OPERATION, DB_OPERATION_VALUE);
span.setTag(Tags.DB_STATEMENT, DB_STATEMENT_VALUE);
span.setTag(Tags.PEER_HOSTNAME, DB_PEER_HOSTNAME_VALUE);
span.setTag(Tags.PEER_PORT, DB_PEER_PORT_VALUE);
span.finish();
}

/**
* JDBC/DB-client-shaped span via {@link SpanPrototype}: component, span.kind, and db.type ride
* the prototype; the dynamic db.* / peer.* tags are set per-span. Compare against {@link
Expand All @@ -272,6 +306,23 @@ public void jdbcClientSpanViaPrototype() {
span.finish();
}

/**
* JDBC-client-shaped span via {@code singleSpanBuilder(SpanPrototype, ...)}: the thread's
* reusable builder carries the prototype. Compare against {@link #jdbcClientSpanViaPrototype} (a
* fresh multi-span builder) and {@link #jdbcClientSpanViaPrototypeStartSpan} (no builder).
*/
@Benchmark
public void jdbcClientSpanViaPrototypeSingleSpanBuilder() {
AgentSpan span = tracer.singleSpanBuilder(jdbcProto, null).start();
span.setTag(Tags.DB_INSTANCE, DB_INSTANCE_VALUE);
span.setTag(Tags.DB_USER, DB_USER_VALUE);
span.setTag(Tags.DB_OPERATION, DB_OPERATION_VALUE);
span.setTag(Tags.DB_STATEMENT, DB_STATEMENT_VALUE);
span.setTag(Tags.PEER_HOSTNAME, DB_PEER_HOSTNAME_VALUE);
span.setTag(Tags.PEER_PORT, DB_PEER_PORT_VALUE);
span.finish();
}

/**
* Web-server-shaped span via {@code startSpan(SpanPrototype, ...)} — the builder-free
* construction entry (no MultiSpanBuilder allocation), the auto-instrumentation path. Compare
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import datadog.trace.bootstrap.instrumentation.api.SpanPrototype;
import datadog.trace.bootstrap.instrumentation.api.Tags;
import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString;
import datadog.trace.common.writer.Writer;
import java.util.List;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
Expand Down Expand Up @@ -44,7 +46,8 @@
* outside {@code Integer}'s box cache, so primitive boxing shows up.
*
* <p>This copy: 1.68 snapshot (decorators apply a prototype in {@code afterStart}, #12037; peer
* tags by id, #12715).
* tags by id, #12715), with the dense store's per-operation sizing hints, which the writer feeds as
* serialization would.
*/
@State(Scope.Benchmark)
@Warmup(iterations = 5)
Expand Down Expand Up @@ -103,7 +106,7 @@ public class TraceShapeBenchmark {

@Setup
public void setup(Blackhole blackhole) {
this.tracer = CoreTracer.builder().writer(new DropWriter(blackhole)).build();
this.tracer = CoreTracer.builder().writer(new SizeRecordingWriter(blackhole)).build();
// the decorators' prototype chains: Base (type, component) -> Server / Client (kind, language)
this.serverProto =
SpanPrototype.builder()
Expand Down Expand Up @@ -267,6 +270,41 @@ public void newApi() {
server.finish();
}

/**
* {@link DropWriter}, except that it first records each span's dense-store size, as serialization
* does for a published trace. Without it the per-operation sizing hints never learn here, and
* every span would be presized to the initial hint and then grow.
*/
static final class SizeRecordingWriter implements Writer {
private final Blackhole blackhole;

SizeRecordingWriter(Blackhole blackhole) {
this.blackhole = blackhole;
}

@Override
public void write(List<DDSpan> trace) {
for (DDSpan span : trace) {
span.spanContext().recordDenseSize();
}
blackhole.consume(trace);
}

@Override
public void start() {}

@Override
public boolean flush() {
return true;
}

@Override
public void close() {}

@Override
public void incrementDropCounts(int spanCount) {}
}

/** The advice's exit: activate around the call, then close and finish. */
void finishScoped(AgentSpan span) {
ContextScope scope = tracer.activateSpan(span);
Expand Down
51 changes: 47 additions & 4 deletions dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import datadog.trace.api.IdGenerationStrategy;
import datadog.trace.api.InstrumenterConfig;
import datadog.trace.api.Pair;
import datadog.trace.api.SizingHint;
import datadog.trace.api.TagMap;
import datadog.trace.api.TraceConfig;
import datadog.trace.api.civisibility.config.BazelMode;
Expand Down Expand Up @@ -1160,8 +1161,26 @@ public CoreSpanBuilder buildSpan(
if (operationName == null) {
operationName = prototype.operationName();
}
CoreSpanBuilder builder =
createMultiSpanBuilder(prototype.instrumentationName(), operationName);
return seedFrom(
createMultiSpanBuilder(prototype.instrumentationName(), operationName), prototype);
}

/**
* Like {@link #buildSpan(SpanPrototype, CharSequence)}, for one span only: the thread's reusable
* builder carries the prototype (see {@link #singleSpanBuilder(String, CharSequence)}), which it
* drops when reset for the next span.
*/
@Override
public CoreSpanBuilder singleSpanBuilder(
@Nonnull final SpanPrototype prototype, CharSequence operationName) {
if (operationName == null) {
operationName = prototype.operationName();
}
return seedFrom(singleSpanBuilder(prototype.instrumentationName(), operationName), prototype);
}

/** Seeds {@code builder}'s prototype and span type; its tags are seeded when the span starts. */
private static CoreSpanBuilder seedFrom(CoreSpanBuilder builder, SpanPrototype prototype) {
builder.spanPrototype = prototype;
if (prototype.spanType() != null) {
builder.spanType = prototype.spanType();
Expand Down Expand Up @@ -1442,8 +1461,9 @@ void write(final SpanList trace) {
writtenTrace.get(i).setTag(APM_ENABLED, 0);
}
}
writer.write(writtenTrace);
writer.write(writtenTrace); // serialization records these spans' sizes
} else {
recordDenseSizes(writtenTrace);
// with span streaming this won't work - it needs to be changed
// to track an effective sampling rate instead, however, tests
// checking that a hard reference on a continuation prevents
Expand All @@ -1455,6 +1475,19 @@ void write(final SpanList trace) {
}
}

/**
* Tunes each span's per-operation sizing hint, for a trace that is dropped rather than written:
* published spans are recorded when they're serialized, with every tag present. Done here rather
* than at span finish: the early post-processors and the root's sampling decision add tags after
* a span finishes, and a hint that missed them would leave every later span of the operation
* growing its store.
*/
private static void recordDenseSizes(List<DDSpan> trace) {
for (int i = 0; i < trace.size(); i++) {
trace.get(i).spanContext().recordDenseSize();
}
}

private List<DDSpan> interceptCompleteTrace(SpanList originalTrace) {
return interceptCompleteTrace(interceptors, originalTrace);
}
Expand Down Expand Up @@ -2373,6 +2406,15 @@ protected static final DDSpanContext buildSpanContext(
requestContextDataIast = builderRequestContextDataIast;
}

// Per-operation dense-store sizing: an entry (local-root) span carries tags a child doesn't,
// so the lane is chosen by whether there's a local parent. A span using its prototype's own
// operation name gets the hint cached on the prototype; any other span (including NONE's,
// which has no operation name) gets its operation's hint from the table. Null with the dense
// store off -- the flag test folds, so the default path does no lookup.
final SizingHint sizingHint =
spanPrototype.sizingHint(
!(resolvedParentSpanContext instanceof DDSpanContext), operationName);

// some attributes are inherited from the parent
context =
new DDSpanContext(
Expand Down Expand Up @@ -2400,7 +2442,8 @@ protected static final DDSpanContext buildSpanContext(
tracer.profilingContextIntegration,
tracer.injectBaggageAsTags,
tracer.injectLinksAsTags,
tagParent);
tagParent,
sizingHint);

// By setting the tags on the context we apply decorators to any tags that have been set via
// the builder. The `mergedTracerTags` are always applied first (the precedence floor:
Expand Down
Loading
Loading