diff --git a/.gitlab/e2e.yml b/.gitlab/e2e.yml index 55867ca6859..dadd1befe23 100644 --- a/.gitlab/e2e.yml +++ b/.gitlab/e2e.yml @@ -18,14 +18,17 @@ GROUND_TRUTH_ALPINE_IMAGE: registry.ddbuild.io/alpine:latest GROUND_TRUTH_MILLSTONE__IMAGE: ${SALUKI_IMAGE_REPO_BASE}/millstone:${CI_COMMIT_SHA} GROUND_TRUTH_DATADOG_INTAKE__IMAGE: ${SALUKI_IMAGE_REPO_BASE}/datadog-intake:${CI_COMMIT_SHA} - GROUND_TRUTH_COMPARISON__IMAGE: ${SALUKI_IMAGE_REPO_BASE}/bundled-agent-adp:${CI_COMMIT_SHA} script: - make build-ground-truth - target/release/ground-truth ./test/correctness/${CORRECTNESS_TEST_CASE}/config.yaml -# Mixin for correctness tests where baseline is the CI-built bundled ADP image. -# Tests with a different baseline (e.g. DDOT) should NOT extend this and instead -# let config.yaml specify the baseline image directly. +# Mixin that overrides the comparison image to the CI-built ADP image. +# Tests comparing against external agent-dev images should NOT extend this. +.test-correctness-adp-comparison: + variables: + GROUND_TRUTH_COMPARISON__IMAGE: ${SALUKI_IMAGE_REPO_BASE}/bundled-agent-adp:${CI_COMMIT_SHA} + +# Mixin that overrides the baseline image to the CI-built ADP image. .test-correctness-adp-baseline: variables: GROUND_TRUTH_BASELINE__IMAGE: ${SALUKI_IMAGE_REPO_BASE}/bundled-agent-adp:${CI_COMMIT_SHA} @@ -94,35 +97,72 @@ build-bundled-agent-adp-image: . test-correctness-dsd-plain: - extends: [.build-common-variables, .test-correctness-definition, .test-correctness-adp-baseline] + extends: [.build-common-variables, .test-correctness-definition, .test-correctness-adp-comparison, .test-correctness-adp-baseline] variables: CORRECTNESS_TEST_CASE: dsd-plain test-correctness-dsd-origin-detection: - extends: [.build-common-variables, .test-correctness-definition, .test-correctness-adp-baseline] + extends: [.build-common-variables, .test-correctness-definition, .test-correctness-adp-comparison, .test-correctness-adp-baseline] variables: CORRECTNESS_TEST_CASE: dsd-origin-detection test-correctness-otlp-metrics: - extends: [.build-common-variables, .test-correctness-definition, .test-correctness-adp-baseline] + extends: [.build-common-variables, .test-correctness-definition, .test-correctness-adp-comparison, .test-correctness-adp-baseline] variables: CORRECTNESS_TEST_CASE: otlp-metrics test-correctness-otlp-traces: - extends: [.build-common-variables, .test-correctness-definition, .test-correctness-adp-baseline] + extends: [.build-common-variables, .test-correctness-definition, .test-correctness-adp-comparison, .test-correctness-adp-baseline] variables: CORRECTNESS_TEST_CASE: otlp-traces test-correctness-otlp-traces-ottl-filtering: - extends: [.build-common-variables, .test-correctness-definition] + extends: [.build-common-variables, .test-correctness-definition, .test-correctness-adp-comparison] variables: CORRECTNESS_TEST_CASE: otlp-traces-ottl-filtering test-correctness-otlp-traces-ottl-transform: - extends: [.build-common-variables, .test-correctness-definition] + extends: [.build-common-variables, .test-correctness-definition, .test-correctness-adp-comparison] variables: CORRECTNESS_TEST_CASE: otlp-traces-ottl-transform +test-correctness-dsd-tag-filterlist: + extends: [.build-common-variables, .test-correctness-definition, .test-correctness-adp-comparison] + variables: + CORRECTNESS_TEST_CASE: dsd-tag-filterlist + +test-correctness-dsd-optimized-tag-filterlist: + extends: [.build-common-variables, .test-correctness-definition] + needs: + - build-datadog-intake-image + - build-millstone-image + variables: + CORRECTNESS_TEST_CASE: dsd-optimized-tag-filterlist + +test-correctness-dsd-additional-optimized-tag-filterlist: + extends: [.build-common-variables, .test-correctness-definition] + needs: + - build-datadog-intake-image + - build-millstone-image + variables: + CORRECTNESS_TEST_CASE: dsd-additional-optimized-tag-filterlist + +test-correctness-dsd-tag-filterlist-context-cache: + extends: [.build-common-variables, .test-correctness-definition] + needs: + - build-datadog-intake-image + - build-millstone-image + variables: + CORRECTNESS_TEST_CASE: dsd-tag-filterlist-context-cache + +test-correctness-dsd-tag-filterlist-post-aggr: + extends: [.build-common-variables, .test-correctness-definition] + needs: + - build-datadog-intake-image + - build-millstone-image + variables: + CORRECTNESS_TEST_CASE: dsd-tag-filterlist-post-aggr + test-integration: stage: e2e tags: ["docker-in-docker:amd64"] diff --git a/Cargo.lock b/Cargo.lock index 78aab11c87a..ee800523c8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1100,7 +1100,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3035,7 +3035,7 @@ dependencies = [ "once_cell", "socket2 0.6.2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3443,9 +3443,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ "aws-lc-rs", "ring", @@ -3900,7 +3900,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b55fb86dfd3a2f5f76ea78310a88f96c4ea21a3031f8d212443d56123fd0521" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Makefile b/Makefile index 9fae09d7e2b..6b145b871db 100644 --- a/Makefile +++ b/Makefile @@ -521,7 +521,7 @@ test-all: test test-property test-docs test-miri test-loom .PHONY: test-correctness test-correctness: ## Runs the complete correctness suite -test-correctness: test-correctness-dsd-plain test-correctness-dsd-origin-detection test-correctness-otlp-metrics test-correctness-otlp-traces test-correctness-otlp-traces-ottl-filtering test-correctness-otlp-traces-ottl-transform +test-correctness: test-correctness-dsd-plain test-correctness-dsd-origin-detection test-correctness-dsd-tag-filterlist test-correctness-dsd-optimized-tag-filterlist test-correctness-dsd-additional-optimized-tag-filterlist test-correctness-dsd-tag-filterlist-context-cache test-correctness-dsd-tag-filterlist-post-aggr test-correctness-otlp-metrics test-correctness-otlp-traces test-correctness-otlp-traces-ottl-filtering test-correctness-otlp-traces-ottl-transform .PHONY: test-correctness-dsd-plain test-correctness-dsd-plain: build-ground-truth @@ -535,6 +535,36 @@ test-correctness-dsd-origin-detection: ## Runs the 'dsd-origin-detection' correc @echo "[*] Running 'dsd-origin-detection' correctness test case..." @target/release/ground-truth $(shell pwd)/test/correctness/dsd-origin-detection/config.yaml +.PHONY: test-correctness-dsd-tag-filterlist +test-correctness-dsd-tag-filterlist: build-ground-truth +test-correctness-dsd-tag-filterlist: ## Runs the 'dsd-tag-filterlist' correctness test case + @echo "[*] Running 'dsd-tag-filterlist' correctness test case..." + @target/release/ground-truth $(shell pwd)/test/correctness/dsd-tag-filterlist/config.yaml + +.PHONY: test-correctness-dsd-optimized-tag-filterlist +test-correctness-dsd-optimized-tag-filterlist: build-ground-truth +test-correctness-dsd-optimized-tag-filterlist: ## Runs the 'dsd-optimized-tag-filterlist' correctness test case + @echo "[*] Running 'dsd-optimized-tag-filterlist' correctness test case..." + @target/release/ground-truth $(shell pwd)/test/correctness/dsd-optimized-tag-filterlist/config.yaml + +.PHONY: test-correctness-dsd-additional-optimized-tag-filterlist +test-correctness-dsd-additional-optimized-tag-filterlist: build-ground-truth +test-correctness-dsd-additional-optimized-tag-filterlist: ## Runs the 'dsd-additional-optimized-tag-filterlist' correctness test case + @echo "[*] Running 'dsd-additional-optimized-tag-filterlist' correctness test case..." + @target/release/ground-truth $(shell pwd)/test/correctness/dsd-additional-optimized-tag-filterlist/config.yaml + +.PHONY: test-correctness-dsd-tag-filterlist-context-cache +test-correctness-dsd-tag-filterlist-context-cache: build-ground-truth +test-correctness-dsd-tag-filterlist-context-cache: ## Runs the 'dsd-tag-filterlist-context-cache' correctness test case + @echo "[*] Running 'dsd-tag-filterlist-context-cache' correctness test case..." + @target/release/ground-truth $(shell pwd)/test/correctness/dsd-tag-filterlist-context-cache/config.yaml + +.PHONY: test-correctness-dsd-tag-filterlist-post-aggr +test-correctness-dsd-tag-filterlist-post-aggr: build-ground-truth +test-correctness-dsd-tag-filterlist-post-aggr: ## Runs the 'dsd-tag-filterlist-post-aggr' correctness test case + @echo "[*] Running 'dsd-tag-filterlist-post-aggr' correctness test case..." + @target/release/ground-truth $(shell pwd)/test/correctness/dsd-tag-filterlist-post-aggr/config.yaml + .PHONY: test-correctness-otlp-metrics test-correctness-otlp-metrics: build-ground-truth test-correctness-otlp-metrics: ## Runs the 'otlp-metrics' correctness test case diff --git a/bin/correctness/ground-truth/src/analysis/metrics/types.rs b/bin/correctness/ground-truth/src/analysis/metrics/types.rs index f0952d6d1a3..a725c0cd563 100644 --- a/bin/correctness/ground-truth/src/analysis/metrics/types.rs +++ b/bin/correctness/ground-truth/src/analysis/metrics/types.rs @@ -107,8 +107,8 @@ impl NormalizedMetric { /// # Errors /// /// If the raw values are empty, or if the values cannot be normalized, an error is returned. - pub fn try_from_values( - context: MetricContext, mut raw_values: Vec<(u64, MetricValue)>, + fn try_from_normalized_values( + context: NormalizedMetricContext, mut raw_values: Vec<(u64, MetricValue)>, ) -> Result { // We need to first sort the raw values by timestamp, to ensure we have proper ordering semantics. raw_values.sort_by(|a, b| a.0.cmp(&b.0)); @@ -116,7 +116,7 @@ impl NormalizedMetric { .with_error_context(|| format!("Failed to normalize values for metric '{}'", context.name()))?; Ok(Self { - context: NormalizedMetricContext::from_stele_context(context), + context, raw_values, value, }) @@ -176,23 +176,24 @@ impl NormalizedMetrics { return Err(generic_error!("Cannot normalize an empty set of metrics.")); } - // Aggregate metric values by (context, type), using a `BTreeMap` so that we can get sorted metrics for ~free. + // Aggregate metric values by normalized (context, type), using a `BTreeMap` so that metric ordering is stable + // even when the original intake payload emits the same tag set in a different order. // // We group by type because metrics with the same context but different types (e.g., Count vs Rate) cannot be // merged together during normalization. let mut aggregated_context_values = BTreeMap::new(); for metric in metrics { - let context = metric.context(); + let context = NormalizedMetricContext::from_stele_context(metric.context().clone()); let metric_type = metric_value_type(metric.values()); - let key = (context.clone(), metric_type); + let key = (context, metric_type); let context_values = aggregated_context_values.entry(key).or_insert_with(Vec::new); context_values.extend_from_slice(metric.values()); } let metrics = aggregated_context_values .into_iter() - .map(|((context, _type), values)| NormalizedMetric::try_from_values(context, values)) + .map(|((context, _type), values)| NormalizedMetric::try_from_normalized_values(context, values)) .try_fold(Vec::new(), |mut metrics, maybe_metric| { metrics.push(maybe_metric?); Ok::<_, GenericError>(metrics) @@ -316,3 +317,42 @@ fn try_normalize_values(raw_values: &[(u64, MetricValue)]) -> Result Metric { + serde_json::from_value(json!({ + "context": { + "name": name, + "tags": tags, + }, + "values": points + .iter() + .map(|(ts, value)| json!([ts, {"mtype": "Count", "value": value}])) + .collect::>(), + })) + .expect("metric should deserialize") + } + + #[test] + fn normalizes_context_order_before_grouping_metrics() { + let metrics = vec![ + metric("tagfilter.miss", &["pod:pod-a", "env:prod"], &[(10, 2.0)]), + metric("tagfilter.miss", &["env:prod", "pod:pod-a"], &[(20, 3.0)]), + ]; + + let normalized = NormalizedMetrics::try_from_stele_metrics(&metrics).expect("metrics should normalize"); + + assert_eq!(normalized.len(), 1); + assert_eq!( + normalized.metrics()[0].context().to_string(), + "tagfilter.miss[env:prod, pod:pod-a]" + ); + assert_eq!(normalized.metrics()[0].raw_values().len(), 2); + } +} diff --git a/lib/saluki-components/src/transforms/tag_filterlist/mod.rs b/lib/saluki-components/src/transforms/tag_filterlist/mod.rs index 4dbadcd22a3..36df58a282d 100644 --- a/lib/saluki-components/src/transforms/tag_filterlist/mod.rs +++ b/lib/saluki-components/src/transforms/tag_filterlist/mod.rs @@ -17,7 +17,7 @@ use saluki_core::{ transforms::{Transform, TransformBuilder, TransformContext}, ComponentContext, }, - data_model::event::EventType, + data_model::event::{metric::Metric, EventType}, topology::OutputDefinition, }; use saluki_error::GenericError; @@ -215,7 +215,7 @@ fn apply_tag_filter(tags: &SharedTagSet, is_exclude: bool, names: &HashSet