diff --git a/telemetry/src/test/groovy/datadog/telemetry/integration/IntegrationPeriodicActionTest.groovy b/telemetry/src/test/groovy/datadog/telemetry/integration/IntegrationPeriodicActionTest.groovy deleted file mode 100644 index bbe25f09425..00000000000 --- a/telemetry/src/test/groovy/datadog/telemetry/integration/IntegrationPeriodicActionTest.groovy +++ /dev/null @@ -1,30 +0,0 @@ -package datadog.telemetry.integration - -import datadog.telemetry.TelemetryService -import datadog.telemetry.api.Integration -import datadog.trace.api.telemetry.IntegrationsCollector -import spock.lang.Specification - -class IntegrationPeriodicActionTest extends Specification { - IntegrationPeriodicAction periodicAction = new IntegrationPeriodicAction() - TelemetryService telemetryService = Mock() - - void 'push integrations into the telemetry service'() { - setup: - IntegrationsCollector.get().update(['web', 'jdbc'], true) - - when: - periodicAction.doIteration(telemetryService) - - then: - 1 * telemetryService.addIntegration( { Integration integration -> - integration.name == 'web' && - integration.enabled - } ) - 1 * telemetryService.addIntegration( { Integration integration -> - integration.name == 'jdbc' && - integration.enabled - } ) - 0 * _ - } -} diff --git a/telemetry/src/test/groovy/datadog/telemetry/metric/ConfigInversionMetricPeriodicActionTest.groovy b/telemetry/src/test/groovy/datadog/telemetry/metric/ConfigInversionMetricPeriodicActionTest.groovy deleted file mode 100644 index 073c1e3e68f..00000000000 --- a/telemetry/src/test/groovy/datadog/telemetry/metric/ConfigInversionMetricPeriodicActionTest.groovy +++ /dev/null @@ -1,29 +0,0 @@ -package datadog.telemetry.metric - -import datadog.telemetry.TelemetryService -import datadog.telemetry.api.Metric -import spock.lang.Specification - -class ConfigInversionMetricPeriodicActionTest extends Specification{ - - void 'test undocumented env var 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 * _._ - } -} diff --git a/telemetry/src/test/groovy/datadog/telemetry/metric/CoreMetricsPeriodActionTest.groovy b/telemetry/src/test/groovy/datadog/telemetry/metric/CoreMetricsPeriodActionTest.groovy deleted file mode 100644 index b4235810beb..00000000000 --- a/telemetry/src/test/groovy/datadog/telemetry/metric/CoreMetricsPeriodActionTest.groovy +++ /dev/null @@ -1,176 +0,0 @@ -package datadog.telemetry.metric - -import datadog.telemetry.TelemetryService -import datadog.telemetry.api.Metric -import datadog.trace.api.metrics.SpanMetricRegistry -import datadog.trace.api.metrics.SpanMetrics -import spock.lang.Specification - -class CoreMetricsPeriodActionTest extends Specification { - - void 'test span metrics with multiple occurrence events'() { - given: - final telemetryService = Mock(TelemetryService) - final action = new CoreMetricsPeriodicAction() - final SpanMetricRegistry spanMetricRegistry = SpanMetricRegistry.getInstance() - final SpanMetrics instr1SpanMetric = spanMetricRegistry.get('instr-1') - final SpanMetrics instr2SpanMetric = spanMetricRegistry.get('instr-2') - - when: - instr1SpanMetric.onSpanCreated() - instr2SpanMetric.onSpanCreated() - instr1SpanMetric.onSpanCreated() - instr2SpanMetric.onSpanCreated() - instr2SpanMetric.onSpanCreated() - instr2SpanMetric.onSpanFinished() - instr2SpanMetric.onSpanFinished() - instr2SpanMetric.onSpanFinished() - instr2SpanMetric.onSpanFinished() - - action.collector().prepareMetrics() - action.doIteration(telemetryService) - - then: - 1 * telemetryService.addMetric({ Metric metric -> - assertMetric(metric, 'spans_created', 'instr-1', 2) - }) - 1 * telemetryService.addMetric({ Metric metric -> - assertMetric(metric, 'spans_created', 'instr-2', 3) - }) - 1 * telemetryService.addMetric({ Metric metric -> - assertMetric(metric, 'spans_finished', 'instr-2', 4) - }) - 0 * _ - } - - void 'test span metrics with interleaved events'() { - given: - final telemetryService = Mock(TelemetryService) - final action = new CoreMetricsPeriodicAction() - final SpanMetricRegistry spanMetricRegistry = SpanMetricRegistry.getInstance() - final SpanMetrics instr1SpanMetric = spanMetricRegistry.get('instr-1') - final SpanMetrics instr2SpanMetric = spanMetricRegistry.get('instr-2') - final SpanMetrics instr3SpanMetric = spanMetricRegistry.get('instr-3') - final SpanMetrics instrASpanMetric = spanMetricRegistry.get('instr-a') - final SpanMetrics instrBSpanMetric = spanMetricRegistry.get('instr-b') - - when: - instr1SpanMetric.onSpanCreated() - instr2SpanMetric.onSpanCreated() - instr3SpanMetric.onSpanCreated() - instr3SpanMetric.onSpanFinished() - instrASpanMetric.onSpanCreated() - instrBSpanMetric.onSpanCreated() - instrBSpanMetric.onSpanFinished() - instr2SpanMetric.onSpanFinished() - instrASpanMetric.onSpanFinished() - instr1SpanMetric.onSpanFinished() - - action.collector().prepareMetrics() - action.doIteration(telemetryService) - - then: - 1 * telemetryService.addMetric({ Metric metric -> - assertMetric(metric, 'spans_created', 'instr-1', 1) - }) - 1 * telemetryService.addMetric({ Metric metric -> - assertMetric(metric, 'spans_created', 'instr-2', 1) - }) - 1 * telemetryService.addMetric({ Metric metric -> - assertMetric(metric, 'spans_created', 'instr-3', 1) - }) - 1 * telemetryService.addMetric({ Metric metric -> - assertMetric(metric, 'spans_finished', 'instr-3', 1) - }) - 1 * telemetryService.addMetric({ Metric metric -> - assertMetric(metric, 'spans_created', 'instr-a', 1) - }) - 1 * telemetryService.addMetric({ Metric metric -> - assertMetric(metric, 'spans_created', 'instr-b', 1) - }) - 1 * telemetryService.addMetric({ Metric metric -> - assertMetric(metric, 'spans_finished', 'instr-b', 1) - }) - 1 * telemetryService.addMetric({ Metric metric -> - assertMetric(metric, 'spans_finished', 'instr-2', 1) - }) - 1 * telemetryService.addMetric({ Metric metric -> - assertMetric(metric, 'spans_finished', 'instr-a', 1) - }) - 1 * telemetryService.addMetric({ Metric metric -> - assertMetric(metric, 'spans_finished', 'instr-1', 1) - }) - 0 * _ - } - - void 'test span metrics'() { - given: - final telemetryService = Mock(TelemetryService) - final action = new CoreMetricsPeriodicAction() - final SpanMetricRegistry spanMetricRegistry = SpanMetricRegistry.getInstance() - final SpanMetrics instr1SpanMetric = spanMetricRegistry.get('instr-1') - final SpanMetrics instr2SpanMetric = spanMetricRegistry.get('instr-2') - final SpanMetrics instr3SpanMetric = spanMetricRegistry.get('instr-3') - final SpanMetrics instrASpanMetric = spanMetricRegistry.get('instr-a') - final SpanMetrics instrBSpanMetric = spanMetricRegistry.get('instr-b') - - when: - instr1SpanMetric.onSpanCreated() - instr2SpanMetric.onSpanCreated() - instr3SpanMetric.onSpanCreated() - instr3SpanMetric.onSpanFinished() - instrASpanMetric.onSpanCreated() - instrBSpanMetric.onSpanCreated() - instrBSpanMetric.onSpanFinished() - instr2SpanMetric.onSpanFinished() - instrASpanMetric.onSpanFinished() - instr1SpanMetric.onSpanFinished() - - action.collector().prepareMetrics() - action.doIteration(telemetryService) - - then: - 1 * telemetryService.addMetric({ Metric metric -> - assertMetric(metric, 'spans_created', 'instr-1', 1) - }) - 1 * telemetryService.addMetric({ Metric metric -> - assertMetric(metric, 'spans_created', 'instr-2', 1) - }) - 1 * telemetryService.addMetric({ Metric metric -> - assertMetric(metric, 'spans_created', 'instr-3', 1) - }) - 1 * telemetryService.addMetric({ Metric metric -> - assertMetric(metric, 'spans_finished', 'instr-3', 1) - }) - 1 * telemetryService.addMetric({ Metric metric -> - assertMetric(metric, 'spans_created', 'instr-a', 1) - }) - 1 * telemetryService.addMetric({ Metric metric -> - assertMetric(metric, 'spans_created', 'instr-b', 1) - }) - 1 * telemetryService.addMetric({ Metric metric -> - assertMetric(metric, 'spans_finished', 'instr-b', 1) - }) - 1 * telemetryService.addMetric({ Metric metric -> - assertMetric(metric, 'spans_finished', 'instr-2', 1) - }) - 1 * telemetryService.addMetric({ Metric metric -> - assertMetric(metric, 'spans_finished', 'instr-a', 1) - }) - 1 * telemetryService.addMetric({ Metric metric -> - assertMetric(metric, 'spans_finished', 'instr-1', 1) - }) - 0 * _ - } - - void assertMetric(Metric metric, String metricName, String instrumentationName, long count) { - assert metric.namespace == 'tracers' - assert metric.common - assert metric.metric == metricName - assert metric.points.size() == 1 - assert metric.points[0].size() == 2 - assert metric.points[0][1] == count - assert metric.tags == ['integration_name:' + instrumentationName] - assert metric.type == Metric.TypeEnum.COUNT - } -} diff --git a/telemetry/src/test/groovy/datadog/telemetry/metric/IastMetricPeriodicActionTest.groovy b/telemetry/src/test/groovy/datadog/telemetry/metric/IastMetricPeriodicActionTest.groovy deleted file mode 100644 index d4808d4aaae..00000000000 --- a/telemetry/src/test/groovy/datadog/telemetry/metric/IastMetricPeriodicActionTest.groovy +++ /dev/null @@ -1,86 +0,0 @@ -package datadog.telemetry.metric - -import datadog.telemetry.TelemetryService -import datadog.telemetry.api.Metric -import datadog.trace.api.iast.SourceTypes -import datadog.trace.api.iast.telemetry.IastMetric -import datadog.trace.api.iast.telemetry.IastMetricCollector -import groovy.transform.CompileDynamic -import spock.lang.Shared -import spock.lang.Specification - -@CompileDynamic -class IastMetricPeriodicActionTest extends Specification { - - @Shared - protected static final IastMetricCollector ORIGINAL_COLLECTOR = IastMetricCollector.INSTANCE - - void setup() { - IastMetricCollector.register(new IastMetricCollector()) - } - - void cleanup() { - IastMetricCollector.register(ORIGINAL_COLLECTOR) - } - - void 'test metric'() { - given: - final action = new IastMetricPeriodicAction() - final service = Mock(TelemetryService) - final iastMetric = IastMetric.EXECUTED_TAINTED - final value = 23 - - when: - IastMetricCollector.add(iastMetric, value) - IastMetricCollector.get().prepareMetrics() - action.doIteration(service) - - then: - 1 * service.addMetric({ matches(it, iastMetric, value, []) }) - 0 * _ - } - - void 'test tagged metric'() { - given: - final action = new IastMetricPeriodicAction() - final service = Mock(TelemetryService) - final iastMetric = IastMetric.INSTRUMENTED_SOURCE - final tag = SourceTypes.REQUEST_PARAMETER_VALUE - final tagString = SourceTypes.toString(tag) - final value = 23 - - when: - IastMetricCollector.add(iastMetric, tag, value) - IastMetricCollector.get().prepareMetrics() - action.doIteration(service) - - then: - 1 * service.addMetric({ matches(it, iastMetric, value, ["${iastMetric.tag.name}:${tagString}"]) }) - 0 * _ - } - - void 'test with no metrics'() { - given: - final action = new IastMetricPeriodicAction() - final service = Mock(TelemetryService) - - when: - action.doIteration(service) - - then: - 0 * _ - } - - private static boolean matches(final Metric metric, final IastMetric iastMetric, final long value, final List tags) { - if (metric.namespace != 'iast') { - return false - } - if (metric.metric != iastMetric.name) { - return false - } - if (metric.tags != tags) { - return false - } - return metric.points.first()[1] == value - } -} diff --git a/telemetry/src/test/groovy/datadog/telemetry/metric/MetricPeriodicActionTest.groovy b/telemetry/src/test/groovy/datadog/telemetry/metric/MetricPeriodicActionTest.groovy deleted file mode 100644 index 649260c38d9..00000000000 --- a/telemetry/src/test/groovy/datadog/telemetry/metric/MetricPeriodicActionTest.groovy +++ /dev/null @@ -1,172 +0,0 @@ -package datadog.telemetry.metric - - -import datadog.telemetry.TelemetryService -import datadog.telemetry.api.DistributionSeries -import datadog.telemetry.api.Metric -import datadog.trace.api.telemetry.MetricCollector -import groovy.transform.NamedDelegate -import groovy.transform.NamedVariant -import javax.annotation.Nonnull -import spock.lang.Specification - -class MetricPeriodicActionTest extends Specification { - - void 'test that common metrics are joined before being sent to telemetry #iterationIndex'() { - given: - final service = Mock(TelemetryService) - final MetricCollector metricCollector = Mock(MetricCollector) - final action = new DefaultMetricPeriodicAction(metricCollector) - - when: - action.doIteration(service) - - then: - metricCollector.drainDistributionSeries() >> [] - metricCollector.drain() >> metrics - expected.each { Metric metric -> - 1 * service.addMetric({ it -> - assertMetric(it, metric) - }) - } - 0 * _ - - where: - metrics | expected - [col(counter: 2L)] | [tel(points: [[_, 2L]])] - [col(counter: 2L), col(counter: 6L)] | [tel(points: [[_, 2L], [_, 6L]])] - [col(counter: 2L), col(namespace: 'other', counter: 6L)] | [tel(points: [[_, 2L]]), tel(namespace: 'other', points: [[_, 6L]])] - [col(counter: 2L), col(common: false, counter: 6L)] | [tel(points: [[_, 2L]]), tel(common: false, points: [[_, 6L]])] - [col(counter: 2L), col(metric: 'other', counter: 6L)] | [tel(points: [[_, 2L]]), tel(metric: 'other', points: [[_, 6L]])] - [col(counter: 2L), col(tags: ['a:b'], counter: 6L)] | [tel(points: [[_, 2L]]), tel(tags: ['a:b'], points: [[_, 6L]])] - [col(counter: 2L, tags: ['a:b']), col(counter: 6L, tags: ['c:d'])] | [tel(points: [[_, 2L]], tags: ['a:b']), tel(points: [[_, 6L]], tags: ['c:d'])] - [col(counter: 2L, tags: ['a:b', 'c:d']), col(counter: 6L, tags: ['a:b', 'c:d'])] | [tel(points: [[_, 2L], [_, 6L]], tags: ['a:b', 'c:d'])] - } - - void 'test that common distribution series are joined before being sent to telemetry #iterationIndex'() { - given: - final service = Mock(TelemetryService) - final MetricCollector metricCollector = Mock(MetricCollector) - final action = new DefaultMetricPeriodicAction(metricCollector) - - when: - action.doIteration(service) - - then: - metricCollector.drain() >> [] - metricCollector.drainDistributionSeries() >> distributionSeries - expected.each { DistributionSeries series -> - 1 * service.addDistributionSeries({ it -> - assertDistributionSeries(it, series) - }) - } - 0 * _ - - where: - distributionSeries | expected - [rawSeries(value: 2)] | [series(points: [2])] - [rawSeries(value: 2), rawSeries(value: 6)] | [series(points: [2, 6])] - [rawSeries(value: 2), rawSeries(namespace: 'other', value: 6)] | [series(points: [2]), series(namespace: 'other', points: [6])] - [rawSeries(value: 2), rawSeries(common: false, value: 6)] | [series(points: [2]), series(common: false, points: [6])] - [rawSeries(value: 2), rawSeries(metric: 'other', value: 6)] | [series(points: [2]), series(metric: 'other', points: [6])] - [rawSeries(value: 2), rawSeries(tags: ['a:b'], value: 6)] | [series(points: [2]), series(tags: ['a:b'], points: [6])] - [rawSeries(value: 2, tags: ['a:b']), rawSeries(value: 6, tags: ['c:d'])] | [series(points: [2], tags: ['a:b']), series(points: [6], tags: ['c:d'])] - [rawSeries(value: 2, tags: ['a:b', 'c:d']), rawSeries(value: 6, tags: ['a:b', 'c:d'])] | [series(points: [2, 6], tags: ['a:b', 'c:d'])] - } - - private static MetricCollector.Metric col(final Map metric) { - metric.namespace = metric.namespace ?: 'namespace' - metric.metric = metric.metric ?: 'metric' - metric.common = metric.common == null ? true : metric.common - metric.tags = metric.tags ?: [] - return new MetricCollector.Metric( - metric.namespace as String, - metric.common as boolean, - metric.metric as String, - 'count', - metric.counter as Long, - metric.tags as String[] - ) - } - - @NamedVariant - private static Metric tel(@NamedDelegate final Metric metric) { - metric.namespace = metric.namespace ?: 'namespace' - metric.metric = metric.metric ?: 'metric' - metric.common = metric.common == null ? true : metric.common - metric.tags = metric.tags ?: [] - metric.type = metric.type ?: Metric.TypeEnum.COUNT - return metric - } - - private static MetricCollector.DistributionSeriesPoint rawSeries(final Map point) { - point.namespace = point.namespace ?: 'namespace' - point.metric = point.metric ?: 'metric' - point.common = point.common == null ? true : point.common - point.tags = point.tags ?: [] - return new MetricCollector.DistributionSeriesPoint( - point.metric as String, - point.common as boolean, - point.namespace as String, - point.value as Integer, - point.tags as List - ) - } - - private static DistributionSeries series(final Map distributionSeries) { - return new DistributionSeries() - .namespace(distributionSeries.namespace as String ?: 'namespace') - .metric(distributionSeries.metric as String ?: 'metric') - .common(distributionSeries.common == null ? true : distributionSeries.common as Boolean) - .tags(distributionSeries.tags as List ?: []) - .points(distributionSeries.points as List) - } - - private static boolean assertMetric(Metric received, Metric expected) { - if (!Objects.equals(received.namespace, expected.namespace)) { - return false - } - if (!Objects.equals(received.metric, expected.metric)) { - return false - } - if (!Objects.equals(received.common, expected.common)) { - return false - } - if (!Objects.equals(received.type, expected.type)) { - return false - } - if (!Objects.equals(received.tags, expected.tags)) { - return false - } - if (received.points.size() != expected.points.size()) { - return false - } - final expectedPoints = expected.points.collect { it[1] } - final receivedPoints = received.points.collect { it[1] } - return receivedPoints.containsAll(expectedPoints) - } - - private static boolean assertDistributionSeries(DistributionSeries received, DistributionSeries expected) { - return Objects.equals(received.namespace, expected.namespace) && - Objects.equals(received.metric, expected.metric) && - Objects.equals(received.common, expected.common) && - Objects.equals(received.tags, expected.tags) && - received.points.size() == expected.points.size() && - received.points.containsAll(expected.points) - } - - class DefaultMetricPeriodicAction extends MetricPeriodicAction { - - private final MetricCollector collector - - DefaultMetricPeriodicAction(@Nonnull final MetricCollector collector) { - this.collector = collector - } - - @Override - @Nonnull - MetricCollector collector() { - return collector - } - } -} diff --git a/telemetry/src/test/groovy/datadog/telemetry/metric/OtelEnvMetricPeriodicActionTest.groovy b/telemetry/src/test/groovy/datadog/telemetry/metric/OtelEnvMetricPeriodicActionTest.groovy deleted file mode 100644 index 7f915625be2..00000000000 --- a/telemetry/src/test/groovy/datadog/telemetry/metric/OtelEnvMetricPeriodicActionTest.groovy +++ /dev/null @@ -1,118 +0,0 @@ -package datadog.telemetry.metric - -import datadog.telemetry.TelemetryService -import datadog.telemetry.api.Metric -import spock.lang.Specification - -class OtelEnvMetricPeriodicActionTest extends Specification{ - - void 'test otel env var hiding metric'() { - setup: - final telemetryService = Mock(TelemetryService) - final action = new OtelEnvMetricPeriodicAction() - - when: - action.collector().setHidingOtelEnvVarMetric("otel_service_name","dd_service_name") - action.collector().prepareMetrics() - action.doIteration(telemetryService) - - then: - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'tracers' && - metric.metric == 'otel.env.hiding' && - metric.points[0][1] == 1 && - metric.tags == ['config_opentelemetry:otel_service_name', 'config_datadog:dd_service_name'] && - metric.type == Metric.TypeEnum.COUNT - } ) - 0 * _._ - } - - void 'test otel env var unsupported metric'() { - setup: - - final telemetryService = Mock(TelemetryService) - final action = new OtelEnvMetricPeriodicAction() - - when: - action.collector().setUnsupportedOtelEnvVarMetric("unsupported_env_var") - action.collector().prepareMetrics() - action.doIteration(telemetryService) - - then: - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'tracers' && - metric.metric == 'otel.env.unsupported' && - metric.points[0][1] == 1 && - metric.tags == ['config_opentelemetry:unsupported_env_var'] && - metric.type == Metric.TypeEnum.COUNT - } ) - 0 * _._ - } - - void 'test otel env var invalid metric'() { - setup: - final telemetryService = Mock(TelemetryService) - final action = new OtelEnvMetricPeriodicAction() - - when: - action.collector().setInvalidOtelEnvVarMetric("otel_env_var","dd_env_var") - action.collector().prepareMetrics() - action.doIteration(telemetryService) - - then: - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'tracers' && - metric.metric == 'otel.env.invalid' && - metric.points[0][1] == 1 && - metric.tags == ['config_opentelemetry:otel_env_var', 'config_datadog:dd_env_var'] && - metric.type == Metric.TypeEnum.COUNT - } ) - 0 * _._ - } - - void 'test Otel env var multiple metrics'() { - setup: - final telemetryService = Mock(TelemetryService) - final action = new OtelEnvMetricPeriodicAction() - - when: - action.collector().setInvalidOtelEnvVarMetric("otel_env_var","dd_env_var") - action.collector().setInvalidOtelEnvVarMetric("otel_env_var2","dd_env_var2") - action.collector().setHidingOtelEnvVarMetric("otel_service_name","dd_service_name") - action.collector().setUnsupportedOtelEnvVarMetric("unsupported_env_var") - action.collector().prepareMetrics() - action.doIteration(telemetryService) - - then: - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'tracers' && - metric.metric == 'otel.env.invalid' && - metric.points[0][1] == 1 && - metric.tags == ['config_opentelemetry:otel_env_var', 'config_datadog:dd_env_var'] && - metric.type == Metric.TypeEnum.COUNT - } ) - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'tracers' && - metric.metric == 'otel.env.invalid' && - metric.points[0][1] == 1 && - metric.tags == ['config_opentelemetry:otel_env_var2', 'config_datadog:dd_env_var2'] && - metric.type == Metric.TypeEnum.COUNT - } ) - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'tracers' && - metric.metric == 'otel.env.hiding' && - metric.points[0][1] == 1 && - metric.tags == ['config_opentelemetry:otel_service_name', 'config_datadog:dd_service_name'] && - metric.type == Metric.TypeEnum.COUNT - } ) - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'tracers' && - metric.metric == 'otel.env.unsupported' && - metric.points[0][1] == 1 && - metric.tags == ['config_opentelemetry:unsupported_env_var'] && - metric.type == Metric.TypeEnum.COUNT - } ) - - 0 * _._ - } -} diff --git a/telemetry/src/test/groovy/datadog/telemetry/metric/WafMetricPeriodicActionSpecification.groovy b/telemetry/src/test/groovy/datadog/telemetry/metric/WafMetricPeriodicActionSpecification.groovy deleted file mode 100644 index 9486fc432aa..00000000000 --- a/telemetry/src/test/groovy/datadog/telemetry/metric/WafMetricPeriodicActionSpecification.groovy +++ /dev/null @@ -1,358 +0,0 @@ -package datadog.telemetry.metric - -import datadog.telemetry.TelemetryService -import datadog.telemetry.api.Metric -import datadog.trace.api.telemetry.WafMetricCollector -import datadog.trace.test.util.DDSpecification - -class WafMetricPeriodicActionSpecification extends DDSpecification { - WafMetricPeriodicAction periodicAction = new WafMetricPeriodicAction() - TelemetryService telemetryService = Mock() - - void 'push waf metrics into the telemetry service'() { - setup: - WafMetricCollector.get().wafInit('0.0.0', 'rules_ver_1', true) - WafMetricCollector.get().wafUpdates('rules_ver_2', true) - WafMetricCollector.get().wafUpdates('rules_ver_3', true) - - when: - periodicAction.doIteration(telemetryService) - - then: - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'appsec' && - metric.metric == 'waf.init' && - metric.points[0][1] == 1 && - metric.tags == ['waf_version:0.0.0', 'event_rules_version:rules_ver_1', 'success:true'] - } ) - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'appsec' && - metric.metric == 'waf.updates' && - metric.points[0][1] == 1 && - metric.tags == ['waf_version:0.0.0', 'event_rules_version:rules_ver_2', 'success:true'] - } ) - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'appsec' && - metric.metric == 'waf.updates' && - metric.points[0][1] == 1 && - metric.tags == ['waf_version:0.0.0', 'event_rules_version:rules_ver_3', 'success:true'] - } ) - 0 * _._ - } - - void 'push waf request metrics and push into the telemetry'() { - when: - WafMetricCollector.get().wafInit('0.0.0', 'rules_ver_1', true) - WafMetricCollector.get().wafRequest(false, false, false, false, false, false, false, false) - WafMetricCollector.get().wafRequest(true, false, false, false, false, false, false, false) - WafMetricCollector.get().wafRequest(false, false, false, false, false, false, false, false) - WafMetricCollector.get().wafRequest(false, true, false, false, false, false, false, false) - WafMetricCollector.get().wafRequest(false, false, false, false, false, false, false, false) - WafMetricCollector.get().wafRequest(false, false, false, true, false, false, false, false) - WafMetricCollector.get().wafRequest(false, false, true, false, false, false, false, false) - WafMetricCollector.get().wafRequest(false, false, false, false, false, true, false, false) - WafMetricCollector.get().wafRequest(false, false, false, false, true, false, false, false) - WafMetricCollector.get().wafRequest(false, false, false, false, false, false, true, false) - WafMetricCollector.get().prepareMetrics() - periodicAction.doIteration(telemetryService) - - then: - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'appsec' && - metric.metric == 'waf.init' - } ) - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'appsec' && - metric.metric == 'waf.requests' && - metric.points[0][1] == 3 && - metric.tags == [ - 'waf_version:0.0.0', - 'event_rules_version:rules_ver_1', - 'rule_triggered:false', - 'request_blocked:false', - 'waf_error:false', - 'waf_timeout:false', - 'block_failure:false', - 'rate_limited:false', - 'input_truncated:false', - 'request_excluded:none', - ] - } ) - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'appsec' && - metric.metric == 'waf.requests' && - metric.points[0][1] == 1 && - metric.tags == [ - 'waf_version:0.0.0', - 'event_rules_version:rules_ver_1', - 'rule_triggered:true', - 'request_blocked:false', - 'waf_error:false', - 'waf_timeout:false', - 'block_failure:false', - 'rate_limited:false', - 'input_truncated:false', - 'request_excluded:none', - ] - } ) - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'appsec' && - metric.metric == 'waf.requests' && - metric.points[0][1] == 1 && - metric.tags == [ - 'waf_version:0.0.0', - 'event_rules_version:rules_ver_1', - 'rule_triggered:false', - 'request_blocked:true', - 'waf_error:false', - 'waf_timeout:false', - 'block_failure:false', - 'rate_limited:false', - 'input_truncated:false', - 'request_excluded:none', - ] - } ) - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'appsec' && - metric.metric == 'waf.requests' && - metric.points[0][1] == 1 && - metric.tags == [ - 'waf_version:0.0.0', - 'event_rules_version:rules_ver_1', - 'rule_triggered:false', - 'request_blocked:false', - 'waf_error:false', - 'waf_timeout:true', - 'block_failure:false', - 'rate_limited:false', - 'input_truncated:false', - 'request_excluded:none', - ] - } ) - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'appsec' && - metric.metric == 'waf.requests' && - metric.points[0][1] == 1 && - metric.tags == [ - 'waf_version:0.0.0', - 'event_rules_version:rules_ver_1', - 'rule_triggered:false', - 'request_blocked:false', - 'waf_error:true', - 'waf_timeout:false', - 'block_failure:false', - 'rate_limited:false', - 'input_truncated:false', - 'request_excluded:none', - ] - } ) - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'appsec' && - metric.metric == 'waf.requests' && - metric.points[0][1] == 1 && - metric.tags == [ - 'waf_version:0.0.0', - 'event_rules_version:rules_ver_1', - 'rule_triggered:false', - 'request_blocked:false', - 'waf_error:false', - 'waf_timeout:false', - 'block_failure:true', - 'rate_limited:false', - 'input_truncated:false', - 'request_excluded:none', - ] - } ) - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'appsec' && - metric.metric == 'waf.requests' && - metric.points[0][1] == 1 && - metric.tags == [ - 'waf_version:0.0.0', - 'event_rules_version:rules_ver_1', - 'rule_triggered:false', - 'request_blocked:false', - 'waf_error:false', - 'waf_timeout:false', - 'block_failure:false', - 'rate_limited:true', - 'input_truncated:false', - 'request_excluded:none', - ] - } ) - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'appsec' && - metric.metric == 'waf.requests' && - metric.points[0][1] == 1 && - metric.tags == [ - 'waf_version:0.0.0', - 'event_rules_version:rules_ver_1', - 'rule_triggered:false', - 'request_blocked:false', - 'waf_error:false', - 'waf_timeout:false', - 'block_failure:false', - 'rate_limited:false', - 'input_truncated:true', - 'request_excluded:none', - ] - } ) - 0 * _._ - - when: 'waf.updates happens' - WafMetricCollector.get().wafUpdates('rules_ver_2', true) - WafMetricCollector.get().wafRequest(false, false, false, false, false, false, false, false) - WafMetricCollector.get().wafRequest(true, false, false, false, false, false, false, false) - WafMetricCollector.get().wafRequest(false, true, false, false, false, false, false, false) - WafMetricCollector.get().wafRequest(false, false, false, true, false, false, false, false) - WafMetricCollector.get().wafRequest(false, false, true, false, false, false, false, false) - WafMetricCollector.get().wafRequest(false, false, false, false, false, true, false, false) - WafMetricCollector.get().wafRequest(false, false, false, false, true, false, false, false) - WafMetricCollector.get().wafRequest(false, false, false, false, false, false, true, false) - WafMetricCollector.get().prepareMetrics() - periodicAction.doIteration(telemetryService) - - then: 'following waf.request have a new event_rules_version tag' - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'appsec' && - metric.metric == 'waf.updates' - } ) - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'appsec' && - metric.metric == 'waf.requests' && - metric.points[0][1] == 1 && - metric.tags == [ - 'waf_version:0.0.0', - 'event_rules_version:rules_ver_2', - 'rule_triggered:false', - 'request_blocked:false', - 'waf_error:false', - 'waf_timeout:false', - 'block_failure:false', - 'rate_limited:false', - 'input_truncated:false', - 'request_excluded:none', - ] - } ) - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'appsec' && - metric.metric == 'waf.requests' && - metric.points[0][1] == 1 && - metric.tags == [ - 'waf_version:0.0.0', - 'event_rules_version:rules_ver_2', - 'rule_triggered:true', - 'request_blocked:false', - 'waf_error:false', - 'waf_timeout:false', - 'block_failure:false', - 'rate_limited:false', - 'input_truncated:false', - 'request_excluded:none', - ] - } ) - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'appsec' && - metric.metric == 'waf.requests' && - metric.points[0][1] == 1 && - metric.tags == [ - 'waf_version:0.0.0', - 'event_rules_version:rules_ver_2', - 'rule_triggered:false', - 'request_blocked:true', - 'waf_error:false', - 'waf_timeout:false', - 'block_failure:false', - 'rate_limited:false', - 'input_truncated:false', - 'request_excluded:none', - ] - } ) - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'appsec' && - metric.metric == 'waf.requests' && - metric.points[0][1] == 1 && - metric.tags == [ - 'waf_version:0.0.0', - 'event_rules_version:rules_ver_2', - 'rule_triggered:false', - 'request_blocked:false', - 'waf_error:false', - 'waf_timeout:true', - 'block_failure:false', - 'rate_limited:false', - 'input_truncated:false', - 'request_excluded:none', - ] - } ) - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'appsec' && - metric.metric == 'waf.requests' && - metric.points[0][1] == 1 && - metric.tags == [ - 'waf_version:0.0.0', - 'event_rules_version:rules_ver_2', - 'rule_triggered:false', - 'request_blocked:false', - 'waf_error:true', - 'waf_timeout:false', - 'block_failure:false', - 'rate_limited:false', - 'input_truncated:false', - 'request_excluded:none', - ] - } ) - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'appsec' && - metric.metric == 'waf.requests' && - metric.points[0][1] == 1 && - metric.tags == [ - 'waf_version:0.0.0', - 'event_rules_version:rules_ver_2', - 'rule_triggered:false', - 'request_blocked:false', - 'waf_error:false', - 'waf_timeout:false', - 'block_failure:true', - 'rate_limited:false', - 'input_truncated:false', - 'request_excluded:none', - ] - } ) - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'appsec' && - metric.metric == 'waf.requests' && - metric.points[0][1] == 1 && - metric.tags == [ - 'waf_version:0.0.0', - 'event_rules_version:rules_ver_2', - 'rule_triggered:false', - 'request_blocked:false', - 'waf_error:false', - 'waf_timeout:false', - 'block_failure:false', - 'rate_limited:true', - 'input_truncated:false', - 'request_excluded:none', - ] - } ) - 1 * telemetryService.addMetric( { Metric metric -> - metric.namespace == 'appsec' && - metric.metric == 'waf.requests' && - metric.points[0][1] == 1 && - metric.tags == [ - 'waf_version:0.0.0', - 'event_rules_version:rules_ver_2', - 'rule_triggered:false', - 'request_blocked:false', - 'waf_error:false', - 'waf_timeout:false', - 'block_failure:false', - 'rate_limited:false', - 'input_truncated:true', - 'request_excluded:none', - ] - } ) - 0 * _._ - } -} diff --git a/telemetry/src/test/groovy/datadog/telemetry/products/ProductChangeActionTest.groovy b/telemetry/src/test/groovy/datadog/telemetry/products/ProductChangeActionTest.groovy deleted file mode 100644 index 9a9eec82320..00000000000 --- a/telemetry/src/test/groovy/datadog/telemetry/products/ProductChangeActionTest.groovy +++ /dev/null @@ -1,28 +0,0 @@ -package datadog.telemetry.products - -import datadog.telemetry.TelemetryService -import datadog.trace.api.telemetry.ProductChange -import datadog.trace.api.telemetry.ProductChangeCollector -import spock.lang.Specification - -import static datadog.trace.api.telemetry.ProductChange.ProductType.APPSEC - -class ProductChangeActionTest extends Specification { - ProductChangeAction action = new ProductChangeAction() - TelemetryService telemetryService = Mock() - - void 'push product changes into the telemetry service'() { - setup: - ProductChangeCollector.get().update(new ProductChange().productType(APPSEC).enabled(true )) - - when: - action.doIteration(telemetryService) - - then: - 1 * telemetryService.addProductChange( { ProductChange product -> - product.getProductType() == APPSEC && - product.isEnabled() - } ) - 0 * _ - } -} diff --git a/telemetry/src/test/groovy/datadog/telemetry/rum/RumPeriodicActionTest.groovy b/telemetry/src/test/groovy/datadog/telemetry/rum/RumPeriodicActionTest.groovy deleted file mode 100644 index 08b06a7d064..00000000000 --- a/telemetry/src/test/groovy/datadog/telemetry/rum/RumPeriodicActionTest.groovy +++ /dev/null @@ -1,58 +0,0 @@ -package datadog.telemetry.rum - -import datadog.telemetry.TelemetryService -import datadog.telemetry.api.DistributionSeries -import datadog.telemetry.api.Metric -import datadog.trace.api.rum.RumInjectorMetrics -import datadog.trace.api.rum.RumTelemetryCollector -import spock.lang.Specification - -class RumPeriodicActionTest extends Specification { - TelemetryService telemetryService = Mock() - - void 'push RUM metrics into the telemetry service'() { - setup: - def metricsCollector = new RumInjectorMetrics() - metricsCollector.onInjectionSucceed("3") - metricsCollector.onInjectionFailed("5", "gzip") - metricsCollector.onInjectionResponseSize("3", 1024) - - def periodicAction = new RumPeriodicAction(metricsCollector) - - when: - periodicAction.doIteration(telemetryService) - - then: - 1 * telemetryService.addMetric({ Metric metric -> - metric.namespace == "rum" && - metric.metric == "injection.succeed" && - metric.type == Metric.TypeEnum.COUNT - }) - - 1 * telemetryService.addMetric({ Metric metric -> - metric.namespace == "rum" && - metric.metric == "injection.failed" && - metric.type == Metric.TypeEnum.COUNT - }) - - 1 * telemetryService.addDistributionSeries({ DistributionSeries dist -> - dist.namespace == "rum" && - dist.metric == "injection.response.bytes" - }) - - 0 * _ - } - - void 'push nothing when no metrics collector is set'() { - setup: - def periodicAction = new RumPeriodicAction(RumTelemetryCollector.NO_OP) - - when: - periodicAction.doIteration(telemetryService) - - then: - 0 * telemetryService.addMetric(_) - 0 * telemetryService.addDistributionSeries(_) - 0 * _ - } -} diff --git a/telemetry/src/test/java/datadog/telemetry/integration/IntegrationPeriodicActionTest.java b/telemetry/src/test/java/datadog/telemetry/integration/IntegrationPeriodicActionTest.java new file mode 100644 index 00000000000..91fc3fe4904 --- /dev/null +++ b/telemetry/src/test/java/datadog/telemetry/integration/IntegrationPeriodicActionTest.java @@ -0,0 +1,48 @@ +package datadog.telemetry.integration; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentCaptor.forClass; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import datadog.telemetry.TelemetryService; +import datadog.telemetry.api.Integration; +import datadog.trace.api.telemetry.IntegrationsCollector; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +class IntegrationPeriodicActionTest { + + private final IntegrationPeriodicAction periodicAction = new IntegrationPeriodicAction(); + private final TelemetryService telemetryService = mock(TelemetryService.class); + + @Test + void pushIntegrationsIntoTheTelemetryService() { + IntegrationsCollector.get().update(Arrays.asList("web", "jdbc"), true); + + periodicAction.doIteration(telemetryService); + + ArgumentCaptor captor = forClass(Integration.class); + verify(telemetryService, times(2)).addIntegration(captor.capture()); + verifyNoMoreInteractions(telemetryService); + + List integrations = captor.getAllValues(); + assertMatchingIntegration(integrations, "web"); + assertMatchingIntegration(integrations, "jdbc"); + } + + private void assertMatchingIntegration(List integrations, String name) { + List matches = + integrations.stream() + .filter(integration -> integration.name.equals(name)) + .collect(Collectors.toList()); + assertEquals(1, matches.size(), "expected exactly one match for " + name); + assertTrue(matches.get(0).enabled); + } +} diff --git a/telemetry/src/test/java/datadog/telemetry/metric/ConfigInversionMetricPeriodicActionTest.java b/telemetry/src/test/java/datadog/telemetry/metric/ConfigInversionMetricPeriodicActionTest.java new file mode 100644 index 00000000000..56a2ec80d52 --- /dev/null +++ b/telemetry/src/test/java/datadog/telemetry/metric/ConfigInversionMetricPeriodicActionTest.java @@ -0,0 +1,42 @@ +package datadog.telemetry.metric; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentCaptor.forClass; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import datadog.telemetry.TelemetryService; +import datadog.telemetry.api.Metric; +import datadog.trace.api.telemetry.ConfigInversionMetricCollectorImpl; +import java.util.Arrays; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +class ConfigInversionMetricPeriodicActionTest { + + private final TelemetryService telemetryService = mock(TelemetryService.class); + private final ConfigInversionMetricPeriodicAction action = + new ConfigInversionMetricPeriodicAction(); + private final ConfigInversionMetricCollectorImpl collector = + ConfigInversionMetricCollectorImpl.getInstance(); + + @Test + void testUndocumentedEnvVarMetric() { + collector.setUndocumentedEnvVarMetric("DD_ENV_VAR"); + collector.prepareMetrics(); + action.doIteration(telemetryService); + + ArgumentCaptor captor = forClass(Metric.class); + verify(telemetryService, times(1)).addMetric(captor.capture()); + verifyNoMoreInteractions(telemetryService); + + Metric metric = captor.getValue(); + assertEquals("tracers", metric.getNamespace()); + assertEquals("untracked.config.detected", metric.getMetric()); + assertEquals(1L, metric.getPoints().get(0).get(1).longValue()); + assertEquals(Arrays.asList("config_name:DD_ENV_VAR"), metric.getTags()); + assertEquals(Metric.TypeEnum.COUNT, metric.getType()); + } +} diff --git a/telemetry/src/test/java/datadog/telemetry/metric/CoreMetricsPeriodicActionTest.java b/telemetry/src/test/java/datadog/telemetry/metric/CoreMetricsPeriodicActionTest.java new file mode 100644 index 00000000000..fff8fc144c7 --- /dev/null +++ b/telemetry/src/test/java/datadog/telemetry/metric/CoreMetricsPeriodicActionTest.java @@ -0,0 +1,156 @@ +package datadog.telemetry.metric; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentCaptor.forClass; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import datadog.telemetry.TelemetryService; +import datadog.telemetry.api.Metric; +import datadog.trace.api.metrics.SpanMetricRegistry; +import datadog.trace.api.metrics.SpanMetrics; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +class CoreMetricsPeriodicActionTest { + + private final TelemetryService telemetryService = mock(TelemetryService.class); + private final CoreMetricsPeriodicAction action = new CoreMetricsPeriodicAction(); + private final SpanMetricRegistry spanMetricRegistry = SpanMetricRegistry.getInstance(); + + @Test + void testSpanMetricsWithMultipleOccurrenceEvents() { + SpanMetrics instr1SpanMetric = spanMetricRegistry.get("instr-1"); + SpanMetrics instr2SpanMetric = spanMetricRegistry.get("instr-2"); + + instr1SpanMetric.onSpanCreated(); + instr2SpanMetric.onSpanCreated(); + instr1SpanMetric.onSpanCreated(); + instr2SpanMetric.onSpanCreated(); + instr2SpanMetric.onSpanCreated(); + instr2SpanMetric.onSpanFinished(); + instr2SpanMetric.onSpanFinished(); + instr2SpanMetric.onSpanFinished(); + instr2SpanMetric.onSpanFinished(); + + action.collector().prepareMetrics(); + action.doIteration(telemetryService); + + ArgumentCaptor captor = forClass(Metric.class); + verify(telemetryService, times(3)).addMetric(captor.capture()); + verifyNoMoreInteractions(telemetryService); + + List metrics = captor.getAllValues(); + assertMetric(metrics, "spans_created", "instr-1", 2); + assertMetric(metrics, "spans_created", "instr-2", 3); + assertMetric(metrics, "spans_finished", "instr-2", 4); + } + + @Test + void testSpanMetricsWithInterleavedEvents() { + SpanMetrics instr1SpanMetric = spanMetricRegistry.get("instr-1"); + SpanMetrics instr2SpanMetric = spanMetricRegistry.get("instr-2"); + SpanMetrics instr3SpanMetric = spanMetricRegistry.get("instr-3"); + SpanMetrics instrASpanMetric = spanMetricRegistry.get("instr-a"); + SpanMetrics instrBSpanMetric = spanMetricRegistry.get("instr-b"); + + instr1SpanMetric.onSpanCreated(); + instr2SpanMetric.onSpanCreated(); + instr3SpanMetric.onSpanCreated(); + instr3SpanMetric.onSpanFinished(); + instrASpanMetric.onSpanCreated(); + instrBSpanMetric.onSpanCreated(); + instrBSpanMetric.onSpanFinished(); + instr2SpanMetric.onSpanFinished(); + instrASpanMetric.onSpanFinished(); + instr1SpanMetric.onSpanFinished(); + + action.collector().prepareMetrics(); + action.doIteration(telemetryService); + + ArgumentCaptor captor = forClass(Metric.class); + verify(telemetryService, times(10)).addMetric(captor.capture()); + verifyNoMoreInteractions(telemetryService); + + List metrics = captor.getAllValues(); + assertMetric(metrics, "spans_created", "instr-1", 1); + assertMetric(metrics, "spans_created", "instr-2", 1); + assertMetric(metrics, "spans_created", "instr-3", 1); + assertMetric(metrics, "spans_finished", "instr-3", 1); + assertMetric(metrics, "spans_created", "instr-a", 1); + assertMetric(metrics, "spans_created", "instr-b", 1); + assertMetric(metrics, "spans_finished", "instr-b", 1); + assertMetric(metrics, "spans_finished", "instr-2", 1); + assertMetric(metrics, "spans_finished", "instr-a", 1); + assertMetric(metrics, "spans_finished", "instr-1", 1); + } + + @Test + void testSpanMetrics() { + SpanMetrics instr1SpanMetric = spanMetricRegistry.get("instr-1"); + SpanMetrics instr2SpanMetric = spanMetricRegistry.get("instr-2"); + SpanMetrics instr3SpanMetric = spanMetricRegistry.get("instr-3"); + SpanMetrics instrASpanMetric = spanMetricRegistry.get("instr-a"); + SpanMetrics instrBSpanMetric = spanMetricRegistry.get("instr-b"); + + instr1SpanMetric.onSpanCreated(); + instr2SpanMetric.onSpanCreated(); + instr3SpanMetric.onSpanCreated(); + instr3SpanMetric.onSpanFinished(); + instrASpanMetric.onSpanCreated(); + instrBSpanMetric.onSpanCreated(); + instrBSpanMetric.onSpanFinished(); + instr2SpanMetric.onSpanFinished(); + instrASpanMetric.onSpanFinished(); + instr1SpanMetric.onSpanFinished(); + + action.collector().prepareMetrics(); + action.doIteration(telemetryService); + + ArgumentCaptor captor = forClass(Metric.class); + verify(telemetryService, times(10)).addMetric(captor.capture()); + verifyNoMoreInteractions(telemetryService); + + List metrics = captor.getAllValues(); + assertMetric(metrics, "spans_created", "instr-1", 1); + assertMetric(metrics, "spans_created", "instr-2", 1); + assertMetric(metrics, "spans_created", "instr-3", 1); + assertMetric(metrics, "spans_finished", "instr-3", 1); + assertMetric(metrics, "spans_created", "instr-a", 1); + assertMetric(metrics, "spans_created", "instr-b", 1); + assertMetric(metrics, "spans_finished", "instr-b", 1); + assertMetric(metrics, "spans_finished", "instr-2", 1); + assertMetric(metrics, "spans_finished", "instr-a", 1); + assertMetric(metrics, "spans_finished", "instr-1", 1); + } + + /** + * MetricPeriodicAction aggregates metrics through a HashMap, so the emission order is not + * guaranteed; match each expected metric by its metric name and instrumentation tag rather than + * by capture position. + */ + private void assertMetric( + List metrics, String metricName, String instrumentationName, long count) { + List expectedTags = Arrays.asList("integration_name:" + instrumentationName); + List matches = + metrics.stream() + .filter(metric -> metric.getMetric().equals(metricName)) + .filter(metric -> expectedTags.equals(metric.getTags())) + .collect(Collectors.toList()); + assertEquals( + 1, matches.size(), "expected exactly one match for " + metricName + " " + expectedTags); + + Metric metric = matches.get(0); + assertEquals("tracers", metric.getNamespace()); + assertEquals(true, metric.getCommon()); + assertEquals(1, metric.getPoints().size()); + assertEquals(2, metric.getPoints().get(0).size()); + assertEquals(count, metric.getPoints().get(0).get(1).longValue()); + assertEquals(Metric.TypeEnum.COUNT, metric.getType()); + } +} diff --git a/telemetry/src/test/java/datadog/telemetry/metric/IastMetricPeriodicActionTest.java b/telemetry/src/test/java/datadog/telemetry/metric/IastMetricPeriodicActionTest.java new file mode 100644 index 00000000000..2189035dd78 --- /dev/null +++ b/telemetry/src/test/java/datadog/telemetry/metric/IastMetricPeriodicActionTest.java @@ -0,0 +1,91 @@ +package datadog.telemetry.metric; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentCaptor.forClass; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import datadog.telemetry.TelemetryService; +import datadog.telemetry.api.Metric; +import datadog.trace.api.iast.SourceTypes; +import datadog.trace.api.iast.telemetry.IastMetric; +import datadog.trace.api.iast.telemetry.IastMetricCollector; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +class IastMetricPeriodicActionTest { + + private static final IastMetricCollector ORIGINAL_COLLECTOR = IastMetricCollector.get(); + + private final IastMetricPeriodicAction action = new IastMetricPeriodicAction(); + private final TelemetryService telemetryService = mock(TelemetryService.class); + + @BeforeEach + void setUp() { + IastMetricCollector.register(new IastMetricCollector()); + } + + @AfterEach + void tearDown() { + IastMetricCollector.register(ORIGINAL_COLLECTOR); + } + + @Test + void testMetric() { + IastMetric iastMetric = IastMetric.EXECUTED_TAINTED; + int value = 23; + + IastMetricCollector.add(iastMetric, value); + IastMetricCollector.get().prepareMetrics(); + action.doIteration(telemetryService); + + ArgumentCaptor captor = forClass(Metric.class); + verify(telemetryService, times(1)).addMetric(captor.capture()); + verifyNoMoreInteractions(telemetryService); + + assertMetric(captor.getValue(), iastMetric, value, Collections.emptyList()); + } + + @Test + void testTaggedMetric() { + IastMetric iastMetric = IastMetric.INSTRUMENTED_SOURCE; + byte tag = SourceTypes.REQUEST_PARAMETER_VALUE; + String tagString = SourceTypes.toString(tag); + int value = 23; + + IastMetricCollector.add(iastMetric, tag, value); + IastMetricCollector.get().prepareMetrics(); + action.doIteration(telemetryService); + + ArgumentCaptor captor = forClass(Metric.class); + verify(telemetryService, times(1)).addMetric(captor.capture()); + verifyNoMoreInteractions(telemetryService); + + assertMetric( + captor.getValue(), + iastMetric, + value, + Arrays.asList(iastMetric.getTag().getName() + ":" + tagString)); + } + + @Test + void testWithNoMetrics() { + action.doIteration(telemetryService); + + verifyNoMoreInteractions(telemetryService); + } + + private void assertMetric(Metric metric, IastMetric iastMetric, long value, List tags) { + assertEquals("iast", metric.getNamespace()); + assertEquals(iastMetric.getName(), metric.getMetric()); + assertEquals(tags, metric.getTags()); + assertEquals(value, metric.getPoints().get(0).get(1).longValue()); + } +} diff --git a/telemetry/src/test/java/datadog/telemetry/metric/MetricPeriodicActionTest.java b/telemetry/src/test/java/datadog/telemetry/metric/MetricPeriodicActionTest.java new file mode 100644 index 00000000000..9041de42794 --- /dev/null +++ b/telemetry/src/test/java/datadog/telemetry/metric/MetricPeriodicActionTest.java @@ -0,0 +1,317 @@ +package datadog.telemetry.metric; + +import static java.util.Collections.emptyList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentCaptor.forClass; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import datadog.telemetry.TelemetryService; +import datadog.telemetry.api.DistributionSeries; +import datadog.telemetry.api.Metric; +import datadog.trace.api.telemetry.MetricCollector; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import org.mockito.ArgumentCaptor; +import org.tabletest.junit.TableTest; +import org.tabletest.junit.TypeConverter; + +public class MetricPeriodicActionTest { + + @TableTest({ + "scenario | metrics | expected ", + "single raw metric produces a single telemetry point | ['col(counter: 2)'] | ['tel(points: [2])'] ", + "two raw metrics with the same identity are joined | ['col(counter: 2)', 'col(counter: 6)'] | ['tel(points: [2, 6])'] ", + "different namespace produces a distinct telemetry metric | ['col(counter: 2)', 'col(namespace: \"other\", counter: 6)'] | ['tel(points: [2])', 'tel(namespace: \"other\", points: [6])'] ", + "different common flag produces a distinct telemetry metric | ['col(counter: 2)', 'col(common: false, counter: 6)'] | ['tel(points: [2])', 'tel(common: false, points: [6])'] ", + "different metric name produces a distinct telemetry metric | ['col(counter: 2)', 'col(metric: \"other\", counter: 6)'] | ['tel(points: [2])', 'tel(metric: \"other\", points: [6])'] ", + "different tags produce a distinct telemetry metric | ['col(counter: 2)', 'col(tags: [a:b], counter: 6)'] | ['tel(points: [2])', 'tel(tags: [a:b], points: [6])'] ", + "distinct tags on both raw metrics stay distinct | ['col(counter: 2, tags: [a:b])', 'col(counter: 6, tags: [c:d])'] | ['tel(points: [2], tags: [a:b])', 'tel(points: [6], tags: [c:d])']", + "same tags on both raw metrics are joined | ['col(counter: 2, tags: [a:b, c:d])', 'col(counter: 6, tags: [a:b, c:d])'] | ['tel(points: [2, 6], tags: [a:b, c:d])'] " + }) + void testCommonMetricsAreJoinedBeforeBeingSentToTelemetry( + List metrics, List expected) { + TelemetryService telemetryService = mock(TelemetryService.class); + MetricCollector metricCollector = mock(MetricCollector.class); + when(metricCollector.drain()).thenReturn(metrics); + when(metricCollector.drainDistributionSeries()).thenReturn(emptyList()); + DefaultMetricPeriodicAction action = new DefaultMetricPeriodicAction(metricCollector); + + action.doIteration(telemetryService); + + ArgumentCaptor captor = forClass(Metric.class); + verify(telemetryService, times(expected.size())).addMetric(captor.capture()); + verifyNoMoreInteractions(telemetryService); + + List actualMetrics = captor.getAllValues(); + for (ExpectedMetric expectedMetric : expected) { + assertMatchingMetric(actualMetrics, expectedMetric); + } + } + + @TableTest({ + "scenario | distributionSeries | expected ", + "single raw series point produces a single telemetry series | ['rawSeries(value: 2)'] | ['series(points: [2])'] ", + "two raw series points with the same identity are joined | ['rawSeries(value: 2)', 'rawSeries(value: 6)'] | ['series(points: [2, 6])'] ", + "different namespace produces a distinct telemetry series | ['rawSeries(value: 2)', 'rawSeries(namespace: \"other\", value: 6)'] | ['series(points: [2])', 'series(namespace: \"other\", points: [6])'] ", + "different common flag produces a distinct telemetry series | ['rawSeries(value: 2)', 'rawSeries(common: false, value: 6)'] | ['series(points: [2])', 'series(common: false, points: [6])'] ", + "different metric name produces a distinct telemetry series | ['rawSeries(value: 2)', 'rawSeries(metric: \"other\", value: 6)'] | ['series(points: [2])', 'series(metric: \"other\", points: [6])'] ", + "different tags produce a distinct telemetry series | ['rawSeries(value: 2)', 'rawSeries(tags: [a:b], value: 6)'] | ['series(points: [2])', 'series(tags: [a:b], points: [6])'] ", + "distinct tags on both raw series points stay distinct | ['rawSeries(value: 2, tags: [a:b])', 'rawSeries(value: 6, tags: [c:d])'] | ['series(points: [2], tags: [a:b])', 'series(points: [6], tags: [c:d])']", + "same tags on both raw series points are joined | ['rawSeries(value: 2, tags: [a:b, c:d])', 'rawSeries(value: 6, tags: [a:b, c:d])'] | ['series(points: [2, 6], tags: [a:b, c:d])'] " + }) + void testCommonDistributionSeriesAreJoinedBeforeBeingSentToTelemetry( + List distributionSeries, + List expected) { + TelemetryService telemetryService = mock(TelemetryService.class); + MetricCollector metricCollector = mock(MetricCollector.class); + when(metricCollector.drain()).thenReturn(emptyList()); + when(metricCollector.drainDistributionSeries()).thenReturn(distributionSeries); + DefaultMetricPeriodicAction action = new DefaultMetricPeriodicAction(metricCollector); + + action.doIteration(telemetryService); + + ArgumentCaptor captor = forClass(DistributionSeries.class); + verify(telemetryService, times(expected.size())).addDistributionSeries(captor.capture()); + verifyNoMoreInteractions(telemetryService); + + List actualSeries = captor.getAllValues(); + for (ExpectedSeries expectedSeries : expected) { + assertMatchingSeries(actualSeries, expectedSeries); + } + } + + /** Parses a {@code col(key: value, ...)} cell into the raw metric it describes. */ + @TypeConverter + public static MetricCollector.Metric col(String token) { + Map args = parseArgs(token); + String namespace = stringArg(args, "namespace", "namespace"); + String metricName = stringArg(args, "metric", "metric"); + boolean common = booleanArg(args, "common", true); + List tags = tagsArg(args); + long counter = Long.parseLong(args.get("counter")); + return new MetricCollector.Metric(namespace, common, metricName, "count", counter, tags); + } + + /** Parses a {@code tel(key: value, ...)} cell into the expected telemetry metric. */ + @TypeConverter + public static ExpectedMetric tel(String token) { + Map args = parseArgs(token); + String namespace = stringArg(args, "namespace", "namespace"); + String metricName = stringArg(args, "metric", "metric"); + boolean common = booleanArg(args, "common", true); + List tags = tagsArg(args); + List points = longListArg(args, "points"); + return new ExpectedMetric(namespace, metricName, common, tags, points); + } + + /** Parses a {@code rawSeries(key: value, ...)} cell into the raw distribution series point. */ + @TypeConverter + public static MetricCollector.DistributionSeriesPoint rawSeries(String token) { + Map args = parseArgs(token); + String namespace = stringArg(args, "namespace", "namespace"); + String metricName = stringArg(args, "metric", "metric"); + boolean common = booleanArg(args, "common", true); + List tags = tagsArg(args); + int value = Integer.parseInt(args.get("value")); + return new MetricCollector.DistributionSeriesPoint(metricName, common, namespace, value, tags); + } + + /** Parses a {@code series(key: value, ...)} cell into the expected telemetry series. */ + @TypeConverter + public static ExpectedSeries series(String token) { + Map args = parseArgs(token); + String namespace = stringArg(args, "namespace", "namespace"); + String metricName = stringArg(args, "metric", "metric"); + boolean common = booleanArg(args, "common", true); + List tags = tagsArg(args); + List points = intListArg(args, "points"); + return new ExpectedSeries(namespace, metricName, common, tags, points); + } + + /** + * Splits the {@code (key: value, ...)} argument list of a builder cell such as {@code col(...)} + * into a name-to-raw-value map, honouring nested {@code [...]} tag/point lists. + */ + private static Map parseArgs(String token) { + String content = token.substring(token.indexOf('(') + 1, token.lastIndexOf(')')).trim(); + Map args = new LinkedHashMap<>(); + if (content.isEmpty()) { + return args; + } + int depth = 0; + int start = 0; + for (int i = 0; i <= content.length(); i++) { + char c = i == content.length() ? ',' : content.charAt(i); + if (c == '[') { + depth++; + } else if (c == ']') { + depth--; + } else if (c == ',' && depth == 0) { + String pair = content.substring(start, i).trim(); + int colon = pair.indexOf(':'); + args.put(pair.substring(0, colon).trim(), pair.substring(colon + 1).trim()); + start = i + 1; + } + } + return args; + } + + private static String stringArg(Map args, String key, String defaultValue) { + String raw = args.get(key); + return raw == null ? defaultValue : unquote(raw); + } + + private static String unquote(String value) { + return value.charAt(0) == '"' ? value.substring(1, value.length() - 1) : value; + } + + private static boolean booleanArg(Map args, String key, boolean defaultValue) { + String raw = args.get(key); + return raw == null ? defaultValue : Boolean.parseBoolean(raw); + } + + private static List tagsArg(Map args) { + String raw = args.get("tags"); + return raw == null ? emptyList() : splitBracketedList(raw); + } + + private static List splitBracketedList(String raw) { + String inner = raw.substring(raw.indexOf('[') + 1, raw.lastIndexOf(']')).trim(); + if (inner.isEmpty()) { + return emptyList(); + } + List values = new ArrayList<>(); + for (String part : inner.split(",")) { + values.add(part.trim()); + } + return values; + } + + private static List longListArg(Map args, String key) { + List values = new ArrayList<>(); + for (String value : splitBracketedList(args.get(key))) { + values.add(Long.parseLong(value)); + } + return values; + } + + private static List intListArg(Map args, String key) { + List values = new ArrayList<>(); + for (String value : splitBracketedList(args.get(key))) { + values.add(Integer.parseInt(value)); + } + return values; + } + + private static void assertMatchingMetric(List actualMetrics, ExpectedMetric expected) { + List matches = + actualMetrics.stream() + .filter(metric -> metric.getNamespace().equals(expected.namespace)) + .filter(metric -> metric.getMetric().equals(expected.metricName)) + .filter(metric -> metric.getCommon().equals(expected.common)) + .filter(metric -> metric.getTags().equals(expected.tags)) + .collect(Collectors.toList()); + assertEquals( + 1, + matches.size(), + "expected exactly one match for " + + expected.namespace + + "/" + + expected.metricName + + " " + + expected.tags); + + Metric metric = matches.get(0); + assertEquals(Metric.TypeEnum.COUNT, metric.getType()); + List actualPoints = + metric.getPoints().stream() + .map(point -> point.get(1).longValue()) + .collect(Collectors.toList()); + assertEquals(expected.points.size(), actualPoints.size()); + assertEquals(true, actualPoints.containsAll(expected.points)); + } + + private static void assertMatchingSeries( + List actualSeries, ExpectedSeries expected) { + List matches = + actualSeries.stream() + .filter(series -> series.getNamespace().equals(expected.namespace)) + .filter(series -> series.getMetric().equals(expected.metricName)) + .filter(series -> series.getCommon().equals(expected.common)) + .filter(series -> series.getTags().equals(expected.tags)) + .collect(Collectors.toList()); + assertEquals( + 1, + matches.size(), + "expected exactly one match for " + + expected.namespace + + "/" + + expected.metricName + + " " + + expected.tags); + + DistributionSeries series = matches.get(0); + assertEquals(expected.points.size(), series.getPoints().size()); + assertEquals(true, series.getPoints().containsAll(expected.points)); + } + + private static class ExpectedMetric { + private final String namespace; + private final String metricName; + private final boolean common; + private final List tags; + private final List points; + + ExpectedMetric( + String namespace, String metricName, boolean common, List tags, List points) { + this.namespace = namespace; + this.metricName = metricName; + this.common = common; + this.tags = tags; + this.points = points; + } + } + + private static class ExpectedSeries { + private final String namespace; + private final String metricName; + private final boolean common; + private final List tags; + private final List points; + + ExpectedSeries( + String namespace, + String metricName, + boolean common, + List tags, + List points) { + this.namespace = namespace; + this.metricName = metricName; + this.common = common; + this.tags = tags; + this.points = points; + } + } + + private static class DefaultMetricPeriodicAction extends MetricPeriodicAction { + private final MetricCollector collector; + + DefaultMetricPeriodicAction(@Nonnull MetricCollector collector) { + this.collector = collector; + } + + @Override + @Nonnull + public MetricCollector collector() { + return collector; + } + } +} diff --git a/telemetry/src/test/java/datadog/telemetry/metric/OtelEnvMetricPeriodicActionTest.java b/telemetry/src/test/java/datadog/telemetry/metric/OtelEnvMetricPeriodicActionTest.java new file mode 100644 index 00000000000..4d825fa5311 --- /dev/null +++ b/telemetry/src/test/java/datadog/telemetry/metric/OtelEnvMetricPeriodicActionTest.java @@ -0,0 +1,128 @@ +package datadog.telemetry.metric; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentCaptor.forClass; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import datadog.telemetry.TelemetryService; +import datadog.telemetry.api.Metric; +import datadog.trace.api.telemetry.OtelEnvMetricCollectorImpl; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +class OtelEnvMetricPeriodicActionTest { + + private final TelemetryService telemetryService = mock(TelemetryService.class); + private final OtelEnvMetricPeriodicAction action = new OtelEnvMetricPeriodicAction(); + private final OtelEnvMetricCollectorImpl collector = OtelEnvMetricCollectorImpl.getInstance(); + + @Test + void testOtelEnvVarHidingMetric() { + collector.setHidingOtelEnvVarMetric("otel_service_name", "dd_service_name"); + collector.prepareMetrics(); + action.doIteration(telemetryService); + + ArgumentCaptor captor = forClass(Metric.class); + verify(telemetryService, times(1)).addMetric(captor.capture()); + verifyNoMoreInteractions(telemetryService); + + Metric metric = captor.getValue(); + assertEquals("tracers", metric.getNamespace()); + assertEquals("otel.env.hiding", metric.getMetric()); + assertEquals(1L, metric.getPoints().get(0).get(1).longValue()); + assertEquals( + Arrays.asList("config_opentelemetry:otel_service_name", "config_datadog:dd_service_name"), + metric.getTags()); + assertEquals(Metric.TypeEnum.COUNT, metric.getType()); + } + + @Test + void testOtelEnvVarUnsupportedMetric() { + collector.setUnsupportedOtelEnvVarMetric("unsupported_env_var"); + collector.prepareMetrics(); + action.doIteration(telemetryService); + + ArgumentCaptor captor = forClass(Metric.class); + verify(telemetryService, times(1)).addMetric(captor.capture()); + verifyNoMoreInteractions(telemetryService); + + Metric metric = captor.getValue(); + assertEquals("tracers", metric.getNamespace()); + assertEquals("otel.env.unsupported", metric.getMetric()); + assertEquals(1L, metric.getPoints().get(0).get(1).longValue()); + assertEquals(Arrays.asList("config_opentelemetry:unsupported_env_var"), metric.getTags()); + assertEquals(Metric.TypeEnum.COUNT, metric.getType()); + } + + @Test + void testOtelEnvVarInvalidMetric() { + collector.setInvalidOtelEnvVarMetric("otel_env_var", "dd_env_var"); + collector.prepareMetrics(); + action.doIteration(telemetryService); + + ArgumentCaptor captor = forClass(Metric.class); + verify(telemetryService, times(1)).addMetric(captor.capture()); + verifyNoMoreInteractions(telemetryService); + + Metric metric = captor.getValue(); + assertEquals("tracers", metric.getNamespace()); + assertEquals("otel.env.invalid", metric.getMetric()); + assertEquals(1L, metric.getPoints().get(0).get(1).longValue()); + assertEquals( + Arrays.asList("config_opentelemetry:otel_env_var", "config_datadog:dd_env_var"), + metric.getTags()); + assertEquals(Metric.TypeEnum.COUNT, metric.getType()); + } + + @Test + void testOtelEnvVarMultipleMetrics() { + collector.setInvalidOtelEnvVarMetric("otel_env_var", "dd_env_var"); + collector.setInvalidOtelEnvVarMetric("otel_env_var2", "dd_env_var2"); + collector.setHidingOtelEnvVarMetric("otel_service_name", "dd_service_name"); + collector.setUnsupportedOtelEnvVarMetric("unsupported_env_var"); + collector.prepareMetrics(); + action.doIteration(telemetryService); + + ArgumentCaptor captor = forClass(Metric.class); + verify(telemetryService, times(4)).addMetric(captor.capture()); + verifyNoMoreInteractions(telemetryService); + + // MetricPeriodicAction aggregates metrics through a HashMap, so the emission order is not + // guaranteed; match each expected metric by its tags rather than by capture position. + List metrics = captor.getAllValues(); + assertMetric( + metrics, + "otel.env.invalid", + Arrays.asList("config_opentelemetry:otel_env_var", "config_datadog:dd_env_var")); + assertMetric( + metrics, + "otel.env.invalid", + Arrays.asList("config_opentelemetry:otel_env_var2", "config_datadog:dd_env_var2")); + assertMetric( + metrics, + "otel.env.hiding", + Arrays.asList("config_opentelemetry:otel_service_name", "config_datadog:dd_service_name")); + assertMetric( + metrics, "otel.env.unsupported", Arrays.asList("config_opentelemetry:unsupported_env_var")); + } + + private void assertMetric(List metrics, String metricName, List tags) { + List matches = + metrics.stream() + .filter(metric -> metric.getMetric().equals(metricName)) + .filter(metric -> tags.equals(metric.getTags())) + .collect(Collectors.toList()); + assertEquals(1, matches.size(), "expected exactly one match for " + metricName + " " + tags); + + Metric metric = matches.get(0); + assertEquals("tracers", metric.getNamespace()); + assertEquals(1L, metric.getPoints().get(0).get(1).longValue()); + assertEquals(Metric.TypeEnum.COUNT, metric.getType()); + } +} diff --git a/telemetry/src/test/java/datadog/telemetry/metric/WafMetricPeriodicActionTest.java b/telemetry/src/test/java/datadog/telemetry/metric/WafMetricPeriodicActionTest.java new file mode 100644 index 00000000000..6340d95bdc0 --- /dev/null +++ b/telemetry/src/test/java/datadog/telemetry/metric/WafMetricPeriodicActionTest.java @@ -0,0 +1,196 @@ +package datadog.telemetry.metric; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentCaptor.forClass; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import datadog.telemetry.TelemetryService; +import datadog.telemetry.api.Metric; +import datadog.trace.api.telemetry.WafMetricCollector; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +class WafMetricPeriodicActionTest { + + private final WafMetricPeriodicAction periodicAction = new WafMetricPeriodicAction(); + private final TelemetryService telemetryService = mock(TelemetryService.class); + + @Test + void pushWafMetricsIntoTheTelemetryService() { + WafMetricCollector.get().wafInit("0.0.0", "rules_ver_1", true); + WafMetricCollector.get().wafUpdates("rules_ver_2", true); + WafMetricCollector.get().wafUpdates("rules_ver_3", true); + + periodicAction.doIteration(telemetryService); + + ArgumentCaptor captor = forClass(Metric.class); + verify(telemetryService, times(3)).addMetric(captor.capture()); + verifyNoMoreInteractions(telemetryService); + + List metrics = captor.getAllValues(); + assertMetric( + metrics, + "waf.init", + 1L, + Arrays.asList("waf_version:0.0.0", "event_rules_version:rules_ver_1", "success:true")); + assertMetric( + metrics, + "waf.updates", + 1L, + Arrays.asList("waf_version:0.0.0", "event_rules_version:rules_ver_2", "success:true")); + assertMetric( + metrics, + "waf.updates", + 1L, + Arrays.asList("waf_version:0.0.0", "event_rules_version:rules_ver_3", "success:true")); + } + + @Test + void pushWafRequestMetricsAndPushIntoTheTelemetry() { + WafMetricCollector.get().wafInit("0.0.0", "rules_ver_1", true); + WafMetricCollector.get().wafRequest(false, false, false, false, false, false, false, false); + WafMetricCollector.get().wafRequest(true, false, false, false, false, false, false, false); + WafMetricCollector.get().wafRequest(false, false, false, false, false, false, false, false); + WafMetricCollector.get().wafRequest(false, true, false, false, false, false, false, false); + WafMetricCollector.get().wafRequest(false, false, false, false, false, false, false, false); + WafMetricCollector.get().wafRequest(false, false, false, true, false, false, false, false); + WafMetricCollector.get().wafRequest(false, false, true, false, false, false, false, false); + WafMetricCollector.get().wafRequest(false, false, false, false, false, true, false, false); + WafMetricCollector.get().wafRequest(false, false, false, false, true, false, false, false); + WafMetricCollector.get().wafRequest(false, false, false, false, false, false, true, false); + WafMetricCollector.get().prepareMetrics(); + periodicAction.doIteration(telemetryService); + + ArgumentCaptor firstBatchCaptor = forClass(Metric.class); + verify(telemetryService, times(9)).addMetric(firstBatchCaptor.capture()); + verifyNoMoreInteractions(telemetryService); + + List firstBatch = firstBatchCaptor.getAllValues(); + assertMetric(firstBatch, "waf.init", 1L, null); + assertRequestMetric( + firstBatch, + 3L, + requestTags("rules_ver_1", false, false, false, false, false, false, false)); + assertRequestMetric( + firstBatch, 1L, requestTags("rules_ver_1", true, false, false, false, false, false, false)); + assertRequestMetric( + firstBatch, 1L, requestTags("rules_ver_1", false, true, false, false, false, false, false)); + assertRequestMetric( + firstBatch, 1L, requestTags("rules_ver_1", false, false, true, false, false, false, false)); + assertRequestMetric( + firstBatch, 1L, requestTags("rules_ver_1", false, false, false, true, false, false, false)); + assertRequestMetric( + firstBatch, 1L, requestTags("rules_ver_1", false, false, false, false, true, false, false)); + assertRequestMetric( + firstBatch, 1L, requestTags("rules_ver_1", false, false, false, false, false, true, false)); + assertRequestMetric( + firstBatch, 1L, requestTags("rules_ver_1", false, false, false, false, false, false, true)); + + clearInvocations(telemetryService); + + // waf.updates happens + WafMetricCollector.get().wafUpdates("rules_ver_2", true); + WafMetricCollector.get().wafRequest(false, false, false, false, false, false, false, false); + WafMetricCollector.get().wafRequest(true, false, false, false, false, false, false, false); + WafMetricCollector.get().wafRequest(false, true, false, false, false, false, false, false); + WafMetricCollector.get().wafRequest(false, false, false, true, false, false, false, false); + WafMetricCollector.get().wafRequest(false, false, true, false, false, false, false, false); + WafMetricCollector.get().wafRequest(false, false, false, false, false, true, false, false); + WafMetricCollector.get().wafRequest(false, false, false, false, true, false, false, false); + WafMetricCollector.get().wafRequest(false, false, false, false, false, false, true, false); + WafMetricCollector.get().prepareMetrics(); + periodicAction.doIteration(telemetryService); + + // following waf.request have a new event_rules_version tag + ArgumentCaptor secondBatchCaptor = forClass(Metric.class); + verify(telemetryService, times(9)).addMetric(secondBatchCaptor.capture()); + verifyNoMoreInteractions(telemetryService); + + List secondBatch = secondBatchCaptor.getAllValues(); + assertMetric(secondBatch, "waf.updates", 1L, null); + assertRequestMetric( + secondBatch, + 1L, + requestTags("rules_ver_2", false, false, false, false, false, false, false)); + assertRequestMetric( + secondBatch, + 1L, + requestTags("rules_ver_2", true, false, false, false, false, false, false)); + assertRequestMetric( + secondBatch, + 1L, + requestTags("rules_ver_2", false, true, false, false, false, false, false)); + assertRequestMetric( + secondBatch, + 1L, + requestTags("rules_ver_2", false, false, true, false, false, false, false)); + assertRequestMetric( + secondBatch, + 1L, + requestTags("rules_ver_2", false, false, false, true, false, false, false)); + assertRequestMetric( + secondBatch, + 1L, + requestTags("rules_ver_2", false, false, false, false, true, false, false)); + assertRequestMetric( + secondBatch, + 1L, + requestTags("rules_ver_2", false, false, false, false, false, true, false)); + assertRequestMetric( + secondBatch, + 1L, + requestTags("rules_ver_2", false, false, false, false, false, false, true)); + } + + private void assertRequestMetric(List metrics, long expectedCount, List tags) { + assertMetric(metrics, "waf.requests", expectedCount, tags); + } + + /** + * Metrics with equal tags are aggregated into a single point by production code, but ones with + * distinct tags land in a HashMap keyed by content, so their relative order in the captured list + * is not guaranteed. Match by (metric name, tags) rather than by position. + */ + private void assertMetric( + List metrics, String metricName, long expectedCount, List tags) { + List matches = + metrics.stream() + .filter(metric -> metric.getMetric().equals(metricName)) + .filter(metric -> tags == null || tags.equals(metric.getTags())) + .collect(Collectors.toList()); + assertEquals(1, matches.size(), "expected exactly one match for " + metricName + " " + tags); + + Metric metric = matches.get(0); + assertEquals("appsec", metric.getNamespace()); + assertEquals(expectedCount, metric.getPoints().get(0).get(1).longValue()); + } + + private List requestTags( + String rulesVersion, + boolean ruleTriggered, + boolean requestBlocked, + boolean wafError, + boolean wafTimeout, + boolean blockFailure, + boolean rateLimited, + boolean inputTruncated) { + return Arrays.asList( + "waf_version:0.0.0", + "event_rules_version:" + rulesVersion, + "rule_triggered:" + ruleTriggered, + "request_blocked:" + requestBlocked, + "waf_error:" + wafError, + "waf_timeout:" + wafTimeout, + "block_failure:" + blockFailure, + "rate_limited:" + rateLimited, + "input_truncated:" + inputTruncated, + "request_excluded:none"); + } +} diff --git a/telemetry/src/test/java/datadog/telemetry/products/ProductChangeActionTest.java b/telemetry/src/test/java/datadog/telemetry/products/ProductChangeActionTest.java new file mode 100644 index 00000000000..d9fcdfad876 --- /dev/null +++ b/telemetry/src/test/java/datadog/telemetry/products/ProductChangeActionTest.java @@ -0,0 +1,37 @@ +package datadog.telemetry.products; + +import static datadog.trace.api.telemetry.ProductChange.ProductType.APPSEC; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import datadog.telemetry.TelemetryService; +import datadog.trace.api.telemetry.ProductChange; +import datadog.trace.api.telemetry.ProductChangeCollector; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +class ProductChangeActionTest { + + private final ProductChangeAction action = new ProductChangeAction(); + private final TelemetryService telemetryService = mock(TelemetryService.class); + + @Test + void pushProductChangesIntoTheTelemetryService() { + ProductChangeCollector.get().update(new ProductChange().productType(APPSEC).enabled(true)); + + action.doIteration(telemetryService); + + ArgumentCaptor productChangeCaptor = + ArgumentCaptor.forClass(ProductChange.class); + verify(telemetryService, times(1)).addProductChange(productChangeCaptor.capture()); + ProductChange productChange = productChangeCaptor.getValue(); + assertEquals(APPSEC, productChange.getProductType()); + assertTrue(productChange.isEnabled()); + + verifyNoMoreInteractions(telemetryService); + } +} diff --git a/telemetry/src/test/java/datadog/telemetry/rum/RumPeriodicActionTest.java b/telemetry/src/test/java/datadog/telemetry/rum/RumPeriodicActionTest.java new file mode 100644 index 00000000000..21c4128c895 --- /dev/null +++ b/telemetry/src/test/java/datadog/telemetry/rum/RumPeriodicActionTest.java @@ -0,0 +1,66 @@ +package datadog.telemetry.rum; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import datadog.telemetry.TelemetryService; +import datadog.telemetry.api.DistributionSeries; +import datadog.telemetry.api.Metric; +import datadog.trace.api.rum.RumInjectorMetrics; +import datadog.trace.api.rum.RumTelemetryCollector; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +class RumPeriodicActionTest { + + private final TelemetryService telemetryService = mock(TelemetryService.class); + + @Test + void pushRumMetricsIntoTheTelemetryService() { + RumInjectorMetrics metricsCollector = new RumInjectorMetrics(); + metricsCollector.onInjectionSucceed("3"); + metricsCollector.onInjectionFailed("5", "gzip"); + metricsCollector.onInjectionResponseSize("3", 1024); + + RumPeriodicAction periodicAction = new RumPeriodicAction(metricsCollector); + + periodicAction.doIteration(telemetryService); + + ArgumentCaptor metricCaptor = ArgumentCaptor.forClass(Metric.class); + verify(telemetryService, times(2)).addMetric(metricCaptor.capture()); + Metric succeedMetric = metricCaptor.getAllValues().get(0); + assertEquals("rum", succeedMetric.getNamespace()); + assertEquals("injection.succeed", succeedMetric.getMetric()); + assertEquals(Metric.TypeEnum.COUNT, succeedMetric.getType()); + + Metric failedMetric = metricCaptor.getAllValues().get(1); + assertEquals("rum", failedMetric.getNamespace()); + assertEquals("injection.failed", failedMetric.getMetric()); + assertEquals(Metric.TypeEnum.COUNT, failedMetric.getType()); + + ArgumentCaptor distributionCaptor = + ArgumentCaptor.forClass(DistributionSeries.class); + verify(telemetryService, times(1)).addDistributionSeries(distributionCaptor.capture()); + DistributionSeries distribution = distributionCaptor.getValue(); + assertEquals("rum", distribution.getNamespace()); + assertEquals("injection.response.bytes", distribution.getMetric()); + + verifyNoMoreInteractions(telemetryService); + } + + @Test + void pushNothingWhenNoMetricsCollectorIsSet() { + RumPeriodicAction periodicAction = new RumPeriodicAction(RumTelemetryCollector.NO_OP); + + periodicAction.doIteration(telemetryService); + + verify(telemetryService, never()).addMetric(any()); + verify(telemetryService, never()).addDistributionSeries(any()); + verifyNoMoreInteractions(telemetryService); + } +}