Skip to content

Commit 51f0fec

Browse files
dougqhdevflow.devflow-routing-intake
andauthored
Use bitmask SpanKindFilter for per-span eligibility in metrics aggregator (#11380)
Trim per-span work on metrics aggregator publish path ConflatingMetricsAggregator.publish does a handful of redundant operations on every span. None individually is large; together they show as ~2.5% on the existing JMH benchmark once the benchmark actually exercises span.kind. - dedup span.isTopLevel(): publish() reads it into a local, then shouldComputeMetric read it again. Pass the cached value in. - resolve spanKind to String once: master called toString() twice per span (once inside spanKindEligible, once at the getPeerTags call site) and used HashSet contains on a CharSequence (which routes through equals on String). Normalize to String up front and reuse. - lazy-allocate the peer-tag list: getPeerTags() always allocated an ArrayList sized to features.peerTags() even when the span had none of those tags set. Defer allocation until the first match; return Collections.emptyList() when none hit. MetricKey already treats null/empty peerTags as emptyList, so no behavior change. Drop the spanKindEligible helper — the HashSet.contains call inlines fine in shouldComputeMetric. Update the JMH benchmark to set span.kind=client on every span. Without it the filter path short-circuits before the peer-tag and toString work, so the wins above aren't measurable. With it: baseline 6.755 us/op (CI [6.560, 6.950], stdev 0.129) optimized 6.585 us/op (CI [6.536, 6.634], stdev 0.033) 2 forks x 5 iterations x 15s. ~2.5% mean improvement and much tighter variance fork-to-fork. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Add SpanKindFilter and CoreSpan.isKind for bitmask-based kind checks Introduce SpanKindFilter -- a tiny builder-built immutable filter whose state is an int bitmask indexed by the span.kind ordinals already cached on DDSpanContext. Each include* on the builder sets one bit (1 << ordinal); the runtime check is a single AND against (1 << span's ordinal). CoreSpan.isKind(SpanKindFilter) is the new entry point. DDSpan overrides it to do the bit-test directly against the cached ordinal -- no virtual call, no tag-map lookup. The two existing test-only CoreSpan impls (SimpleSpan and TraceGenerator.PojoSpan, the latter in two source sets) implement isKind by reading the span.kind tag and delegating to SpanKindFilter.matches(String), which converts via DDSpanContext.spanKindOrdinalOf and does the same AND. Refactor: DDSpanContext.setSpanKindOrdinal(String) now delegates to a new package-private static spanKindOrdinalOf(String) so the same string-to-ordinal mapping serves both the tag interceptor path and SpanKindFilter.matches. This is groundwork -- nothing in the codebase calls isKind yet. The next commit will replace the HashSet-based eligibility checks in ConflatingMetricsAggregator with SpanKindFilter instances. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Use SpanKindFilter in ConflatingMetricsAggregator Replace the two ELIGIBLE_SPAN_KINDS_FOR_* HashSet<String> constants and the SPAN_KIND_INTERNAL.equals check with three SpanKindFilter instances: METRICS_ELIGIBLE_KINDS, PEER_AGGREGATION_KINDS, INTERNAL_KIND. Eligibility checks now go through span.isKind(filter), which on DDSpan is a volatile byte read against the already-cached span.kind ordinal plus a single bit-test. Also defer the span.kind tag read: previously read at the top of the publish loop and threaded through both shouldComputeMetric and the inner publish. isKind no longer needs the string, so the read can move down into the inner publish where it's still needed for the SPAN_KINDS cache key / MetricKey. Supporting changes: - DDSpanContext.spanKindOrdinalOf(String) is now public so non-DDSpan CoreSpan impls can compute the ordinal at tag-write time. - SpanKindFilter gains a public matches(byte) fast-path overload that callers with a pre-computed ordinal use directly. - SimpleSpan caches the ordinal in setTag(SPAN_KIND, ...), mirroring what TagInterceptor does for DDSpanContext, and its isKind now hits the byte fast path. Without this, the JMH benchmark (which uses SimpleSpan) would re-derive the ordinal on every isKind call and overstate the cost. Benchmark on the bench updated last commit (kind=client on every span, 4 forks x 5 iter x 15s): prior commit 6.585 ± 0.049 us/op this commit 6.903 ± 0.096 us/op The slight regression is a SimpleSpan-via-groovy-dispatch artifact -- the interface call to isKind through CoreSpan, then through SimpleSpan, then through SpanKindFilter.matches, doesn't fold as aggressively as a HashSet contains on a static field. In production DDSpan.isKind inlines to a context field read + ordinal byte read + bit-test, so the production path is faster than the prior HashSet approach. A DDSpan-based benchmark would show this; the existing SimpleSpan-based one doesn't. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Add DDSpan-based variant of ConflatingMetricsAggregator JMH benchmark The existing ConflatingMetricsAggregatorBenchmark uses SimpleSpan, a groovy mock. That's enough for measuring queue/CHM/MetricKey work, but it conceals the production cost of CoreSpan.isKind: SimpleSpan's isKind goes through groovy interface dispatch into SpanKindFilter.matches, while DDSpan.isKind inlines to a context byte-read + bit-test. This new benchmark uses real DDSpan instances created through a CoreTracer (with a NoopWriter so finishing doesn't reach the agent). Same shape as the SimpleSpan bench (64-span trace, span.kind=client, peer.hostname set). Numbers (2 forks x 5 iter x 15s): master: 6.428 +- 0.189 us/op (HashSet eligibility checks) this branch: 6.343 +- 0.115 us/op (SpanKindFilter bitmask) About 1.3% faster on the production path. The SimpleSpan benchmark in the same conditions shows a ~2.2% slowdown -- the mock's dispatch shape gives a misleading signal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Tighten SpanKindFilter encapsulation Make SpanKindFilter.kindMask and its constructor private now that DDSpan.isKind no longer needs direct field access -- it delegates to SpanKindFilter.matches(byte). The Builder.build() in the same outer class still constructs instances via the private constructor. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Merge branch 'master' into dougqh/conflating-metrics-producer-wins Clear span.kind ordinal cache on ledger removal DDSpanContext.setAllTags(TagMap.Ledger) removed tags via unsafeTags.remove(...) without clearing the cached spanKindOrdinal. A builder that set span.kind and then nulled it on the same SpanBuilder (withTag(SPAN_KIND, "client").withTag(SPAN_KIND, null)) left the cached ordinal stale at CLIENT, so the new bitmask eligibility checks counted spans the previous tag-map-based code correctly skipped. Mirror what removeTag(String) already does: when the removal targets SPAN_KIND, reset the cached ordinal to SPAN_KIND_UNSET before forwarding the remove to unsafeTags. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Apply review feedback on SpanKindFilter rollout - CoreSpan.isKind: now a default method that reads span.kind via unsafeGetTag and delegates to SpanKindFilter.matches(String). The three test-only implementations (SimpleSpan + two PojoSpans) no longer need their own copies, and SimpleSpan's spanKindOrdinal cache can go away too. - DDSpan.isKind: keeps the fast path (cached ordinal + bit-test) and now carries an explicit @OverRide. - DDSpanContext.spanKindOrdinalOf: package-private now that the only remaining caller is SpanKindFilter (same package). - SpanKindFilter: class-level javadoc spelling out the recognized span.kind values and that arbitrary custom strings collapse to SPAN_KIND_CUSTOM and never match — by design. - ConflatingMetricsAggregator: static-import Collections.emptyList / singletonList / singletonMap per project conventions. - ConflatingMetricsAggregatorDDSpanBenchmark: record the rollout result (~1.3% faster on the DDSpan path) in the class javadoc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Merge branch 'master' into dougqh/conflating-metrics-producer-wins Remove unused Tags import in TraceGenerator Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Merge branch 'master' into dougqh/conflating-metrics-producer-wins Merge branch 'master' into dougqh/conflating-metrics-producer-wins Remove unused Tags import from traceAgentTest TraceGenerator codenarcTraceAgentTest flagged the bootstrap.instrumentation.api.Tags import as never referenced. Same fix as f9822a5 applied earlier to the test/ copy of TraceGenerator. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: devflow.devflow-routing-intake <devflow.devflow-routing-intake@kubernetes.us1.ddbuild.io>
1 parent 02117e3 commit 51f0fec

