Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 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
0d8077c
Have the span own its tag stores directly
dougqh Oct 23, 2026
ec5c752
Add the dense id store behind an experimental flag
dougqh Oct 23, 2026
1427599
Allocate FlatHashtable tables with an array constructor, not reflection
dougqh Oct 26, 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
162124d
Presize dense tag stores and seed span prototypes in bulk
dougqh Oct 23, 2026
44cd41f
Take the span's tag lock once in setTagsFrom
dougqh Oct 23, 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
3d082ef
Merge branch 'dougqh/tagmap-store-facade' into dougqh/tagmap-split-kn…
dougqh Oct 26, 2026
53a6056
Merge branch 'dougqh/tagmap-split-known-custom' into dougqh/tagmap-de…
dougqh Oct 26, 2026
285c42c
Merge branch 'dougqh/tagmap-dense-id-store' into dougqh/prototype-fas…
dougqh Oct 26, 2026
da492d0
Merge branch 'dougqh/prototype-fast-path' into dougqh/settagsfrom-loc…
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
7b75d24
Merge branch 'dougqh/tagmap-store-facade' into dougqh/tagmap-split-kn…
dougqh Oct 26, 2026
7e69d2c
Merge branch 'dougqh/tagmap-split-known-custom' into dougqh/tagmap-de…
dougqh Oct 26, 2026
6490f9f
Merge branch 'dougqh/tagmap-dense-id-store' into dougqh/prototype-fas…
dougqh Oct 26, 2026
dffd5a3
Merge branch 'dougqh/prototype-fast-path' into dougqh/settagsfrom-loc…
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
070b75b
Merge branch 'dougqh/tagmap-store-facade' into dougqh/tagmap-split-kn…
dougqh Oct 26, 2026
3e9de0d
Merge branch 'dougqh/tagmap-split-known-custom' into dougqh/tagmap-de…
dougqh Oct 26, 2026
d302913
Merge branch 'dougqh/tagmap-dense-id-store' into dougqh/prototype-fas…
dougqh Oct 26, 2026
6b80d7e
Merge branch 'dougqh/prototype-fast-path' into dougqh/settagsfrom-loc…
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
1cdf1c9
Merge branch 'dougqh/tagmap-store-facade' into dougqh/tagmap-split-kn…
dougqh Oct 26, 2026
f9d9fdf
Merge branch 'dougqh/tagmap-split-known-custom' into dougqh/tagmap-de…
dougqh Oct 26, 2026
66d1433
Merge branch 'dougqh/tagmap-dense-id-store' into dougqh/prototype-fas…
dougqh Oct 26, 2026
aef01ed
Merge branch 'dougqh/prototype-fast-path' into dougqh/settagsfrom-loc…
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
ad4afa5
Merge branch 'dougqh/tagmap-store-facade' into dougqh/tagmap-split-kn…
dougqh Oct 26, 2026
174a26b
Merge branch 'dougqh/tagmap-split-known-custom' into dougqh/tagmap-de…
dougqh Oct 26, 2026
d28c182
Merge branch 'dougqh/tagmap-dense-id-store' into dougqh/prototype-fas…
dougqh Oct 26, 2026
0a160a8
Merge branch 'dougqh/prototype-fast-path' into dougqh/settagsfrom-loc…
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
aabae45
Merge branch 'dougqh/tagmap-store-facade' into dougqh/tagmap-split-kn…
dougqh Oct 27, 2026
b06629d
Merge branch 'dougqh/tagmap-split-known-custom' into dougqh/tagmap-de…
dougqh Oct 27, 2026
5f4b059
Restore HashSupport.toInternalString's Javadoc
dougqh Oct 27, 2026
09c8cb6
Merge branch 'dougqh/tagmap-dense-id-store' into dougqh/prototype-fas…
dougqh Oct 27, 2026
e2722b7
Merge branch 'dougqh/prototype-fast-path' into dougqh/settagsfrom-loc…
dougqh Oct 27, 2026
93dd84d
Merge branch 'dougqh/tagmap-store-classes' into dougqh/tagmap-store-f…
dougqh Oct 27, 2026
271f997
Merge branch 'dougqh/tagmap-store-facade' into dougqh/tagmap-split-kn…
dougqh Oct 27, 2026
b26ad08
Merge branch 'dougqh/tagmap-split-known-custom' into dougqh/tagmap-de…
dougqh Oct 27, 2026
83b9541
Merge branch 'dougqh/tagmap-dense-id-store' into dougqh/prototype-fas…
dougqh Oct 27, 2026
aec3323
Merge branch 'dougqh/prototype-fast-path' into dougqh/settagsfrom-loc…
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
5920fe6
Merge branch 'dougqh/tagmap-store-facade' into dougqh/tagmap-split-kn…
dougqh Oct 27, 2026
0aba9cb
Merge branch 'dougqh/tagmap-split-known-custom' into dougqh/tagmap-de…
dougqh Oct 27, 2026
fdc5ce3
Merge branch 'dougqh/tagmap-dense-id-store' into dougqh/prototype-fas…
dougqh Oct 27, 2026
0d2a9e0
Merge branch 'dougqh/prototype-fast-path' into dougqh/settagsfrom-loc…
dougqh Oct 27, 2026
6c0e91c
Extract peer connection tags with a TagExtractor
dougqh Oct 30, 2026
16ecf44
Add FrozenTags, a cached form of setTagsFrom
dougqh Oct 30, 2026
7f0c4f7
Freeze netty 4.1 client peer tags once per connection
dougqh Oct 30, 2026
cf67e59
Copy the non-overlapping frozen tags in bulk, overwrite the rest
dougqh Oct 30, 2026
5d1f878
Freeze Kafka consumer tags once per topic per poll
dougqh Oct 30, 2026
3ddcf66
Replay frozen tags in write order and keep peer DNS outside the tag lock
dougqh Oct 30, 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 @@ -209,6 +209,21 @@ object KnownTagsEmitter {
"""
};

/**
* Each tag's id, indexed by serial; slot 0 (no tag) is 0. Lets a store that keeps only the
* 16-bit serial recover the full id, flag and slot bits included.
*/
static final long[] IDS_BY_SERIAL = {
0L,
""".trimIndent()
)
for (t in reg.tags.sortedBy { it.serial }) {
appendLine(" ${idC(t.ddName)},")
}
appendLine(
"""
};

/**
* The registry's name↔id tables, as a {@link KnownTagCodec.Resolver}. {@code KnownTagCodec}
* reads this field from its own holder, so the two classes complete each other: the codec owns
Expand All @@ -221,6 +236,11 @@ object KnownTagsEmitter {
int serial = KnownTagCodec.serialNum(tagId);
return serial < NAMES_BY_SERIAL.length ? NAMES_BY_SERIAL[serial] : null;
}

@Override
public long idOf(int serial) {
return IDS_BY_SERIAL[serial];
}

// openTelemetryNameOf: id -> OTel-namespace name on spans of a direction, null when
// the tag has none there. The caller (a serializer) owns any fall-back-to-Datadog-name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ class TagRegistryGeneratorPluginTest {
public final class KnownTagCodec {
public interface Resolver {
String nameOf(long id);
long idOf(int serial);
String openTelemetryNameOf(long id, int direction);
long keyOf(String name);
long directionalKeyOf(String name, int direction);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package datadog.trace.bootstrap.instrumentation.decorator;

import static datadog.trace.bootstrap.instrumentation.java.net.HostNameResolver.hostName;

import datadog.appsec.api.blocking.BlockingException;
import datadog.context.Context;
import datadog.context.ContextScope;
Expand All @@ -15,8 +13,6 @@
import datadog.trace.bootstrap.instrumentation.api.ErrorPriorities;
import datadog.trace.bootstrap.instrumentation.api.SpanPrototype;
import java.lang.reflect.Method;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.concurrent.ExecutionException;
Expand Down Expand Up @@ -194,11 +190,14 @@ protected void doOnError(final AgentSpan span, final Throwable throwable, byte e
throwable instanceof ExecutionException ? throwable.getCause() : throwable, errorPriority);
}

// The peer extractors write straight to the span rather than through setTagsFrom: resolving the
// hostname can mean a reverse DNS lookup, which mustn't run under the span's tag lock. To skip
// the lookup per span, freeze the extraction once per address instead (FrozenTags).

public final void onPeerConnection(
final AgentSpan span, @Nullable final InetSocketAddress remoteConnection) {
if (remoteConnection != null) {
onPeerConnection(span, remoteConnection.getAddress(), !remoteConnection.isUnresolved());
setPeerPort(span, remoteConnection.getPort());
PeerSocketAddress.INSTANCE.extract(remoteConnection, span);
}
}

Expand All @@ -210,15 +209,7 @@ public final void onPeerConnection(
public final void onPeerConnection(
AgentSpan span, @Nullable InetAddress remoteAddress, boolean resolved) {
if (remoteAddress != null) {
String ip = remoteAddress.getHostAddress();
if (resolved && Config.get().isPeerHostNameEnabled()) {
span.setTag(KnownTags.PEER_HOSTNAME_ID, hostName(remoteAddress, ip));
}
if (remoteAddress instanceof Inet4Address) {
span.setTag(KnownTags.PEER_IPV4_ID, ip);
} else if (remoteAddress instanceof Inet6Address) {
span.setTag(KnownTags.PEER_IPV6_ID, ip);
}
(resolved ? PeerAddress.RESOLVED : PeerAddress.UNRESOLVED).extract(remoteAddress, span);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package datadog.trace.bootstrap.instrumentation.decorator;

import static datadog.trace.bootstrap.instrumentation.java.net.HostNameResolver.hostName;

import datadog.trace.api.Config;
import datadog.trace.api.KnownTags;
import datadog.trace.bootstrap.instrumentation.api.SpanData;
import datadog.trace.bootstrap.instrumentation.api.TagExtractor;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;

/**
* Extracts the peer tags of a remote {@link InetAddress}: its IP, as {@code peer.ipv4} or {@code
* peer.ipv6}, and, when the address was resolved and peer hostnames are enabled, {@code
* peer.hostname}.
*
* <p>Resolving the hostname may do a reverse DNS lookup, so don't apply this with {@code
* span.setTagsFrom(address, extractor)}, which holds the span's tag lock while it runs: call {@code
* extract} on the span directly, or freeze it once per address ({@code FrozenTags.of}).
*/
public final class PeerAddress implements TagExtractor<InetAddress> {
/** For an address that was resolved from a hostname. */
public static final PeerAddress RESOLVED = new PeerAddress(true);

/** For an address that wasn't resolved: no {@code peer.hostname}. */
public static final PeerAddress UNRESOLVED = new PeerAddress(false);

private final boolean resolved;

private PeerAddress(boolean resolved) {
this.resolved = resolved;
}

@Override
public void extract(InetAddress address, SpanData span) {
String ip = address.getHostAddress();
if (resolved && Config.get().isPeerHostNameEnabled()) {
span.setTag(KnownTags.PEER_HOSTNAME_ID, hostName(address, ip));
}
if (address instanceof Inet4Address) {
span.setTag(KnownTags.PEER_IPV4_ID, ip);
} else if (address instanceof Inet6Address) {
span.setTag(KnownTags.PEER_IPV6_ID, ip);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package datadog.trace.bootstrap.instrumentation.decorator;

import datadog.trace.api.KnownTags;
import datadog.trace.bootstrap.instrumentation.api.SpanData;
import datadog.trace.bootstrap.instrumentation.api.TagExtractor;
import java.net.InetAddress;
import java.net.InetSocketAddress;

/**
* Extracts the peer tags of a remote {@link InetSocketAddress}: those of its address (see {@link
* PeerAddress}), when it has one, and {@code peer.port}, when set.
*/
public final class PeerSocketAddress implements TagExtractor<InetSocketAddress> {
public static final PeerSocketAddress INSTANCE = new PeerSocketAddress();

private PeerSocketAddress() {}

@Override
public void extract(InetSocketAddress socketAddress, SpanData span) {
// an unresolved socket address is exactly one with no address
InetAddress address = socketAddress.getAddress();
if (address != null) {
PeerAddress.RESOLVED.extract(address, span);
}
int port = socketAddress.getPort();
if (port > BaseDecorator.UNSET_PORT) {
span.setTag(KnownTags.PEER_PORT_ID, port);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import datadog.trace.api.naming.SpanNaming;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes;
import datadog.trace.bootstrap.instrumentation.api.SpanData;
import datadog.trace.bootstrap.instrumentation.api.TagExtractor;
import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString;
import datadog.trace.bootstrap.instrumentation.api.http.OtelClientUrl;
Expand Down Expand Up @@ -73,7 +74,7 @@ public CharSequence operationName(@Nonnull final String protocol) {

private static final class FullUrl implements TagExtractor<URI> {
@Override
public void extract(URI uri, AgentSpan span) {
public void extract(URI uri, SpanData span) {
// URLConnection historically records the original URL, including its query and fragment.
span.setTag(KnownTags.HTTP_URL_ID, uri.toString());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package datadog.trace.bootstrap.instrumentation.decorator;

import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;

import datadog.trace.api.KnownTags;
import datadog.trace.bootstrap.instrumentation.api.SpanData;
import datadog.trace.test.junit.utils.config.WithConfig;
import datadog.trace.test.junit.utils.config.WithConfigExtension;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

// registered for the class so the one method-level @WithConfig is undone after that method
@ExtendWith(WithConfigExtension.class)
class PeerAddressTest {
private final SpanData span = mock(SpanData.class);

// getByAddress pairs a hostname with an IP without a DNS lookup
private static InetAddress ipv4() throws Exception {
return InetAddress.getByAddress("db.example", new byte[] {10, 0, 0, 1});
}

private static InetAddress ipv6() throws Exception {
byte[] ip = new byte[16];
ip[15] = 1;
return InetAddress.getByAddress("db6.example", ip);
}

@Test
void resolvedIpv4SetsHostnameAndIpv4() throws Exception {
PeerAddress.RESOLVED.extract(ipv4(), span);

verify(span).setTag(KnownTags.PEER_HOSTNAME_ID, (CharSequence) "db.example");
verify(span).setTag(KnownTags.PEER_IPV4_ID, (CharSequence) "10.0.0.1");
verifyNoMoreInteractions(span);
}

@Test
void resolvedIpv6SetsHostnameAndIpv6() throws Exception {
InetAddress address = ipv6();
PeerAddress.RESOLVED.extract(address, span);

verify(span).setTag(KnownTags.PEER_HOSTNAME_ID, (CharSequence) "db6.example");
verify(span).setTag(KnownTags.PEER_IPV6_ID, (CharSequence) address.getHostAddress());
verifyNoMoreInteractions(span);
}

@Test
void unresolvedSkipsHostname() throws Exception {
PeerAddress.UNRESOLVED.extract(ipv4(), span);

verify(span).setTag(KnownTags.PEER_IPV4_ID, (CharSequence) "10.0.0.1");
verifyNoMoreInteractions(span);
}

@Test
@WithConfig(key = "trace.peer.hostname.enabled", value = "false")
void hostnameDisabledSkipsHostname() throws Exception {
PeerAddress.RESOLVED.extract(ipv4(), span);

verify(span).setTag(KnownTags.PEER_IPV4_ID, (CharSequence) "10.0.0.1");
verifyNoMoreInteractions(span);
}

@Test
void socketAddressAddsPort() throws Exception {
PeerSocketAddress.INSTANCE.extract(new InetSocketAddress(ipv4(), 5432), span);

verify(span).setTag(KnownTags.PEER_HOSTNAME_ID, (CharSequence) "db.example");
verify(span).setTag(KnownTags.PEER_IPV4_ID, (CharSequence) "10.0.0.1");
verify(span).setTag(KnownTags.PEER_PORT_ID, 5432);
verifyNoMoreInteractions(span);
}

@Test
void unresolvedSocketAddressSetsOnlyPort() {
PeerSocketAddress.INSTANCE.extract(
InetSocketAddress.createUnresolved("bad.address.local", 999), span);

verify(span).setTag(KnownTags.PEER_PORT_ID, 999);
verifyNoMoreInteractions(span);
}

@Test
void unsetPortIsSkipped() throws Exception {
PeerSocketAddress.INSTANCE.extract(new InetSocketAddress(ipv4(), 0), span);

verify(span, never()).setTag(eq(KnownTags.PEER_PORT_ID), anyInt());
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package datadog.trace.bootstrap.instrumentation.decorator;

import static datadog.trace.api.config.GeneralConfig.TRACE_OTEL_SEMANTICS_ENABLED;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
Expand All @@ -24,7 +24,8 @@ void datadogUrlConnectionKeepsOriginalUrl() {

UrlConnectionDecorator.DECORATE.onURI(span, URI.create(url));

verify(span).setTag(KnownTags.HTTP_URL_ID, url);
verify(span, never()).setTag(eq(KnownTags.HTTP_QUERY_STRING_ID), anyString());
// the URL extractors set tags through SpanData, whose id setters take a CharSequence
verify(span).setTag(KnownTags.HTTP_URL_ID, (CharSequence) url);
verify(span, never()).setTag(eq(KnownTags.HTTP_QUERY_STRING_ID), any(CharSequence.class));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public String[] helperClassNames() {
packageName + ".TextMapInjectAdapterInterface",
packageName + ".KafkaConsumerInfo",
packageName + ".KafkaConsumerInstrumentationHelper",
packageName + ".KafkaConsumerTags",
packageName + ".KafkaDecorator",
packageName + ".TextMapExtractAdapter",
packageName + ".TracingIterableDelegator",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package datadog.trace.instrumentation.kafka_clients;

import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.CONSUMER_GROUP;
import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.KAFKA_BOOTSTRAP_SERVERS;
import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.KAFKA_CLUSTER_ID;
import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.MESSAGING_DESTINATION_NAME;

import datadog.trace.bootstrap.instrumentation.api.FrozenTags;
import datadog.trace.bootstrap.instrumentation.api.SpanData;
import datadog.trace.bootstrap.instrumentation.api.TagExtractor;
import org.apache.kafka.clients.consumer.ConsumerRecord;

/**
* The consumer tags every record of one topic in one poll shares -- destination, group, cluster id
* and bootstrap servers -- extracted from the topic and frozen once, then applied to each record's
* span. Records come out of a poll partition by partition, so the topic rarely changes between
* records and one cached topic suffices. Used by one iterator, on one thread.
*/
public final class KafkaConsumerTags implements TagExtractor<String> {
private final String group;
private final String clusterId;
private final String bootstrapServers;

private String frozenTopic;
private FrozenTags frozen;

public KafkaConsumerTags(String group, String clusterId, String bootstrapServers) {
this.group = group;
this.clusterId = clusterId;
this.bootstrapServers = bootstrapServers;
}

/** The frozen tags for {@code record}'s topic, frozen when the topic changes. */
public FrozenTags of(final ConsumerRecord<?, ?> record) {
final String topic = KafkaDecorator.topicOf(record);
if (topic != frozenTopic && !topic.equals(frozenTopic)) {
frozen = FrozenTags.of(topic, this);
frozenTopic = topic;
}
return frozen;
}

@Override
public void extract(final String topic, final SpanData span) {
span.setTag(MESSAGING_DESTINATION_NAME, topic);
if (group != null) {
span.setTag(CONSUMER_GROUP, group);
}
if (clusterId != null) {
span.setTag(KAFKA_CLUSTER_ID, clusterId);
}
if (bootstrapServers != null) {
span.setTag(KAFKA_BOOTSTRAP_SERVERS, bootstrapServers);
}
}
}
Loading
Loading