Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
14f3422
Add InstrumenterConfig.isDataStreamsEnabled() accessor
ericfirth Aug 26, 2026
bc5b126
Add TargetSystem.DATA_STREAMS and InstrumenterModule.DataStreams base…
ericfirth Aug 28, 2026
ab45b03
Add isEnabled() test coverage for DataStreams
ericfirth Aug 28, 2026
d93cdd3
Fix test isolation bug in testDataStreamsIsEnabledWhenDataStreamsEnab…
ericfirth Aug 28, 2026
4b971cf
Remove unused AfterEach import from InstrumenterModuleTest
ericfirth Aug 28, 2026
e06d87a
Wire AgentInstaller.getEnabledSystems() to DATA_STREAMS target system
ericfirth Aug 28, 2026
88d3ced
Add SamplingMechanism.DATA_STREAMS constant
ericfirth Aug 28, 2026
d415510
Fix DATA_STREAMS case formatting to match DATA_JOBS style
ericfirth Aug 28, 2026
1f63ee4
Swap kafka-clients-0.11 instrumentations to InstrumenterModule.DataSt…
ericfirth Aug 28, 2026
9d6e647
Suppress APM billing for DSM-only Kafka produce spans
ericfirth Aug 28, 2026
0d7f5f8
Swap kafka-clients-3.8 instrumentations to InstrumenterModule.DataStr…
ericfirth Aug 28, 2026
9c85a52
Decouple DSM from APM tracing in kafka-clients-3.8 (produce and consu…
ericfirth Aug 28, 2026
d4c4904
chore: kafka-streams-0.11 DSM decoupling (base-class swap + suppression)
ericfirth Aug 28, 2026
4c77872
refactor: change kafka-streams-1.0 InternalTopologyBuilderInstrumenta…
ericfirth Aug 28, 2026
4a3bcfb
refactor: decouple kafka-connect-0.11 instrumentation from APM tracing
ericfirth Aug 28, 2026
8b7aebf
Migrate InstrumenterConfigTest to JUnit 5 and add isDataStreamsEnable…
ericfirth Aug 28, 2026
25935aa
Migrate SamplingMechanismTest to JUnit 5 and add DATA_STREAMS cases
ericfirth Aug 28, 2026
f755f81
test: add JUnit 5 tests for AgentInstaller.getEnabledSystems() DATA_S…
ericfirth Aug 28, 2026
3e47f9e
fix: suppress APM billing for DSM-only Kafka consume spans in kafka-c…
ericfirth Aug 28, 2026
86764eb
Add tests for DSM billing suppression on kafka produce/consume spans
ericfirth Aug 28, 2026
dcce923
fix: only force DSM-only USER_DROP when the Kafka span owns the local…
ericfirth Aug 28, 2026
3bad75e
fix: keep Kafka Code Origin instrumentation on the Tracing base class
ericfirth Aug 28, 2026
dd07f44
test: cover DSM-only sampling suppression for Kafka produce, consume …
ericfirth Aug 28, 2026
8f5f263
fix: suppress DSM-only Kafka poll-span billing gap and address code r…
ericfirth Aug 31, 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
1 change: 1 addition & 0 deletions dd-java-agent/agent-installer/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ dependencies {
compileOnly project(':products:metrics:metrics-lib')

testImplementation project(':dd-java-agent:testing')
testImplementation project(':utils:test-junit-utils')
}

tasks.named("compileMain_java11Java", JavaCompile) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,9 @@ public static Set<InstrumenterModule.TargetSystem> getEnabledSystems() {
if (cfg.isUsmEnabled()) {
enabledSystems.add(InstrumenterModule.TargetSystem.USM);
}
if (cfg.isDataStreamsEnabled()) {
enabledSystems.add(InstrumenterModule.TargetSystem.DATA_STREAMS);
}
if (cfg.isLlmObsEnabled()) {
enabledSystems.add(InstrumenterModule.TargetSystem.LLMOBS);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package datadog.trace.agent.tooling;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import datadog.trace.agent.tooling.InstrumenterModule.TargetSystem;
import datadog.trace.test.junit.utils.config.WithConfig;
import datadog.trace.test.junit.utils.config.WithConfigExtension;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

/**
* Tests for {@link AgentInstaller#getEnabledSystems()} to verify that it correctly includes target
* systems based on their corresponding configuration flags.
*/
@ExtendWith(WithConfigExtension.class)
class AgentInstallerGetEnabledSystemsTest {

/**
* Verifies that DATA_STREAMS target system is not included when data.streams.enabled is false
* (default).
*/
@Test
void dataStreamsNotIncludedWhenDisabled() {
Set<TargetSystem> enabledSystems = AgentInstaller.getEnabledSystems();
assertFalse(
enabledSystems.contains(TargetSystem.DATA_STREAMS),
"DATA_STREAMS should not be included when disabled");
}

/** Verifies that DATA_STREAMS target system is included when data.streams.enabled is true. */
@Test
@WithConfig(key = "data.streams.enabled", value = "true")
void dataStreamsIncludedWhenEnabled() {
Set<TargetSystem> enabledSystems = AgentInstaller.getEnabledSystems();
assertTrue(
enabledSystems.contains(TargetSystem.DATA_STREAMS),
"DATA_STREAMS should be included when enabled");
}

/** Verifies that USM target system is not included when usm.enabled is false (default). */
@Test
void usmNotIncludedWhenDisabled() {
Set<TargetSystem> enabledSystems = AgentInstaller.getEnabledSystems();
assertFalse(
enabledSystems.contains(TargetSystem.USM), "USM should not be included when disabled");
}

/** Verifies that USM target system is included when usm.enabled is true. */
@Test
@WithConfig(key = "usm.enabled", value = "true")
void usmIncludedWhenEnabled() {
Set<TargetSystem> enabledSystems = AgentInstaller.getEnabledSystems();
assertTrue(enabledSystems.contains(TargetSystem.USM), "USM should be included when enabled");
}

/** Verifies that LLMOBS target system is not included when llmobs.enabled is false (default). */
@Test
void llmobsNotIncludedWhenDisabled() {
Set<TargetSystem> enabledSystems = AgentInstaller.getEnabledSystems();
assertFalse(
enabledSystems.contains(TargetSystem.LLMOBS),
"LLMOBS should not be included when disabled");
}

/** Verifies that LLMOBS target system is included when llmobs.enabled is true. */
@Test
@WithConfig(key = "llmobs.enabled", value = "true")
void llmobsIncludedWhenEnabled() {
Set<TargetSystem> enabledSystems = AgentInstaller.getEnabledSystems();
assertTrue(
enabledSystems.contains(TargetSystem.LLMOBS), "LLMOBS should be included when enabled");
}
}
1 change: 1 addition & 0 deletions dd-java-agent/agent-tooling/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ dependencies {
api libs.bytebuddyagent

testImplementation project(':dd-java-agent:testing')
testImplementation project(':utils:test-junit-utils')
testImplementation libs.bytebuddy
testImplementation group: 'com.google.guava', name: 'guava-testlib', version: '20.0'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public abstract class InstrumenterModule implements Instrumenter {
* <li>{@link TargetSystem#IAST iast}
* <li>{@link TargetSystem#CIVISIBILITY ci-visibility}
* <li>{@link TargetSystem#USM usm}
* <li>{@link TargetSystem#DATA_STREAMS data-streams}
* <li>{@link TargetSystem#CONTEXT_TRACKING context-tracking}
* <li>{@link TargetSystem#RASP rasp}
* </ul>
Expand All @@ -53,6 +54,7 @@ public enum TargetSystem {
CIVISIBILITY,
USM,
LLMOBS,
DATA_STREAMS,
CONTEXT_TRACKING,
RASP,
}
Expand Down Expand Up @@ -320,6 +322,24 @@ public final boolean isApplicable(Set<TargetSystem> enabledSystems) {
}
}

/** Parent class for instrumentations that support both tracing and Data Streams Monitoring */
public abstract static class DataStreams extends InstrumenterModule {
public DataStreams(String instrumentationName, String... additionalNames) {
super(instrumentationName, additionalNames);
}

@Override
public final boolean isApplicable(Set<TargetSystem> enabledSystems) {
return enabledSystems.contains(TargetSystem.TRACING)
|| enabledSystems.contains(TargetSystem.DATA_STREAMS);
}

@Override
public boolean isEnabled() {
return super.isEnabled() || InstrumenterConfig.get().isDataStreamsEnabled();
}
}

/** Parent class for all CI related instrumentations */
public abstract static class CiVisibility extends InstrumenterModule {
public CiVisibility(String instrumentationName, String... additionalNames) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package datadog.trace.agent.tooling;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import datadog.trace.agent.tooling.InstrumenterModule.TargetSystem;
import datadog.trace.test.junit.utils.config.WithConfig;
import datadog.trace.test.junit.utils.config.WithConfigExtension;
import java.util.HashSet;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(WithConfigExtension.class)
class InstrumenterModuleTest {

@Test
void testDataStreamsIsApplicableWithTracing() {
Set<TargetSystem> enabledSystems = new HashSet<>();
enabledSystems.add(TargetSystem.TRACING);

InstrumenterModule.DataStreams module = new InstrumenterModule.DataStreams("test-module") {};

assertTrue(module.isApplicable(enabledSystems));
}

@Test
void testDataStreamsIsApplicableWithDataStreams() {
Set<TargetSystem> enabledSystems = new HashSet<>();
enabledSystems.add(TargetSystem.DATA_STREAMS);

InstrumenterModule.DataStreams module = new InstrumenterModule.DataStreams("test-module") {};

assertTrue(module.isApplicable(enabledSystems));
}

@Test
void testDataStreamsIsApplicableWithBoth() {
Set<TargetSystem> enabledSystems = new HashSet<>();
enabledSystems.add(TargetSystem.TRACING);
enabledSystems.add(TargetSystem.DATA_STREAMS);

InstrumenterModule.DataStreams module = new InstrumenterModule.DataStreams("test-module") {};

assertTrue(module.isApplicable(enabledSystems));
}

@Test
void testDataStreamsIsApplicableWithNeither() {
Set<TargetSystem> enabledSystems = new HashSet<>();
enabledSystems.add(TargetSystem.APPSEC);

InstrumenterModule.DataStreams module = new InstrumenterModule.DataStreams("test-module") {};

assertFalse(module.isApplicable(enabledSystems));
}

@Test
@WithConfig(key = "trace.test-kafka-module.enabled", value = "false")
@WithConfig(key = "data.streams.enabled", value = "true")
void testDataStreamsIsEnabledWhenDataStreamsEnabledOverridesFalse() {
// When tracing for this integration is disabled but DSM is explicitly enabled,
// isEnabled() should still return true.
InstrumenterModule.DataStreams module =
new InstrumenterModule.DataStreams("test-kafka-module") {};

assertTrue(module.isEnabled());
}

@Test
@WithConfig(key = "trace.test-kafka-module.enabled", value = "true")
@WithConfig(key = "data.streams.enabled", value = "false")
void testDataStreamsIsEnabledWhenSuperEnabledIsTrue() {
// When super.isEnabled() is true, isEnabled() should return true regardless of DSM state.
InstrumenterModule.DataStreams module =
new InstrumenterModule.DataStreams("test-kafka-module") {};

assertTrue(module.isEnabled());
}

@Test
@WithConfig(key = "trace.test-kafka-module.enabled", value = "true")
@WithConfig(key = "data.streams.enabled", value = "true")
void testDataStreamsIsEnabledWhenBothEnabled() {
// When both super.isEnabled() and DSM are enabled, isEnabled() should return true.
InstrumenterModule.DataStreams module =
new InstrumenterModule.DataStreams("test-kafka-module") {};

assertTrue(module.isEnabled());
}

@Test
@WithConfig(key = "trace.test-kafka-module.enabled", value = "false")
@WithConfig(key = "data.streams.enabled", value = "false")
void testDataStreamsIsEnabledWhenBothDisabled() {
// When both super.isEnabled() and DSM are disabled, isEnabled() should return false.
InstrumenterModule.DataStreams module =
new InstrumenterModule.DataStreams("test-kafka-module") {};

assertFalse(module.isEnabled());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@
import org.apache.kafka.common.TopicPartition;

@AutoService(InstrumenterModule.class)
public final class ConsumerCoordinatorInstrumentation extends InstrumenterModule.Tracing
public final class ConsumerCoordinatorInstrumentation extends InstrumenterModule.DataStreams
implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice {

public ConsumerCoordinatorInstrumentation() {
super("kafka", "kafka-0.11");
super(KafkaDecorator.INTEGRATION_NAME, KafkaDecorator.LEGACY_INTEGRATION_NAME);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.api.Config;
import datadog.trace.api.sampling.PrioritySampling;
import datadog.trace.api.sampling.SamplingMechanism;
import datadog.trace.bootstrap.InstrumentationContext;
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
Expand All @@ -44,11 +46,11 @@
* and cluster ID, in the context store for later use.
*/
@AutoService(InstrumenterModule.class)
public final class KafkaConsumerInfoInstrumentation extends InstrumenterModule.Tracing
public final class KafkaConsumerInfoInstrumentation extends InstrumenterModule.DataStreams
implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice {

public KafkaConsumerInfoInstrumentation() {
super("kafka", "kafka-0.11");
super(KafkaDecorator.INTEGRATION_NAME, KafkaDecorator.LEGACY_INTEGRATION_NAME);
}

@Override
Expand Down Expand Up @@ -254,6 +256,13 @@ public static AgentScope onEnter(@Advice.This KafkaConsumer consumer) {

if (traceConfig().isDataStreamsEnabled()) {
final AgentSpan span = startSpan(JAVA_KAFKA.toString(), KAFKA_POLL);
// setSamplingPriority is trace-level: it resolves to the local root span. This 2-arg
// startSpan honours the active scope, so `span` may be a child of a customer trace. Only
// force the DSM-only drop when `span` is the local root, otherwise we would silently drop
// that whole customer trace.
if (!KafkaDecorator.TRACING_ENABLED && span.getLocalRootSpan() == span) {
span.setSamplingPriority(PrioritySampling.USER_DROP, SamplingMechanism.DATA_STREAMS);
}
return activateSpan(span);
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@
import org.apache.kafka.clients.consumer.ConsumerRecords;

@AutoService(InstrumenterModule.class)
public final class KafkaConsumerInstrumentation extends InstrumenterModule.Tracing
public final class KafkaConsumerInstrumentation extends InstrumenterModule.DataStreams
implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice {

public KafkaConsumerInstrumentation() {
super("kafka", "kafka-0.11");
super(KafkaDecorator.INTEGRATION_NAME, KafkaDecorator.LEGACY_INTEGRATION_NAME);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import datadog.trace.api.Config;
import datadog.trace.api.Functions;
import datadog.trace.api.InstrumenterConfig;
import datadog.trace.api.cache.DDCache;
import datadog.trace.api.cache.DDCaches;
import datadog.trace.api.naming.SpanNaming;
Expand All @@ -20,6 +21,7 @@
import datadog.trace.bootstrap.instrumentation.api.Tags;
import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString;
import datadog.trace.bootstrap.instrumentation.decorator.MessagingClientDecorator;
import java.util.Arrays;
import java.util.function.Function;
import java.util.function.Supplier;
import org.apache.kafka.clients.consumer.ConsumerRecord;
Expand All @@ -29,6 +31,10 @@

public class KafkaDecorator extends MessagingClientDecorator {
private static final String KAFKA = "kafka";
// Kept in sync with the names each kafka-clients-0.11 instrumentation module passes to its own
// super(...) constructor call, so TRACING_ENABLED can't drift from what is actually registered.
public static final String INTEGRATION_NAME = KAFKA;
public static final String LEGACY_INTEGRATION_NAME = "kafka-0.11";
public static final CharSequence JAVA_KAFKA = UTF8BytesString.create("java-kafka");
public static final CharSequence KAFKA_CONSUME =
UTF8BytesString.create(
Expand All @@ -42,6 +48,11 @@ public class KafkaDecorator extends MessagingClientDecorator {
public static final boolean KAFKA_LEGACY_TRACING = Config.get().isKafkaLegacyTracingEnabled();
public static final boolean TIME_IN_QUEUE_ENABLED =
Config.get().isTimeInQueueEnabled(!KAFKA_LEGACY_TRACING, KAFKA);
public static final boolean TRACING_ENABLED =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Within this module, the DSM-only USER_DROP guard is hand-copied 4 times: twice in TracingIterator.java (using spanContext == null, no local-root check — see my comment there), once in KafkaConsumerInfoInstrumentation.java's poll span, and once in KafkaProducerInstrumentation.java's produce span (the latter two correctly use span.getLocalRootSpan() == span plus a comment explaining why). That the fixed/correct version only made it to 2 of 4 in-module copies is exactly the risk of this duplication. Cross-module sharing with kafka-streams-0.11 etc. is understandably not worth the trouble, but a small static helper here in KafkaDecorator (e.g. maybeForceDsmOnlyDrop(AgentSpan span)), or a private helper in TracingIterator shared between its two call sites, seems low-cost and would have kept all four in-module copies in sync.

InstrumenterConfig.get()
.isIntegrationEnabled(
Arrays.asList(INTEGRATION_NAME, LEGACY_INTEGRATION_NAME),
InstrumenterConfig.get().isIntegrationsEnabled());
public static final String KAFKA_PRODUCED_KEY = "x_datadog_kafka_produced";
private final String spanKind;
private final CharSequence spanType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan;
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan;
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.traceConfig;
import static datadog.trace.instrumentation.kafka_clients.KafkaDecorator.JAVA_KAFKA;
import static datadog.trace.instrumentation.kafka_clients.KafkaDecorator.KAFKA_PRODUCE;
import static datadog.trace.instrumentation.kafka_clients.KafkaDecorator.PRODUCER_DECORATE;
Expand All @@ -37,6 +38,8 @@
import datadog.trace.api.datastreams.DataStreamsTags;
import datadog.trace.api.datastreams.DataStreamsTransactionExtractor;
import datadog.trace.api.datastreams.StatsPoint;
import datadog.trace.api.sampling.PrioritySampling;
import datadog.trace.api.sampling.SamplingMechanism;
import datadog.trace.bootstrap.InstrumentationContext;
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
Expand All @@ -58,11 +61,11 @@
import org.apache.kafka.common.record.RecordBatch;

@AutoService(InstrumenterModule.class)
public final class KafkaProducerInstrumentation extends InstrumenterModule.Tracing
public final class KafkaProducerInstrumentation extends InstrumenterModule.DataStreams
implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice {

public KafkaProducerInstrumentation() {
super("kafka", "kafka-0.11");
super(KafkaDecorator.INTEGRATION_NAME, KafkaDecorator.LEGACY_INTEGRATION_NAME);
}

@Override
Expand Down Expand Up @@ -165,6 +168,15 @@ public static AgentScope onEnter(
} else {
span = startSpan(JAVA_KAFKA.toString(), KAFKA_PRODUCE);
callbackParentSpan = localActiveSpan;
// setSamplingPriority is trace-level: it resolves to the local root span. This 2-arg
// startSpan honours the active scope, so `span` may be a child of a customer trace
// (localActiveSpan above). Only force the DSM-only drop when `span` is the local root,
// otherwise we would silently drop that whole customer trace.
if (!KafkaDecorator.TRACING_ENABLED
&& traceConfig().isDataStreamsEnabled()
&& span.getLocalRootSpan() == span) {
span.setSamplingPriority(PrioritySampling.USER_DROP, SamplingMechanism.DATA_STREAMS);
}
}
PRODUCER_DECORATE.afterStart(span);
PRODUCER_DECORATE.onProduce(span, record, producerConfig, clusterId);
Expand Down
Loading
Loading