From 61fc9fa130de59032ce2ed295e0de0b8786258ee Mon Sep 17 00:00:00 2001 From: Matthew Li Date: Thu, 24 Jul 2025 17:02:22 -0400 Subject: [PATCH 1/4] Adding ConfigInversion Telemetry component --- .../ConfigInversionMetricCollector.java | 70 +++++++++++++++++++ .../ConfigInversionMetricCollectorTest.groovy | 28 ++++++++ ...figInversionMetricCollectorTestHelper.java | 21 ++++++ .../datadog/telemetry/TelemetrySystem.java | 2 + .../ConfigInversionMetricPeriodicAction.java | 13 ++++ 5 files changed, 134 insertions(+) create mode 100644 internal-api/src/main/java/datadog/trace/api/telemetry/ConfigInversionMetricCollector.java create mode 100644 internal-api/src/test/groovy/datadog/trace/api/telemetry/ConfigInversionMetricCollectorTest.groovy create mode 100644 internal-api/src/test/groovy/datadog/trace/api/telemetry/ConfigInversionMetricCollectorTestHelper.java create mode 100644 telemetry/src/main/java/datadog/telemetry/metric/ConfigInversionMetricPeriodicAction.java diff --git a/internal-api/src/main/java/datadog/trace/api/telemetry/ConfigInversionMetricCollector.java b/internal-api/src/main/java/datadog/trace/api/telemetry/ConfigInversionMetricCollector.java new file mode 100644 index 00000000000..cdd781c78d6 --- /dev/null +++ b/internal-api/src/main/java/datadog/trace/api/telemetry/ConfigInversionMetricCollector.java @@ -0,0 +1,70 @@ +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; + +public class ConfigInversionMetricCollector + implements MetricCollector { + private static final Logger log = LoggerFactory.getLogger(ConfigInversionMetricCollector.class); + private static final String CONFIG_INVERSION_KEY_TAG = "config_name:"; + private static final String CONFIG_INVERSION_METRIC_NAME = "untracked.config.detected"; + private static final String NAMESPACE = "tracers"; + private static final ConfigInversionMetricCollector INSTANCE = + new ConfigInversionMetricCollector(); + + private final BlockingQueue metricsQueue; + + private ConfigInversionMetricCollector() { + this.metricsQueue = new ArrayBlockingQueue<>(RAW_QUEUE_SIZE); + } + + public static ConfigInversionMetricCollector getInstance() { + return INSTANCE; + } + + public void setUndocumentedEnvVarMetric(String configName) { + setMetricConfigInversionMetric(CONFIG_INVERSION_KEY_TAG + configName); + } + + private void setMetricConfigInversionMetric(final String... tags) { + if (!metricsQueue.offer( + new ConfigInversionMetricCollector.ConfigInversionMetric( + NAMESPACE, true, CONFIG_INVERSION_METRIC_NAME, "count", 1, tags))) { + log.debug("Unable to add telemetry metric {} for {}", CONFIG_INVERSION_METRIC_NAME, tags[0]); + } + } + + @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 ConfigInversionMetric extends MetricCollector.Metric { + public ConfigInversionMetric( + 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/groovy/datadog/trace/api/telemetry/ConfigInversionMetricCollectorTest.groovy b/internal-api/src/test/groovy/datadog/trace/api/telemetry/ConfigInversionMetricCollectorTest.groovy new file mode 100644 index 00000000000..07041f02616 --- /dev/null +++ b/internal-api/src/test/groovy/datadog/trace/api/telemetry/ConfigInversionMetricCollectorTest.groovy @@ -0,0 +1,28 @@ +package datadog.trace.api.telemetry + +import datadog.trace.test.util.DDSpecification + +import static datadog.trace.api.telemetry.ConfigInversionMetricCollector.CONFIG_INVERSION_METRIC_NAME + +class ConfigInversionMetricCollectorTest extends DDSpecification { + + def "should emit metric when unsupported env var is used"() { + setup: + def collector = ConfigInversionMetricCollector.getInstance() + + when: + ConfigInversionMetricCollectorTestHelper.checkAndEmitUnsupported("DD_UNKNOWN_FEATURE") + collector.prepareMetrics() + def metrics = collector.drain() + + then: + metrics.size() == 1 + def metric = metrics[0] + metric.type == 'count' + metric.value == 1 + metric.namespace == 'tracers' + metric.metricName == CONFIG_INVERSION_METRIC_NAME + metric.tags.size() == 1 + metric.tags[0] == 'config_name:DD_UNKNOWN_FEATURE' + } +} diff --git a/internal-api/src/test/groovy/datadog/trace/api/telemetry/ConfigInversionMetricCollectorTestHelper.java b/internal-api/src/test/groovy/datadog/trace/api/telemetry/ConfigInversionMetricCollectorTestHelper.java new file mode 100644 index 00000000000..9cb70dd68b5 --- /dev/null +++ b/internal-api/src/test/groovy/datadog/trace/api/telemetry/ConfigInversionMetricCollectorTestHelper.java @@ -0,0 +1,21 @@ +package datadog.trace.api.telemetry; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +// Lightweight helper class to simulate Config Inversion ConfigHelper scenario where telemetry +// metrics are emitted for "unknown" environment variables. +public class ConfigInversionMetricCollectorTestHelper { + private static final Set SUPPORTED_ENV_VARS = + new HashSet<>(Arrays.asList("DD_ENV", "DD_SERVICE")); + + private static final ConfigInversionMetricCollector configInversionMetricCollector = + ConfigInversionMetricCollector.getInstance(); + + public static void checkAndEmitUnsupported(String envVarName) { + if (!SUPPORTED_ENV_VARS.contains(envVarName)) { + configInversionMetricCollector.setUndocumentedEnvVarMetric(envVarName); + } + } +} diff --git a/telemetry/src/main/java/datadog/telemetry/TelemetrySystem.java b/telemetry/src/main/java/datadog/telemetry/TelemetrySystem.java index 8a1c3af38de..8edac4935e2 100644 --- a/telemetry/src/main/java/datadog/telemetry/TelemetrySystem.java +++ b/telemetry/src/main/java/datadog/telemetry/TelemetrySystem.java @@ -10,6 +10,7 @@ import datadog.telemetry.integration.IntegrationPeriodicAction; import datadog.telemetry.log.LogPeriodicAction; import datadog.telemetry.metric.CiVisibilityMetricPeriodicAction; +import datadog.telemetry.metric.ConfigInversionMetricPeriodicAction; import datadog.telemetry.metric.CoreMetricsPeriodicAction; import datadog.telemetry.metric.IastMetricPeriodicAction; import datadog.telemetry.metric.OtelEnvMetricPeriodicAction; @@ -51,6 +52,7 @@ static Thread createTelemetryRunnable( if (telemetryMetricsEnabled) { actions.add(new CoreMetricsPeriodicAction()); actions.add(new OtelEnvMetricPeriodicAction()); + actions.add(new ConfigInversionMetricPeriodicAction()); actions.add(new IntegrationPeriodicAction()); actions.add(new WafMetricPeriodicAction()); if (Verbosity.OFF != Config.get().getIastTelemetryVerbosity()) { diff --git a/telemetry/src/main/java/datadog/telemetry/metric/ConfigInversionMetricPeriodicAction.java b/telemetry/src/main/java/datadog/telemetry/metric/ConfigInversionMetricPeriodicAction.java new file mode 100644 index 00000000000..e7eb7dfab48 --- /dev/null +++ b/telemetry/src/main/java/datadog/telemetry/metric/ConfigInversionMetricPeriodicAction.java @@ -0,0 +1,13 @@ +package datadog.telemetry.metric; + +import datadog.trace.api.telemetry.ConfigInversionMetricCollector; +import datadog.trace.api.telemetry.MetricCollector; +import edu.umd.cs.findbugs.annotations.NonNull; + +public class ConfigInversionMetricPeriodicAction extends MetricPeriodicAction { + @Override + @NonNull + public MetricCollector collector() { + return ConfigInversionMetricCollector.getInstance(); + } +} From 9cbb54bcfc50c57b6fc7c62520a0b67b9781fe80 Mon Sep 17 00:00:00 2001 From: Matthew Li Date: Fri, 25 Jul 2025 14:16:09 -0400 Subject: [PATCH 2/4] adding test coverage --- .../ConfigInversionMetricCollectorTest.groovy | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/internal-api/src/test/groovy/datadog/trace/api/telemetry/ConfigInversionMetricCollectorTest.groovy b/internal-api/src/test/groovy/datadog/trace/api/telemetry/ConfigInversionMetricCollectorTest.groovy index 07041f02616..7f63048eb27 100644 --- a/internal-api/src/test/groovy/datadog/trace/api/telemetry/ConfigInversionMetricCollectorTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/api/telemetry/ConfigInversionMetricCollectorTest.groovy @@ -25,4 +25,17 @@ class ConfigInversionMetricCollectorTest extends DDSpecification { metric.tags.size() == 1 metric.tags[0] == 'config_name:DD_UNKNOWN_FEATURE' } + + def "should not emit metric when supported env var is used"() { + setup: + def collector = ConfigInversionMetricCollector.getInstance() + + when: + ConfigInversionMetricCollectorTestHelper.checkAndEmitUnsupported("DD_ENV") + collector.prepareMetrics() + def metrics = collector.drain() + + then: + metrics.isEmpty() + } } From 8d2c687c66e9412a46ad17b2aee5ef9bb9f7bd1c Mon Sep 17 00:00:00 2001 From: Matthew Li Date: Fri, 25 Jul 2025 14:52:59 -0400 Subject: [PATCH 3/4] adding test coverage --- ...igInversionMetricPeriodicActionTest.groovy | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 telemetry/src/test/groovy/datadog/telemetry/metric/ConfigInversionMetricPeriodicActionTest.groovy diff --git a/telemetry/src/test/groovy/datadog/telemetry/metric/ConfigInversionMetricPeriodicActionTest.groovy b/telemetry/src/test/groovy/datadog/telemetry/metric/ConfigInversionMetricPeriodicActionTest.groovy new file mode 100644 index 00000000000..7ab019ad1fd --- /dev/null +++ b/telemetry/src/test/groovy/datadog/telemetry/metric/ConfigInversionMetricPeriodicActionTest.groovy @@ -0,0 +1,29 @@ +package datadog.telemetry.metric + +import datadog.telemetry.TelemetryService +import datadog.telemetry.api.Metric +import spock.lang.Specification + +class ConfigInversionMetricPeriodicActionTest extends Specification{ + + void 'test otel env var hiding metric'() { + setup: + final telemetryService = Mock(TelemetryService) + final action = new ConfigInversionMetricPeriodicAction() + + when: + action.collector().setUndocumentedEnvVarMetric("DD_ENV_VAR") + action.collector().prepareMetrics() + action.doIteration(telemetryService) + + then: + 1 * telemetryService.addMetric({ Metric metric -> + metric.namespace == 'tracers' && + metric.metric == 'untracked.config.detected' && + metric.points[0][1] == 1 && + metric.tags == ['config_name:DD_ENV_VAR'] && + metric.type == Metric.TypeEnum.COUNT + }) + 0 * _._ + } +} From e254dd18d90f6a77ee86fbb38702d1058ba70e18 Mon Sep 17 00:00:00 2001 From: Matthew Li Date: Tue, 5 Aug 2025 09:57:02 -0400 Subject: [PATCH 4/4] update test name --- buildSrc/.kotlin/errors/errors-1754401818862.log | 4 ++++ .../metric/ConfigInversionMetricPeriodicActionTest.groovy | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 buildSrc/.kotlin/errors/errors-1754401818862.log diff --git a/buildSrc/.kotlin/errors/errors-1754401818862.log b/buildSrc/.kotlin/errors/errors-1754401818862.log new file mode 100644 index 00000000000..1219b509f09 --- /dev/null +++ b/buildSrc/.kotlin/errors/errors-1754401818862.log @@ -0,0 +1,4 @@ +kotlin version: 2.0.21 +error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output: + 1. Kotlin compile daemon is ready + diff --git a/telemetry/src/test/groovy/datadog/telemetry/metric/ConfigInversionMetricPeriodicActionTest.groovy b/telemetry/src/test/groovy/datadog/telemetry/metric/ConfigInversionMetricPeriodicActionTest.groovy index 7ab019ad1fd..073c1e3e68f 100644 --- a/telemetry/src/test/groovy/datadog/telemetry/metric/ConfigInversionMetricPeriodicActionTest.groovy +++ b/telemetry/src/test/groovy/datadog/telemetry/metric/ConfigInversionMetricPeriodicActionTest.groovy @@ -6,7 +6,7 @@ import spock.lang.Specification class ConfigInversionMetricPeriodicActionTest extends Specification{ - void 'test otel env var hiding metric'() { + void 'test undocumented env var metric'() { setup: final telemetryService = Mock(TelemetryService) final action = new ConfigInversionMetricPeriodicAction()