diff --git a/dd-java-agent/instrumentation/graal/graal-native-image-20.0/src/main/java/datadog/trace/instrumentation/graal/nativeimage/NativeImageGeneratorRunnerInstrumentation.java b/dd-java-agent/instrumentation/graal/graal-native-image-20.0/src/main/java/datadog/trace/instrumentation/graal/nativeimage/NativeImageGeneratorRunnerInstrumentation.java index cf13266a41e..405d3f35bc2 100644 --- a/dd-java-agent/instrumentation/graal/graal-native-image-20.0/src/main/java/datadog/trace/instrumentation/graal/nativeimage/NativeImageGeneratorRunnerInstrumentation.java +++ b/dd-java-agent/instrumentation/graal/graal-native-image-20.0/src/main/java/datadog/trace/instrumentation/graal/nativeimage/NativeImageGeneratorRunnerInstrumentation.java @@ -108,6 +108,7 @@ public static void onEnter(@Advice.Argument(value = 0, readOnly = false) String[ + "datadog.trace.api.telemetry.ConfigInversionMetricCollectorImpl$ConfigInversionMetric:build_time," + "datadog.trace.api.telemetry.NoOpConfigInversionMetricCollector:build_time," + "datadog.trace.api.telemetry.OtelEnvMetricCollectorImpl:build_time," + + "datadog.trace.api.telemetry.OtelSpiCollector:build_time," + "datadog.trace.api.profiling.ProfilingEnablement:build_time," + "datadog.trace.bootstrap.config.provider.ConfigConverter:build_time," + "datadog.trace.bootstrap.config.provider.ConfigConverter$ValueOfLookup:build_time," diff --git a/internal-api/src/main/java/datadog/trace/api/telemetry/OtelSpiCollector.java b/internal-api/src/main/java/datadog/trace/api/telemetry/OtelSpiCollector.java new file mode 100644 index 00000000000..375b17b6893 --- /dev/null +++ b/internal-api/src/main/java/datadog/trace/api/telemetry/OtelSpiCollector.java @@ -0,0 +1,75 @@ +package datadog.trace.api.telemetry; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Collects telemetry about OpenTelemetry SPIs detected in the customer environment. */ +public class OtelSpiCollector implements MetricCollector { + private static final Logger log = LoggerFactory.getLogger(OtelSpiCollector.class); + private static final String OTEL_SPI_DETECTED_METRIC_NAME = "otel.spi.detected"; + private static final String SPI_CLASS_TAG = "spi_class:"; + private static final String SOURCE_TAG = "source:"; + private static final String NAMESPACE = "tracers"; + private static final OtelSpiCollector INSTANCE = new OtelSpiCollector(); + + private final BlockingQueue metricsQueue; + + private OtelSpiCollector() { + this.metricsQueue = new ArrayBlockingQueue<>(RAW_QUEUE_SIZE); + } + + public static OtelSpiCollector getInstance() { + return INSTANCE; + } + + public void recordSpiDetected(String spiFqn, String source) { + if (!metricsQueue.offer( + new OtelSpiMetric( + NAMESPACE, + true, + OTEL_SPI_DETECTED_METRIC_NAME, + "count", + 1, + SPI_CLASS_TAG + spiFqn, + SOURCE_TAG + source))) { + log.debug( + "Unable to add telemetry metric {} for spi_class={} source={}", + OTEL_SPI_DETECTED_METRIC_NAME, + spiFqn, + source); + } + } + + @Override + public void prepareMetrics() { + // Nothing to do here + } + + @Override + public Collection drain() { + if (this.metricsQueue.isEmpty()) { + return Collections.emptyList(); + } + List drained = new ArrayList<>(this.metricsQueue.size()); + this.metricsQueue.drainTo(drained); + return drained; + } + + public static class OtelSpiMetric extends MetricCollector.Metric { + public OtelSpiMetric( + String namespace, + boolean common, + String metricName, + String type, + Number value, + final String... tags) { + super(namespace, common, metricName, type, value, tags); + } + } +} diff --git a/internal-api/src/test/java/datadog/trace/api/telemetry/OtelSpiCollectorTest.java b/internal-api/src/test/java/datadog/trace/api/telemetry/OtelSpiCollectorTest.java new file mode 100644 index 00000000000..f1ddf2f2dea --- /dev/null +++ b/internal-api/src/test/java/datadog/trace/api/telemetry/OtelSpiCollectorTest.java @@ -0,0 +1,69 @@ +package datadog.trace.api.telemetry; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Iterator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class OtelSpiCollectorTest { + + private final OtelSpiCollector collector = OtelSpiCollector.getInstance(); + + @BeforeEach + public void clearQueue() { + collector.drain(); + } + + @Test + public void singletonReturnsSameInstance() { + assertSame(OtelSpiCollector.getInstance(), OtelSpiCollector.getInstance()); + } + + @Test + public void recordedMetricSurfacesOnDrain() { + collector.recordSpiDetected( + "io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider", "extensions_path"); + + Iterator drained = collector.drain().iterator(); + assertTrue(drained.hasNext()); + OtelSpiCollector.OtelSpiMetric metric = drained.next(); + assertEquals("tracers", metric.namespace); + assertEquals("otel.spi.detected", metric.metricName); + assertEquals("count", metric.type); + assertTrue(metric.common); + assertEquals(1, metric.value); + assertTrue( + metric.tags.contains( + "spi_class:io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider")); + assertTrue(metric.tags.contains("source:extensions_path")); + } + + @Test + public void multipleRecordsDrainAsDistinctMetrics() { + collector.recordSpiDetected( + "io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider", "app_classpath"); + collector.recordSpiDetected( + "io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider", "app_classpath"); + collector.recordSpiDetected( + "io.opentelemetry.sdk.autoconfigure.spi.ConfigurableSamplerProvider", "extensions_path"); + + assertEquals(3, collector.drain().size()); + } + + @Test + public void drainWithoutRecordsReturnsEmpty() { + assertEquals(0, collector.drain().size()); + } + + @Test + public void drainClearsQueue() { + collector.recordSpiDetected( + "io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider", + "extensions_path"); + assertEquals(1, collector.drain().size()); + assertEquals(0, collector.drain().size()); + } +} diff --git a/telemetry/src/main/java/datadog/telemetry/TelemetrySystem.java b/telemetry/src/main/java/datadog/telemetry/TelemetrySystem.java index 4c66f5349f2..1899083d3df 100644 --- a/telemetry/src/main/java/datadog/telemetry/TelemetrySystem.java +++ b/telemetry/src/main/java/datadog/telemetry/TelemetrySystem.java @@ -15,6 +15,7 @@ import datadog.telemetry.metric.IastMetricPeriodicAction; import datadog.telemetry.metric.LLMObsMetricPeriodicAction; import datadog.telemetry.metric.OtelEnvMetricPeriodicAction; +import datadog.telemetry.metric.OtelSpiMetricPeriodicAction; import datadog.telemetry.metric.WafMetricPeriodicAction; import datadog.telemetry.products.ProductChangeAction; import datadog.telemetry.rum.RumPeriodicAction; @@ -57,6 +58,7 @@ static Thread createTelemetryRunnable( if (telemetryMetricsEnabled) { actions.add(new CoreMetricsPeriodicAction()); actions.add(new OtelEnvMetricPeriodicAction()); + actions.add(new OtelSpiMetricPeriodicAction()); actions.add(new ConfigInversionMetricPeriodicAction()); actions.add(new IntegrationPeriodicAction()); actions.add(new WafMetricPeriodicAction()); diff --git a/telemetry/src/main/java/datadog/telemetry/metric/OtelSpiMetricPeriodicAction.java b/telemetry/src/main/java/datadog/telemetry/metric/OtelSpiMetricPeriodicAction.java new file mode 100644 index 00000000000..bbdd9bfc89c --- /dev/null +++ b/telemetry/src/main/java/datadog/telemetry/metric/OtelSpiMetricPeriodicAction.java @@ -0,0 +1,13 @@ +package datadog.telemetry.metric; + +import datadog.trace.api.telemetry.MetricCollector; +import datadog.trace.api.telemetry.OtelSpiCollector; +import edu.umd.cs.findbugs.annotations.NonNull; + +public class OtelSpiMetricPeriodicAction extends MetricPeriodicAction { + @Override + @NonNull + public MetricCollector collector() { + return OtelSpiCollector.getInstance(); + } +}