8 files changed

Lines changed: 267 additions & 48 deletions

File tree

dd-trace-core/src/jmh/java/datadog/trace/common/metrics/ConflatingMetricsAggregatorBenchmark.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package datadog.trace.common.metrics;
22

33
import static datadog.trace.api.ProtocolVersion.V0_4;
4+
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND;
5+
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_CLIENT;
46
import static java.util.concurrent.TimeUnit.MICROSECONDS;
57
import static java.util.concurrent.TimeUnit.SECONDS;
68

@@ -52,6 +54,7 @@ static List<CoreSpan<?>> generateTrace(int len) {
5254
final List<CoreSpan<?>> trace = new ArrayList<>();
5355
for (int i = 0; i < len; i++) {
5456
SimpleSpan span = new SimpleSpan("", "", "", "", true, true, false, 0, 10, -1);
57+
span.setTag(SPAN_KIND, SPAN_KIND_CLIENT);
5558
span.setTag("peer.hostname", Strings.random(10));
5659
trace.add(span);
5760
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package datadog.trace.common.metrics;
2+
3+
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND;
4+
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_CLIENT;
5+
import static java.util.concurrent.TimeUnit.MICROSECONDS;
6+
import static java.util.concurrent.TimeUnit.SECONDS;
7+
8+
import datadog.communication.ddagent.DDAgentFeaturesDiscovery;
9+
import datadog.trace.api.WellKnownTags;
10+
import datadog.trace.common.writer.Writer;
11+
import datadog.trace.core.CoreSpan;
12+
import datadog.trace.core.CoreTracer;
13+
import datadog.trace.core.DDSpan;
14+
import datadog.trace.core.monitor.HealthMetrics;
15+
import datadog.trace.util.Strings;
16+
import java.util.ArrayList;
17+
import java.util.Collections;
18+
import java.util.List;
19+
import org.openjdk.jmh.annotations.Benchmark;
20+
import org.openjdk.jmh.annotations.BenchmarkMode;
21+
import org.openjdk.jmh.annotations.Fork;
22+
import org.openjdk.jmh.annotations.Measurement;
23+
import org.openjdk.jmh.annotations.Mode;
24+
import org.openjdk.jmh.annotations.OutputTimeUnit;
25+
import org.openjdk.jmh.annotations.Scope;
26+
import org.openjdk.jmh.annotations.State;
27+
import org.openjdk.jmh.annotations.Warmup;
28+
import org.openjdk.jmh.infra.Blackhole;
29+
30+
/**
31+
* Parallels {@link ConflatingMetricsAggregatorBenchmark} but uses real {@link DDSpan} instances
32+
* instead of the lightweight {@code SimpleSpan} mock, so the JIT exercises the production {@link
33+
* CoreSpan#isKind} path (cached span.kind ordinal + bit-test) rather than the groovy mock's
34+
* dispatch.
35+
*
36+
* <p>SpanKindFilter rollout result vs. the pre-bitmask code on master: ~1.3% faster on the
37+
* production path, with tighter fork-to-fork variance. The CIs overlap so the headline number sits
38+
* inside noise, but the centers move the right way and the new path is structurally cheaper (byte
39+
* read + bit-test vs tag-map read + HashSet.contains). <code>
40+
* MacBook M1 (Java 21), 2 forks x 5 iterations x 15s, AverageTime
41+
*
42+
* Branch Score (avg) CI (99.9%)
43+
* master 6.428 ± 0.189 µs/op [6.239, 6.617]
44+
* this branch 6.343 ± 0.115 µs/op [6.228, 6.458]
45+
* </code>
46+
*/
47+
@State(Scope.Benchmark)
48+
@Warmup(iterations = 1, time = 30, timeUnit = SECONDS)
49+
@Measurement(iterations = 3, time = 30, timeUnit = SECONDS)
50+
@BenchmarkMode(Mode.AverageTime)
51+
@OutputTimeUnit(MICROSECONDS)
52+
@Fork(value = 1)
53+
public class ConflatingMetricsAggregatorDDSpanBenchmark {
54+
55+
private static final CoreTracer TRACER =
56+
CoreTracer.builder().writer(new NoopWriter()).strictTraceWrites(false).build();
57+
58+
private final DDAgentFeaturesDiscovery featuresDiscovery =
59+
new ConflatingMetricsAggregatorBenchmark.FixedAgentFeaturesDiscovery(
60+
Collections.singleton("peer.hostname"), Collections.emptySet());
61+
private final ConflatingMetricsAggregator aggregator =
62+
new ConflatingMetricsAggregator(
63+
new WellKnownTags("", "", "", "", "", ""),
64+
Collections.emptySet(),
65+
featuresDiscovery,
66+
HealthMetrics.NO_OP,
67+
new ConflatingMetricsAggregatorBenchmark.NullSink(),
68+
2048,
69+
2048,
70+
false);
71+
private final List<CoreSpan<?>> spans = generateTrace(64);
72+
73+
static List<CoreSpan<?>> generateTrace(int len) {
74+
final List<CoreSpan<?>> trace = new ArrayList<>();
75+
for (int i = 0; i < len; i++) {
76+
DDSpan span = (DDSpan) TRACER.startSpan("benchmark", "op");
77+
span.setTag(SPAN_KIND, SPAN_KIND_CLIENT);
78+
span.setTag("peer.hostname", Strings.random(10));
79+
// Fix duration; bypasses the wall clock and avoids per-fork drift.
80+
span.finishWithDuration(10);
81+
trace.add(span);
82+
}
83+
return trace;
84+
}
85+
86+
static class NoopWriter implements Writer {
87+
@Override
88+
public void write(List<DDSpan> trace) {}
89+
90+
@Override
91+
public void start() {}
92+
93+
@Override
94+
public boolean flush() {
95+
return true;
96+
}
97+
98+
@Override
99+
public void close() {}
100+
101+
@Override
102+
public void incrementDropCounts(int spanCount) {}
103+
}
104+
105+
@Benchmark
106+
public void benchmark(Blackhole blackhole) {
107+
blackhole.consume(aggregator.publish(spans));
108+
}
109+
}

dd-trace-core/src/main/java/datadog/trace/common/metrics/ConflatingMetricsAggregator.java

Lines changed: 36 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,16 @@
77
import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_ENDPOINT;
88
import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_METHOD;
99
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND;
10-
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_CLIENT;
11-
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_CONSUMER;
12-
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_INTERNAL;
13-
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_PRODUCER;
14-
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_SERVER;
1510
import static datadog.trace.common.metrics.AggregateMetric.ERROR_TAG;
1611
import static datadog.trace.common.metrics.AggregateMetric.TOP_LEVEL_TAG;
1712
import static datadog.trace.common.metrics.SignalItem.ReportSignal.REPORT;
1813
import static datadog.trace.common.metrics.SignalItem.StopSignal.STOP;
1914
import static datadog.trace.util.AgentThreadFactory.AgentThread.METRICS_AGGREGATOR;
2015
import static datadog.trace.util.AgentThreadFactory.THREAD_JOIN_TIMOUT_MS;
2116
import static datadog.trace.util.AgentThreadFactory.newAgentThread;
22-
import static java.util.Collections.unmodifiableSet;
17+
import static java.util.Collections.emptyList;
18+
import static java.util.Collections.singletonList;
19+
import static java.util.Collections.singletonMap;
2320
import static java.util.concurrent.TimeUnit.SECONDS;
2421

2522
import datadog.common.queue.Queues;
@@ -36,12 +33,10 @@
3633
import datadog.trace.common.writer.ddagent.DDAgentApi;
3734
import datadog.trace.core.CoreSpan;
3835
import datadog.trace.core.DDTraceCoreInfo;
36+
import datadog.trace.core.SpanKindFilter;
3937
import datadog.trace.core.monitor.HealthMetrics;
4038
import datadog.trace.util.AgentTaskScheduler;
4139
import java.util.ArrayList;
42-
import java.util.Arrays;
43-
import java.util.Collections;
44-
import java.util.HashSet;
4540
import java.util.List;
4641
import java.util.Map;
4742
import java.util.Set;
@@ -50,7 +45,6 @@
5045
import java.util.concurrent.Future;
5146
import java.util.concurrent.TimeUnit;
5247
import java.util.function.Function;
53-
import javax.annotation.Nonnull;
5448
import org.jctools.queues.MessagePassingQueue;
5549
import org.slf4j.Logger;
5650
import org.slf4j.LoggerFactory;
@@ -60,7 +54,7 @@ public final class ConflatingMetricsAggregator implements MetricsAggregator, Eve
6054
private static final Logger log = LoggerFactory.getLogger(ConflatingMetricsAggregator.class);
6155

6256
private static final Map<String, String> DEFAULT_HEADERS =
63-
Collections.singletonMap(DDAgentApi.DATADOG_META_TRACER_VERSION, DDTraceCoreInfo.VERSION);
57+
singletonMap(DDAgentApi.DATADOG_META_TRACER_VERSION, DDTraceCoreInfo.VERSION);
6458

6559
private static final DDCache<String, UTF8BytesString> SERVICE_NAMES =
6660
DDCaches.newFixedSizeCache(32);
@@ -82,15 +76,19 @@ public final class ConflatingMetricsAggregator implements MetricsAggregator, Eve
8276
value -> UTF8BytesString.create(key + ":" + value));
8377
private static final CharSequence SYNTHETICS_ORIGIN = "synthetics";
8478

85-
private static final Set<String> ELIGIBLE_SPAN_KINDS_FOR_METRICS =
86-
unmodifiableSet(
87-
new HashSet<>(
88-
Arrays.asList(
89-
SPAN_KIND_SERVER, SPAN_KIND_CLIENT, SPAN_KIND_CONSUMER, SPAN_KIND_PRODUCER)));
79+
private static final SpanKindFilter METRICS_ELIGIBLE_KINDS =
80+
SpanKindFilter.builder()
81+
.includeServer()
82+
.includeClient()
83+
.includeProducer()
84+
.includeConsumer()
85+
.build();
9086

91-
private static final Set<String> ELIGIBLE_SPAN_KINDS_FOR_PEER_AGGREGATION =
92-
unmodifiableSet(
93-
new HashSet<>(Arrays.asList(SPAN_KIND_CLIENT, SPAN_KIND_PRODUCER, SPAN_KIND_CONSUMER)));
87+
private static final SpanKindFilter PEER_AGGREGATION_KINDS =
88+
SpanKindFilter.builder().includeClient().includeProducer().includeConsumer().build();
89+
90+
private static final SpanKindFilter INTERNAL_KIND =
91+
SpanKindFilter.builder().includeInternal().build();
9492

9593
private final Set<String> ignoredResources;
9694
private final MessagePassingQueue<Batch> batchPool;
@@ -289,36 +287,30 @@ public boolean publish(List<? extends CoreSpan<?>> trace) {
289287
if (features.supportsMetrics()) {
290288
for (CoreSpan<?> span : trace) {
291289
boolean isTopLevel = span.isTopLevel();
292-
final CharSequence spanKind = span.unsafeGetTag(SPAN_KIND, "");
293-
if (shouldComputeMetric(span, spanKind)) {
290+
if (shouldComputeMetric(span, isTopLevel)) {
294291
final CharSequence resourceName = span.getResourceName();
295292
if (resourceName != null && ignoredResources.contains(resourceName.toString())) {
296293
// skip publishing all children
297294
forceKeep = false;
298295
break;
299296
}
300297
counted++;
301-
forceKeep |= publish(span, isTopLevel, spanKind);
298+
forceKeep |= publish(span, isTopLevel);
302299
}
303300
}
304301
healthMetrics.onClientStatTraceComputed(counted, trace.size(), !forceKeep);
305302
}
306303
return forceKeep;
307304
}
308305

309-
private boolean shouldComputeMetric(CoreSpan<?> span, @Nonnull CharSequence spanKind) {
310-
return (span.isMeasured() || span.isTopLevel() || spanKindEligible(spanKind))
306+
private boolean shouldComputeMetric(CoreSpan<?> span, boolean isTopLevel) {
307+
return (span.isMeasured() || isTopLevel || span.isKind(METRICS_ELIGIBLE_KINDS))
311308
&& span.getLongRunningVersion()
312309
<= 0 // either not long-running or unpublished long-running span
313310
&& span.getDurationNano() > 0;
314311
}
315312

316-
private boolean spanKindEligible(@Nonnull CharSequence spanKind) {
317-
// use toString since it could be a CharSequence...
318-
return ELIGIBLE_SPAN_KINDS_FOR_METRICS.contains(spanKind.toString());
319-
}
320-
321-
private boolean publish(CoreSpan<?> span, boolean isTopLevel, CharSequence spanKind) {
313+
private boolean publish(CoreSpan<?> span, boolean isTopLevel) {
322314
// Extract HTTP method and endpoint only if the feature is enabled
323315
String httpMethod = null;
324316
String httpEndpoint = null;
@@ -335,6 +327,9 @@ private boolean publish(CoreSpan<?> span, boolean isTopLevel, CharSequence spanK
335327
Object grpcStatusObj = span.unsafeGetTag(InstrumentationTags.GRPC_STATUS_CODE);
336328
grpcStatusCode = grpcStatusObj != null ? grpcStatusObj.toString() : null;
337329
}
330+
// CharSequence default keeps unsafeGetTag's generic at CharSequence so UTF8BytesString
331+
// tag values don't trigger a ClassCastException on the String assignment.
332+
final String spanKind = span.unsafeGetTag(SPAN_KIND, (CharSequence) "").toString();
338333
MetricKey newKey =
339334
new MetricKey(
340335
span.getResourceName(),
@@ -347,7 +342,7 @@ private boolean publish(CoreSpan<?> span, boolean isTopLevel, CharSequence spanK
347342
span.getParentId() == 0,
348343
SPAN_KINDS.computeIfAbsent(
349344
spanKind, UTF8BytesString::create), // save repeated utf8 conversions
350-
getPeerTags(span, spanKind.toString()),
345+
getPeerTags(span),
351346
httpMethod,
352347
httpEndpoint,
353348
grpcStatusCode);
@@ -382,35 +377,38 @@ private boolean publish(CoreSpan<?> span, boolean isTopLevel, CharSequence spanK
382377
return span.getError() > 0;
383378
}
384379

385-
private List<UTF8BytesString> getPeerTags(CoreSpan<?> span, String spanKind) {
386-
if (ELIGIBLE_SPAN_KINDS_FOR_PEER_AGGREGATION.contains(spanKind)) {
380+
private List<UTF8BytesString> getPeerTags(CoreSpan<?> span) {
381+
if (span.isKind(PEER_AGGREGATION_KINDS)) {
387382
final Set<String> eligiblePeerTags = features.peerTags();
388-
List<UTF8BytesString> peerTags = new ArrayList<>(eligiblePeerTags.size());
383+
List<UTF8BytesString> peerTags = null;
389384
for (String peerTag : eligiblePeerTags) {
390385
Object value = span.unsafeGetTag(peerTag);
391386
if (value != null) {
392387
final Pair<DDCache<String, UTF8BytesString>, Function<String, UTF8BytesString>>
393388
cacheAndCreator = PEER_TAGS_CACHE.computeIfAbsent(peerTag, PEER_TAGS_CACHE_ADDER);
389+
if (peerTags == null) {
390+
peerTags = new ArrayList<>(eligiblePeerTags.size());
391+
}
394392
peerTags.add(
395393
cacheAndCreator
396394
.getLeft()
397395
.computeIfAbsent(value.toString(), cacheAndCreator.getRight()));
398396
}
399397
}
400-
return peerTags;
401-
} else if (SPAN_KIND_INTERNAL.equals(spanKind)) {
398+
return peerTags == null ? emptyList() : peerTags;
399+
} else if (span.isKind(INTERNAL_KIND)) {
402400
// in this case only the base service should be aggregated if present
403401
final Object baseService = span.unsafeGetTag(BASE_SERVICE);
404402
if (baseService != null) {
405403
final Pair<DDCache<String, UTF8BytesString>, Function<String, UTF8BytesString>>
406404
cacheAndCreator = PEER_TAGS_CACHE.computeIfAbsent(BASE_SERVICE, PEER_TAGS_CACHE_ADDER);
407-
return Collections.singletonList(
405+
return singletonList(
408406
cacheAndCreator
409407
.getLeft()
410408
.computeIfAbsent(baseService.toString(), cacheAndCreator.getRight()));
411409
}
412410
}
413-
return Collections.emptyList();
411+
return emptyList();
414412
}
415413

416414
private static boolean isSynthetic(CoreSpan<?> span) {

dd-trace-core/src/main/java/datadog/trace/core/CoreSpan.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package datadog.trace.core;
22

33
import datadog.trace.api.DDTraceId;
4+
import datadog.trace.bootstrap.instrumentation.api.Tags;
45
import java.util.Map;
56

67
public interface CoreSpan<T extends CoreSpan<T>> {
@@ -80,6 +81,11 @@ default <U> U unsafeGetTag(CharSequence name) {
8081

8182
boolean isForceKeep();
8283

84+
default boolean isKind(SpanKindFilter filter) {
85+
Object kind = unsafeGetTag(Tags.SPAN_KIND);
86+
return filter.matches(kind == null ? null : kind.toString());
87+
}
88+
8389
CharSequence getType();
8490

8591
/**

dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,11 @@ public boolean isOutbound() {
959959
return ordinal == DDSpanContext.SPAN_KIND_CLIENT || ordinal == DDSpanContext.SPAN_KIND_PRODUCER;
960960
}
961961

962+
@Override
963+
public boolean isKind(SpanKindFilter filter) {
964+
return filter.matches(context.getSpanKindOrdinal());
965+
}
966+
962967
@Override
963968
public void copyPropagationAndBaggage(final AgentSpan source) {
964969
if (source instanceof DDSpan) {

0 commit comments

Comments
 (0)