Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
eabce40
Bucket known tags by slot in TagMap
dougqh Oct 22, 2026
19b2e88
Add whole-span and whole-trace allocation benchmarks
dougqh Oct 22, 2026
6e65a31
Vary TraceShapeBenchmark's trace size; test bucketHash
dougqh Oct 22, 2026
d72b784
Merge branch 'master' into dougqh/tagmap-slot-buckets
dougqh Oct 22, 2026
2271e4f
Add a whole-request benchmark for the header code paths
dougqh Oct 22, 2026
fa9b441
Make a null tag value remove the tag in TagMap
dougqh Oct 23, 2026
7894411
Merge branch 'dougqh/tagmap-null-removes' into dougqh/tagmap-slot-buc…
dougqh Oct 23, 2026
8e57e87
Introduce SpanStorage for eager tag post-processors
dougqh Oct 23, 2026
d5a7f64
Extract TagMap's bucket mechanics into HashSupport
dougqh Oct 22, 2026
96c49d0
Add id and name tag stores, not yet used
dougqh Oct 23, 2026
8a02c1d
Make TagMap a facade over an id store and a name store
dougqh Oct 23, 2026
3c91e7f
Attach HeaderPathBenchmark's server span through the local tracer
dougqh Oct 26, 2026
ae4ec9a
Merge branch 'dougqh/tagmap-slot-buckets' into dougqh/eager-postproce…
dougqh Oct 26, 2026
926ab92
Merge branch 'dougqh/eager-postprocessors-span-context' into dougqh/t…
dougqh Oct 26, 2026
f1b60e4
Remove an earlier payload tag when a later value at its path is null
dougqh Oct 26, 2026
54ed705
Merge branch 'dougqh/tagmap-null-removes' into dougqh/tagmap-slot-buc…
dougqh Oct 26, 2026
e0b022c
Merge branch 'dougqh/tagmap-slot-buckets' into dougqh/eager-postproce…
dougqh Oct 26, 2026
d6dafcb
Merge branch 'dougqh/eager-postprocessors-span-context' into dougqh/t…
dougqh Oct 26, 2026
884c508
Load and store known tags by id through SpanStorage
dougqh Oct 26, 2026
590f6e7
Test that a known tag's id and name address the same tag in SpanStorage
dougqh Oct 26, 2026
cc74b8c
Merge branch 'dougqh/eager-postprocessors-span-context' into dougqh/t…
dougqh Oct 26, 2026
44aedf3
Merge branch 'dougqh/tagmap-hash-support' into dougqh/tagmap-store-cl…
dougqh Oct 26, 2026
6f247e6
Merge branch 'dougqh/tagmap-store-classes' into dougqh/tagmap-store-f…
dougqh Oct 26, 2026
52a5a5b
Load known tags by id from the id store, without an entry
dougqh Oct 26, 2026
afad98b
Load and store values in the tag stores, as through SpanStorage
dougqh Oct 26, 2026
5b0e1d9
Merge branch 'dougqh/tagmap-store-classes' into dougqh/tagmap-store-f…
dougqh Oct 26, 2026
38dc4b3
Rename SpanStorage to TagStore
dougqh Oct 26, 2026
13dd68f
Merge branch 'dougqh/eager-postprocessors-span-context' into dougqh/t…
dougqh Oct 26, 2026
4540183
Apply suggestion from @sarahchen6
dougqh Oct 26, 2026
7739065
Say why the span implements TagStore
dougqh Oct 26, 2026
08b0645
Merge branch 'dougqh/tagmap-null-removes' into dougqh/tagmap-slot-buc…
dougqh Oct 26, 2026
f5fa140
Merge branch 'dougqh/tagmap-slot-buckets' into dougqh/eager-postproce…
dougqh Oct 26, 2026
a0f2786
Merge branch 'master' into dougqh/tagmap-null-removes
dougqh Oct 26, 2026
0c73aff
Merge branch 'dougqh/tagmap-null-removes' into dougqh/tagmap-slot-buc…
dougqh Oct 26, 2026
933a688
Merge branch 'dougqh/tagmap-slot-buckets' into dougqh/eager-postproce…
dougqh Oct 26, 2026
36bbbd2
Test that urlOrPath answers the same from a TagStore as from a map
dougqh Oct 26, 2026
a647029
Merge branch 'dougqh/eager-postprocessors-span-context' into dougqh/t…
dougqh Oct 26, 2026
d6ecaba
Merge branch 'dougqh/tagmap-hash-support' into dougqh/tagmap-store-cl…
dougqh Oct 26, 2026
619ccbf
Merge branch 'dougqh/tagmap-store-classes' into dougqh/tagmap-store-f…
dougqh Oct 26, 2026
b9dbfa9
Name the post-processor chain's TagStore parameter unsafeTags
dougqh Oct 26, 2026
3eed220
Rename TagStoreTest's storage helper and locals
dougqh Oct 26, 2026
76d27dc
Merge branch 'dougqh/eager-postprocessors-span-context' into dougqh/t…
dougqh Oct 26, 2026
6c2bf20
Merge branch 'dougqh/tagmap-hash-support' into dougqh/tagmap-store-cl…
dougqh Oct 26, 2026
f2de8eb
Merge branch 'dougqh/tagmap-store-classes' into dougqh/tagmap-store-f…
dougqh Oct 26, 2026
f34d3df
Find a parent's shadowing entry through HashSupport.find
dougqh Oct 26, 2026
fefa3ea
Merge branch 'dougqh/tagmap-hash-support' into dougqh/tagmap-store-cl…
dougqh Oct 26, 2026
4dab7c1
Merge branch 'dougqh/tagmap-store-classes' into dougqh/tagmap-store-f…
dougqh Oct 26, 2026
d46e822
Keep the serializer's tag walk free of tombstone checks
dougqh Oct 27, 2026
a0e4c10
Make an id store's cursor its own bucket walker
dougqh Oct 27, 2026
99ca579
Fix the id store Javadoc's stale links
dougqh Oct 27, 2026
966e3a9
Test HashStore's read-through, removal and merge
dougqh Oct 27, 2026
7cfe207
Merge branch 'dougqh/tagmap-store-classes' into dougqh/tagmap-store-f…
dougqh Oct 27, 2026
5f4b059
Restore HashSupport.toInternalString's Javadoc
dougqh Oct 27, 2026
93dd84d
Merge branch 'dougqh/tagmap-store-classes' into dougqh/tagmap-store-f…
dougqh Oct 27, 2026
b6608e5
Cover the id store factory, the cursor's bucket walk and HashStore
dougqh Oct 27, 2026
faa1122
Merge branch 'dougqh/tagmap-store-classes' into dougqh/tagmap-store-f…
dougqh Oct 27, 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
@@ -0,0 +1,232 @@
package datadog.trace.core;

