Skip to content
Draft
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
@@ -0,0 +1,91 @@
package datadog.trace.api;

import static java.util.concurrent.TimeUnit.SECONDS;

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;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;

/**
* Per-mechanism benchmark for {@link SpanPrototype}: the constant-tag application a span pays at
* start. Compares the three phases of the mechanism, holding the resulting tag set identical:
*
* <ul>
* <li><b>oldPerSpanStamps</b> — a fresh {@code TagMap} filled by N individual {@code set(entry)}
* calls, as {@code BaseDecorator.afterStart} does today (once per span).
* <li><b>newBulkApply</b> — a fresh map + one {@code putAll} of the baked-once prototype (the
* afterStart-consolidation on-ramp).
* <li><b>newConstructionSeed</b> — the span's map is <em>born</em> as a {@code copy()} of the
* frozen prototype (clone-at-birth; what increment 2's construction wiring unlocks).
* </ul>
*
* <p>Isolates the constant-application only (not span creation or the {@code afterStart} virtual
* chain), so the delta is purely N-stamps vs. bulk-copy. Run with {@code -prof gc} — the
* interesting axes are ops/s and B/op.
*/
@State(Scope.Thread)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(SECONDS)
@Warmup(iterations = 5, time = 2)
@Measurement(iterations = 5, time = 2)
@Fork(3)
@Threads(8)
public class SpanPrototypeBenchmark {

// The constant set a typical server span carries, as cached entries (the shared-Entry
// hand-optimization the decorators use today).
private static final TagMap.Entry COMPONENT = TagMap.Entry.create(Tags.COMPONENT, "netty");
private static final TagMap.Entry KIND =
TagMap.Entry.create(Tags.SPAN_KIND, Tags.SPAN_KIND_SERVER);
private static final TagMap.Entry LANGUAGE =
TagMap.Entry.create(DDTags.LANGUAGE_TAG_KEY, DDTags.LANGUAGE_TAG_VALUE);
private static final TagMap.Entry ANALYTICS =
TagMap.Entry.create(DDTags.ANALYTICS_SAMPLE_RATE, 1.0d);

private SpanPrototype prototype;

@Setup(Level.Trial)
public void setUp() {
// Baked once — the same constants, composed through the builder.
prototype =
SpanPrototype.builder()
.initComponent("netty")
.initKind(Tags.SPAN_KIND_SERVER)
.initTag(DDTags.LANGUAGE_TAG_KEY, DDTags.LANGUAGE_TAG_VALUE)
.initTag(ANALYTICS)
.build();
}

@Benchmark
public TagMap oldPerSpanStamps() {
TagMap tags = TagMap.create();
tags.set(COMPONENT);
tags.set(KIND);
tags.set(LANGUAGE);
tags.set(ANALYTICS);
return tags;
}

@Benchmark
public TagMap newBulkApply() {
TagMap tags = TagMap.create();
tags.putAll(prototype.tags());
return tags;
}

@Benchmark
public TagMap newConstructionSeed() {
return prototype.tags().copy();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package datadog.trace.bootstrap.instrumentation.api;

import datadog.trace.api.TagMap;

/**
* A baked-once, frozen descriptor of a span's constant initial state — the per-decorator constants
* (instrumentation name, span type, {@code span.kind}, component, …) that {@code
* BaseDecorator.afterStart} otherwise stamps one entry at a time, per span.
*
* <p>Composed through {@link #builder()}: authors set identity and constant tags via typed methods
* and never touch {@link TagMap} directly. {@link Builder#extends_(SpanPrototype)} inherits a base
* prototype (e.g. a SpanType base like {@code HttpServer}) so an integration adds only what's
* specific to it. Rides the existing {@code TagMap} API, so it's independent of any deeper TagMap
* rework — the internal seed can get faster without changing this surface.
*
* <p>v1 carries identity + constant tags. Derivation / canonicalization / lifecycle hooks are
* deliberately out — grown when the work that needs each arrives, not pre-slotted.
*/
public final class SpanPrototype {
/** The empty prototype — for spans created without a decorator-provided prototype. */
public static final SpanPrototype NONE = builder().build();

public static Builder builder() {
return new Builder();
}

private final String instrumentationName;
private final CharSequence operationName;
private final CharSequence spanType;
private final TagMap tags; // frozen

private SpanPrototype(final Builder builder) {
this.instrumentationName = builder.instrumentationName;
this.operationName = builder.operationName;
this.spanType = builder.spanType;
this.tags = builder.tags.immutableCopy();
}

public String instrumentationName() {
return instrumentationName;
}

public CharSequence operationName() {
return operationName;
}

public CharSequence spanType() {
return spanType;
}

/** The frozen constant tags — the internal seed applied at span construction. */
public TagMap tags() {
return tags;
}

public static final class Builder {
private String instrumentationName;
private CharSequence operationName;
private CharSequence spanType;
// Internal accumulator — never exposed; authors compose via the typed methods below.
private final TagMap tags = TagMap.create();

private Builder() {}

/**
* Inherit a base prototype's identity and constant tags (e.g. a SpanType base). Subsequent
* identity / {@code init*} calls on this builder override the inherited values.
*/
public Builder extends_(final SpanPrototype base) {
if (base != null) {
if (base.instrumentationName != null) {
this.instrumentationName = base.instrumentationName;
}
if (base.operationName != null) {
this.operationName = base.operationName;
}
if (base.spanType != null) {
this.spanType = base.spanType;
}
this.tags.putAll(base.tags);
}
return this;
}

public Builder instrumentationName(final String[] instrumentationNames) {
return (instrumentationNames == null || instrumentationNames.length == 0)
? this
: instrumentationName(instrumentationNames[0]);
}

public Builder instrumentationName(final String instrumentationName) {
this.instrumentationName = instrumentationName;
return this;
}

public Builder operationName(final CharSequence operationName) {
this.operationName = operationName;
return this;
}

public Builder spanType(final CharSequence spanType) {
this.spanType = spanType;
return this;
}

/** Sets {@code span.kind}. */
public Builder initKind(final CharSequence kind) {
return initTag(Tags.SPAN_KIND, kind);
}

/** Sets {@code component}. */
public Builder initComponent(final CharSequence component) {
return initTag(Tags.COMPONENT, component);
}

public Builder initTag(final String key, final CharSequence value) {
if (value != null) {
this.tags.set(key, value);
}
return this;
}

public Builder initTag(final String key, final Object value) {
if (value != null) {
this.tags.set(key, value);
}
return this;
}

/**
* Advanced/internal: reuse an already-built entry — a decorator's cached constant or a metric
* entry — rather than re-creating it. Authors should prefer the typed {@code init*} methods.
*/
public Builder initTag(final TagMap.EntryReader entry) {
if (entry != null) {
this.tags.set(entry);
}
return this;
}

public SpanPrototype build() {
return new SpanPrototype(this);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package datadog.trace.bootstrap.instrumentation.api;

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;

class SpanPrototypeTest {

@Test
void extendsInheritsBaseIdentityAndTagsThenOverrides() {
final SpanPrototype base =
SpanPrototype.builder()
.instrumentationName("base")
.spanType("base-type")
.initKind("server")
.build();
final SpanPrototype derived =
SpanPrototype.builder().extends_(base).initComponent("netty").spanType("http").build();

assertEquals("base", derived.instrumentationName()); // inherited
assertEquals("http", derived.spanType()); // overridden
assertEquals("server", derived.tags().getString(Tags.SPAN_KIND)); // inherited tag
assertEquals("netty", derived.tags().getString(Tags.COMPONENT)); // added tag
}
}
Loading