Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -3,6 +3,7 @@
import static java.util.concurrent.TimeUnit.MICROSECONDS;

import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.SpanPrototype;
import datadog.trace.bootstrap.instrumentation.api.Tags;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
Expand Down Expand Up @@ -42,7 +43,10 @@
* five-method {@code Writer} interface (implemented as a no-op {@link DropWriter}). If you add to
* it, keep it inside that stable surface or grafting it onto old tags for the historical curve will
* stop compiling. (Source rebuilds only reach ~v1.53 — older tags hit dead build-time dependencies;
* deeper history is a published-jar job.)
* deeper history is a published-jar job.) The {@code *ViaPrototype} / {@code
* *ViaPrototypeStartSpan} arms are the exception: they exercise {@link SpanPrototype} (new in this
* PR) and do not graft onto pre-SpanPrototype tags — the historical table above covers the baseline
* arms only.
*
* <p>Spans are finished against {@link DropWriter} so the create/tag/finish allocation is isolated
* from serialization and agent I/O — those live on a different lever and would otherwise leak into
Expand Down Expand Up @@ -132,11 +136,33 @@ public class SpanCreationBenchmark {

CoreTracer tracer;

// Baked-once prototypes carrying only the type-constant subset each baseline sets individually
// (component + span.kind; jdbc also db.type). The dynamic tags are set per-span in both arms, so
// the *ViaPrototype vs *Span delta isolates the construction-path seeding of just those
// constants.
SpanPrototype webProto;
Comment thread
mcculls marked this conversation as resolved.
SpanPrototype jdbcProto;

@Setup
public void setup(Blackhole blackhole) {
// DropWriter keeps finish() from pulling in serialization / agent I/O, so -prof gc reflects
// span creation + tagging + PendingTrace completion only.
this.tracer = CoreTracer.builder().writer(new DropWriter(blackhole)).build();
this.webProto =
SpanPrototype.builder()
.initInstrumentationName(INSTRUMENTATION_NAME)
.initOperationName(SERVER_OPERATION_NAME)
.initComponentOnly(COMPONENT_VALUE)
.initKind(Tags.SPAN_KIND_SERVER)
.build();
this.jdbcProto =
SpanPrototype.builder()
.initInstrumentationName(INSTRUMENTATION_NAME)
.initOperationName(JDBC_OPERATION_NAME)
.initComponentOnly(DB_COMPONENT_VALUE)
.initKind(Tags.SPAN_KIND_CLIENT)
.initTag(Tags.DB_TYPE, DB_TYPE_VALUE)
.build();
}

@TearDown
Expand Down Expand Up @@ -211,4 +237,69 @@ public void jdbcClientSpan() {
span.setTag(Tags.PEER_PORT, DB_PEER_PORT_VALUE);
span.finish();
}

/**
* Web-server-shaped span via {@link SpanPrototype}: the type-constants (component, span.kind)
* ride a baked-once prototype seeded at construction; the dynamic http.* / peer.port tags are set
* per-span, as real instrumentation does. Compare against {@link #webServerSpan} (identical tags,
* all set individually) to read the prototype's construction-path win on a full span.
*/
@Benchmark
public void webServerSpanViaPrototype() {
AgentSpan span = tracer.buildSpan(webProto, null).start(); // null -> prototype's operationName
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/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
* #jdbcClientSpan}.
*/
@Benchmark
public void jdbcClientSpanViaPrototype() {
AgentSpan span = tracer.buildSpan(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
* against {@link #webServerSpanViaPrototype} (same prototype, but {@code buildSpan(...).start()}
* allocates a builder) to read the builder-free saving, and against {@link #webServerSpan} for
* the full win.
*/
@Benchmark
public void webServerSpanViaPrototypeStartSpan() {
AgentSpan span = tracer.startSpan(webProto, null); // null -> prototype's operationName
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/DB-client-shaped span via the builder-free {@code startSpan(SpanPrototype, ...)}. */
@Benchmark
public void jdbcClientSpanViaPrototypeStartSpan() {
AgentSpan span = tracer.startSpan(jdbcProto, null);
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();
}
}
75 changes: 75 additions & 0 deletions dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
import datadog.trace.bootstrap.instrumentation.api.ProfilingContextIntegration;
import datadog.trace.bootstrap.instrumentation.api.SpanAttributes;
import datadog.trace.bootstrap.instrumentation.api.SpanLink;
import datadog.trace.bootstrap.instrumentation.api.SpanPrototype;
import datadog.trace.bootstrap.instrumentation.api.TagContext;
import datadog.trace.bootstrap.instrumentation.api.Tags;
import datadog.trace.civisibility.interceptor.CiVisibilityApmProtocolInterceptor;
Expand Down Expand Up @@ -133,6 +134,7 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.zip.ZipOutputStream;
import javax.annotation.Nonnull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -1077,6 +1079,27 @@ public CoreSpanBuilder buildSpan(
return createMultiSpanBuilder(instrumentationName, operationName);
}

/**
* Seeds identity (instrumentation name, operation, span type) and constant tags from a prototype.
* {@code operationName} overrides the prototype's when non-null — the explicit value wins, the
* prototype is the fallback. The prototype's tags are seeded during {@link CoreSpanBuilder}
* construction just before the builder's own tags, so explicit tags override prototype constants.
*/
@Override
public CoreSpanBuilder buildSpan(
@Nonnull final SpanPrototype prototype, CharSequence operationName) {
if (operationName == null) {
operationName = prototype.operationName();
}
CoreSpanBuilder builder =
createMultiSpanBuilder(prototype.instrumentationName(), operationName);
Comment thread
dougqh marked this conversation as resolved.
builder.spanPrototype = prototype;
Comment thread
mcculls marked this conversation as resolved.
if (prototype.spanType() != null) {
builder.spanType = prototype.spanType();
}
Comment thread
mcculls marked this conversation as resolved.
return builder;
}

MultiSpanBuilder createMultiSpanBuilder(
final String instrumentationName, final CharSequence operationName) {
return new MultiSpanBuilder(this, instrumentationName, operationName);
Expand Down Expand Up @@ -1182,6 +1205,17 @@ public AgentSpan startSpan(
this, instrumentationName, spanName, parent, CoreSpanBuilder.IGNORE_SCOPE, startTimeMicros);
}

@Override
public AgentSpan startSpan(
@Nonnull final SpanPrototype prototype, final CharSequence operationName) {
return CoreSpanBuilder.startSpan(
this,
prototype,
operationName != null ? operationName : prototype.operationName(),
CoreSpanBuilder.USE_SCOPE,
CoreSpanBuilder.AUTO_ASSIGN_TIMESTAMP);
}

@Override
public AgentScope activateSpan(AgentSpan span) {
return scopeManager.activateSpan(span);
Expand Down Expand Up @@ -1622,6 +1656,7 @@ public abstract static class CoreSpanBuilder implements AgentTracer.SpanBuilder
// Builder attributes
// Make sure any fields added here are also reset properly in ReusableSingleSpanBuilder.reset
protected TagMap.Ledger tagLedger;
protected SpanPrototype spanPrototype = SpanPrototype.NONE;
protected long timestampMicro;
protected AgentSpanContext parent;
protected String serviceName;
Expand Down Expand Up @@ -1660,6 +1695,7 @@ protected static final DDSpan buildSpan(
boolean errorFlag,
CharSequence spanType,
TagMap.Ledger tagLedger,
SpanPrototype spanPrototype,
List<AgentSpanLink> links,
Object builderRequestContextDataAppSec,
Object builderRequestContextDataIast,
Expand All @@ -1679,6 +1715,7 @@ protected static final DDSpan buildSpan(
errorFlag,
spanType,
tagLedger,
spanPrototype,
links,
builderRequestContextDataAppSec,
builderRequestContextDataIast,
Expand Down Expand Up @@ -1764,6 +1801,7 @@ protected AgentSpan startImpl() {
this.errorFlag,
this.spanType,
this.tagLedger,
this.spanPrototype,
this.links,
this.builderRequestContextDataAppSec,
this.builderRequestContextDataIast,
Expand All @@ -1790,6 +1828,33 @@ protected static final AgentSpan startSpan(
false /* errorFlag */,
null /* spanType */,
null /* tagLedger */,
SpanPrototype.NONE /* spanPrototype */,
null /* links */,
null /* appSec */,
null /* iast */,
null /* ciViz */);
}

protected static final AgentSpan startSpan(
final CoreTracer tracer,
final SpanPrototype prototype,
final CharSequence operationName,
final boolean ignoreScope,
final long timestampMicros) {
return startSpan(
tracer,
AUTO_ASSIGN_SPAN_ID,
prototype.instrumentationName(),
timestampMicros,
null /* serviceName */,
operationName,
null /* resourceName */,
null /* specifiedParentSpanContext */,
ignoreScope,
false /* errorFlag */,
prototype.spanType(),
null /* tagLedger */,
prototype,
null /* links */,
null /* appSec */,
null /* iast */,
Expand All @@ -1809,6 +1874,7 @@ protected static final AgentSpan startSpan(
boolean errorFlag,
CharSequence spanType,
TagMap.Ledger tagLedger,
SpanPrototype spanPrototype,
List<AgentSpanLink> links,
Object builderRequestContextDataAppSec,
Object builderRequestContextDataIast,
Expand Down Expand Up @@ -1871,6 +1937,7 @@ protected static final AgentSpan startSpan(
errorFlag,
spanType,
tagLedger,
spanPrototype,
links,
builderRequestContextDataAppSec,
builderRequestContextDataIast,
Expand Down Expand Up @@ -2009,6 +2076,7 @@ protected static final DDSpanContext buildSpanContext(
boolean errorFlag,
CharSequence spanType,
TagMap.Ledger tagLedger,
SpanPrototype spanPrototype,
List<AgentSpanLink> links,
Object builderRequestContextDataAppSec,
Object builderRequestContextDataIast,
Expand Down Expand Up @@ -2256,6 +2324,12 @@ protected static final DDSpanContext buildSpanContext(
if (mergedTracerTagsNeedsIntercept) {
context.setAllTags(mergedTracerTags, true);
}
if (spanPrototype != SpanPrototype.NONE) {
// Seed the prototype's constant tags + integration name as fallback defaults (span type was
// already seeded onto the builder). apply never clobbers, so tags set below still win, and
// this is the same seam decorator afterStart uses.
context.apply(spanPrototype);
}
context.setAllTags(tagLedger);
context.setAllTags(coreTags, coreTagsNeedsIntercept);
context.setAllTags(rootSpanTags, rootSpanTagsNeedsIntercept);
Expand Down Expand Up @@ -2340,6 +2414,7 @@ final boolean reset(String instrumentationName, CharSequence operationName) {
this.operationName = operationName;

if (this.tagLedger != null) this.tagLedger.reset();
this.spanPrototype = SpanPrototype.NONE;
this.timestampMicro = 0L;
this.parent = null;
this.serviceName = null;
Expand Down
8 changes: 8 additions & 0 deletions dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import datadog.trace.bootstrap.instrumentation.api.AttachableWrapper;
import datadog.trace.bootstrap.instrumentation.api.ErrorPriorities;
import datadog.trace.bootstrap.instrumentation.api.ResourceNamePriorities;
import datadog.trace.bootstrap.instrumentation.api.SpanPrototype;
import datadog.trace.bootstrap.instrumentation.api.SpanWrapper;
import datadog.trace.core.util.StackTraces;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
Expand Down Expand Up @@ -671,6 +672,13 @@ public final DDSpan setSpanType(final CharSequence type) {
return this;
}

@Override
public void apply(@Nonnull final SpanPrototype prototype) {
// Route straight to the context (owner of the tag map + future fast path) rather than through
// the interface default's per-setter delegation.
context.apply(prototype);
}

// Getters

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import datadog.trace.bootstrap.instrumentation.api.ProfilerContext;
import datadog.trace.bootstrap.instrumentation.api.ProfilingContextIntegration;
import datadog.trace.bootstrap.instrumentation.api.ResourceNamePriorities;
import datadog.trace.bootstrap.instrumentation.api.SpanPrototype;
import datadog.trace.bootstrap.instrumentation.api.Tags;
import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString;
import datadog.trace.core.propagation.PropagationTags;
Expand Down Expand Up @@ -601,6 +602,61 @@ public void setSpanType(final CharSequence spanType) {
this.spanType = spanType;
}

/**
* Applies a {@link SpanPrototype} as fallback defaults: stamps its span type, constant tags, and
* integration name only where the span has not already set them. Prototype values are the lowest
* precedence -- anything explicitly set (a builder {@code withSpanType}, explicit tags, an
* earlier decorator) wins. Because it never clobbers, {@code apply} is order-independent and
* self-neutralizes once construction has already seeded the same prototype.
*
* <p>This is the shared seam for both the construction path ({@code CoreSpanBuilder}) and
* decorator {@code afterStart} (via {@link DDSpan#apply}). The context owns the tag map, so the
* eventual cheaper bulk-share path (skipping interception for non-intercepted tags) and the
* identity short-circuit will land here -- deferred to the dense-store / tag-registry work, which
* exposes intercept status at the internal-api level. Until then the constant tags route through
* the interceptor, identical to the per-tag calls this replaces.
*/
public void apply(@Nonnull final SpanPrototype prototype) {
if (this.spanType == null) {
final CharSequence spanType = prototype.spanType();
if (spanType != null) {
setSpanType(spanType);
}
}
seedAbsentTags(prototype.tags());
if (this.integrationName == null) {
final CharSequence integrationName = prototype.integrationName();
if (integrationName != null) {
setIntegrationName(integrationName);
}
}
}

/**
* Seeds tags that are not already present, routed through the interceptor. Mirrors {@link
* #setAllTags(TagMap, boolean)}'s intercepting path but skips any key already set, so explicit
* tags keep precedence over the prototype's constant defaults.
*/
private void seedAbsentTags(final TagMap map) {
if (map == null) {
return;
}
synchronized (unsafeTags) {
map.forEach(
this,
(ctx, tagEntry) -> {
final String tag = tagEntry.tag();
if (ctx.unsafeTags.containsKey(tag)) {
return;
}
final Object value = tagEntry.objectValue();
if (!ctx.tagInterceptor.interceptTag(ctx, tag, value)) {
ctx.unsafeTags.set(tagEntry);
}
});
}
}

/** Forces the local root span sampling decision to keep according manual mechanism. */
public void forceKeep() {
forceKeep(SamplingMechanism.MANUAL);
Expand Down Expand Up @@ -1391,6 +1447,7 @@ public void setIntegrationName(CharSequence integrationName) {
this.integrationName = integrationName;
}

@Override
public CharSequence getIntegrationName() {
return integrationName;
}
Expand Down
Loading