import static datadog.context.Context.root;
import static datadog.trace.bootstrap.instrumentation.api.AgentSpan.fromContext;
import static java.util.concurrent.TimeUnit.MICROSECONDS;

import datadog.context.Context;
import datadog.context.ContextScope;
import datadog.context.propagation.CarrierSetter;
import datadog.context.propagation.CarrierVisitor;
import datadog.context.propagation.Propagators;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext;
import datadog.trace.bootstrap.instrumentation.api.SpanPrototype;
import datadog.trace.bootstrap.instrumentation.api.Tags;
import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;

/**
* One whole request per op, through the header code paths: extract the inbound headers with the
* default propagator, start the server span from what was extracted, run a few children, make one
* outbound HTTP call that injects its headers, then finish everything.
*
* <p>The existing propagation benchmarks each measure one step (extracting into a context,
* injecting from a prebuilt one, parsing one header value). They can't see costs that the extracted
* state causes later, on every span of the trace: for example, a {@code tracestate} that leads each
* span to allocate sampling state. Measuring the whole request catches those.
*
* <p>{@code headers} selects the inbound headers, under the default propagation styles:
*
* <ul>
* <li>{@code none}: a new trace;
* <li>{@code datadog}: {@code x-datadog-*}, with propagated tags;
* <li>{@code tracecontext}: {@code traceparent} and a {@code tracestate} carrying {@code dd},
* OpenTelemetry ({@code ot}) and a third-party member;
* <li>{@code both}: Datadog and W3C headers for the same trace, as a Datadog-to-Datadog call
* sends them;
* <li>{@code bothBaggage}: {@code both}, plus a {@code baggage} header.
* </ul>
*/
@State(Scope.Benchmark)
@Warmup(iterations = 5)
@Measurement(iterations = 5)
@BenchmarkMode(Mode.Throughput)
@Threads(8)
@OutputTimeUnit(MICROSECONDS)
@Fork(value = 3, jvmArgsAppend = "-DTEST_LOG_LEVEL=warn")
public class HeaderPathBenchmark {
static final int JDBC_SPANS = 3;

static final String TRACE_ID_HEX = "66a0f6c800000000a3ce929d0e0e4736";
static final String TRACE_ID_LOW = "11803532876627986230"; // a3ce929d0e0e4736
static final String PARENT_ID = "67667974448284343"; // 00f067aa0ba902b7
static final String PARENT_ID_HEX = "00f067aa0ba902b7";

static final String SERVER_INSTRUMENTATION = "tomcat-server";
static final CharSequence SERVER_OPERATION = UTF8BytesString.create("servlet.request");
static final String JDBC_INSTRUMENTATION = "java-jdbc-prepared_statement";
static final CharSequence JDBC_OPERATION = UTF8BytesString.create("database.query");
static final String CLIENT_INSTRUMENTATION = "okhttp";
static final CharSequence CLIENT_OPERATION = UTF8BytesString.create("okhttp.request");

static final CarrierVisitor<Map<String, String>> GETTER =
new CarrierVisitor<Map<String, String>>() {
@Override
public void forEachKeyValue(
Map<String, String> carrier, BiConsumer<String, String> visitor) {
carrier.forEach(visitor);
}
};
static final CarrierSetter<Map<String, String>> SETTER = Map::put;

@Param({"none", "datadog", "tracecontext", "both", "bothBaggage"})
String headers;

CoreTracer tracer;
Map<String, String> inbound;
SpanPrototype serverProto;
SpanPrototype jdbcProto;
SpanPrototype clientProto;

/** One outbound carrier per thread, reused across requests. */
@State(Scope.Thread)
public static class Outbound {
final Map<String, String> headers = new HashMap<>();
}

@Setup
public void setup(Blackhole blackhole) {
this.tracer = CoreTracer.builder().writer(new DropWriter(blackhole)).build();
this.inbound = inboundHeaders(headers);
this.serverProto =
SpanPrototype.builder()
.initInstrumentationName(SERVER_INSTRUMENTATION)
.initOperationName(SERVER_OPERATION)
.initSpanType("web")
.initComponentAndIntegration("tomcat-server")
.initKind(Tags.SPAN_KIND_SERVER)
.initTag("language", "jvm")
.build();
this.jdbcProto =
SpanPrototype.builder()
.initInstrumentationName(JDBC_INSTRUMENTATION)
.initOperationName(JDBC_OPERATION)
.initSpanType("sql")
.initComponentAndIntegration("java-jdbc")
.initKind(Tags.SPAN_KIND_CLIENT)
.build();
this.clientProto =
SpanPrototype.builder()
.initInstrumentationName(CLIENT_INSTRUMENTATION)
.initOperationName(CLIENT_OPERATION)
.initSpanType("http")
.initComponentAndIntegration("okhttp")
.initKind(Tags.SPAN_KIND_CLIENT)
.build();
checkPropagation();
}

/**
* Fails fast if the headers aren't actually extracted and injected, or if the outbound call
* doesn't continue the extracted trace, either of which would measure something else.
*/
void checkPropagation() {
Context parent = Propagators.defaultPropagator().extract(root(), inbound, GETTER);
if (!headers.equals("none") && fromContext(parent) == null) {
throw new IllegalStateException("nothing extracted from " + headers + " headers");
}
Outbound outbound = new Outbound();
request(outbound);
String injectedTraceId = outbound.headers.get("x-datadog-trace-id");
if (injectedTraceId == null) {
throw new IllegalStateException("nothing injected: " + outbound.headers);
}
if (!headers.equals("none") && !injectedTraceId.equals(TRACE_ID_LOW)) {
throw new IllegalStateException(
"outbound call didn't continue the extracted trace: " + outbound.headers);
}
}

@TearDown
public void tearDown() {
this.tracer.close();
}

static Map<String, String> inboundHeaders(String style) {
Map<String, String> h = new HashMap<>();
h.put("host", "localhost:8080");
h.put("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Chrome/120.0");
h.put("accept", "application/json");
if (style.equals("datadog") || style.startsWith("both")) {
h.put("x-datadog-trace-id", TRACE_ID_LOW);
h.put("x-datadog-parent-id", PARENT_ID);
h.put("x-datadog-sampling-priority", "1");
h.put("x-datadog-tags", "_dd.p.dm=-0,_dd.p.tid=66a0f6c800000000");
}
if (style.equals("tracecontext") || style.startsWith("both")) {
h.put("traceparent", "00-" + TRACE_ID_HEX + "-" + PARENT_ID_HEX + "-01");
h.put(
"tracestate",
"dd=s:1;p:"
+ PARENT_ID_HEX
+ ";t.dm:-0;t.tid:66a0f6c800000000,"
+ "ot=th:8;rv:9b8233f7e3a151,rojo=00f067aa0ba902b7");
}
if (style.equals("bothBaggage")) {
h.put("baggage", "userId=alice,serverNode=DF%2028,isProduction=false");
}
return h;
}

@Benchmark
public void request(Outbound outbound) {
outbound.headers.clear();
// HttpServerDecorator.extract / startSpan
Context parent = Propagators.defaultPropagator().extract(root(), inbound, GETTER);
AgentSpan extractedSpan = fromContext(parent);
AgentSpanContext extracted = extractedSpan == null ? null : extractedSpan.spanContext();
AgentSpan server = tracer.startSpan(SERVER_INSTRUMENTATION, SERVER_OPERATION, extracted);
server.setMeasured(true);
server.applyOverwriting(serverProto);
server.setTag(Tags.HTTP_METHOD, "GET");
server.setTag(Tags.HTTP_URL, "http://localhost:8080/owners/42");
server.setTag(Tags.HTTP_ROUTE, "/owners/{ownerId}");
server.setTag(Tags.PEER_PORT, 54321);

try (ContextScope serverScope = tracer.attach(parent.with(server))) {
for (int i = 0; i < JDBC_SPANS; ++i) {
AgentSpan jdbc = tracer.startSpan(JDBC_INSTRUMENTATION, JDBC_OPERATION);
jdbc.applyOverwriting(jdbcProto);
jdbc.setTag(Tags.DB_TYPE, "postgresql");
jdbc.setTag(Tags.DB_INSTANCE, "petclinic");
jdbc.setTag(Tags.PEER_HOSTNAME, "db.internal");
jdbc.setResourceName("SELECT * FROM owners WHERE id = ?");
try (ContextScope s = tracer.activateSpan(jdbc)) {
// the statement executes
}
jdbc.finish();
}

// the outbound call: start the client span, inject its headers, finish
AgentSpan client = tracer.startSpan(CLIENT_INSTRUMENTATION, CLIENT_OPERATION);
client.applyOverwriting(clientProto);
client.setTag(Tags.HTTP_METHOD, "GET");
client.setTag(Tags.HTTP_URL, "http://inventory.internal/items/7");
client.setTag(Tags.PEER_HOSTNAME, "inventory.internal");
client.setTag(Tags.PEER_PORT, 8080);
Propagators.defaultPropagator()
.inject(tracer.currentContext().with(client), outbound.headers, SETTER);
client.setHttpStatusCode(200);
client.finish();
}
server.setHttpStatusCode(200);
server.finish();
}
}
Loading
Loading