Skip to content

Commit 295db59

Browse files
committed
add tests for multiple per-key collapse
1 parent 7a73821 commit 295db59

1 file changed

Lines changed: 91 additions & 14 deletions

File tree

‎libdd-trace-stats/src/stats_exporter.rs‎

Lines changed: 91 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -645,21 +645,31 @@ mod tests {
645645

646646
/// Build a concentrator with `max_entries_per_bucket = 1` pre-seeded with four distinct spans
647647
/// so that three spans are collapsed into the overflow bucket.
648+
///
649+
/// per_key_collapsed: enable small per-key limits to also collapse on `resource` and
650+
/// `http_endpoint`
648651
#[cfg(any(feature = "telemetry", feature = "dogstatsd"))]
649-
fn get_collapsed_concentrator() -> SpanConcentrator {
652+
fn get_collapsed_concentrator(per_key_collapsed: bool) -> SpanConcentrator {
650653
use crate::span_concentrator::CardinalityLimitConfig;
651-
use libdd_trace_utils::span::{trace_utils, v04::SpanSlice};
654+
use libdd_trace_utils::span::{
655+
trace_utils,
656+
v04::{SpanSlice, VecMap},
657+
};
652658

659+
let mut cardinality_limit_config = CardinalityLimitConfig {
660+
whole_key_limit: 2, // max 2 distinct key → third distinct span collapses
661+
..Default::default()
662+
};
663+
if per_key_collapsed {
664+
cardinality_limit_config.resource_limit = 1;
665+
cardinality_limit_config.http_endpoint_limit = 1;
666+
}
653667
let mut concentrator = SpanConcentrator::new(
654668
BUCKETS_DURATION,
655669
SystemTime::now(),
656670
vec![],
657671
vec![],
658-
Some(CardinalityLimitConfig {
659-
whole_key_limit: 2, // max 2 distinct key → third distinct span collapses
660-
resource_limit: 1, // max 1 distinct resource values
661-
..Default::default()
662-
}),
672+
Some(cardinality_limit_config),
663673
#[cfg(feature = "stats-obfuscation")]
664674
None,
665675
);
@@ -669,20 +679,26 @@ mod tests {
669679
service: "svc-a",
670680
resource: "resource-a",
671681
duration: 10,
682+
meta: VecMap::from_iter([("http.endpoint", "/")]),
672683
..Default::default()
673684
},
685+
// only resource get collapsed if per-key limits are enabled
674686
SpanSlice {
675687
service: "svc-a",
676688
resource: "resource-b",
677689
duration: 20,
690+
meta: VecMap::from_iter([("http.endpoint", "/")]),
678691
..Default::default()
679692
},
693+
// both resource and http_endpoint get collapsed if per-key limits are enabled
680694
SpanSlice {
681695
service: "svc-b",
682-
resource: "resource-a",
696+
resource: "resource-c",
683697
duration: 20,
698+
meta: VecMap::from_iter([("http.endpoint", "/hello.txt")]),
684699
..Default::default()
685700
},
701+
// both resource and http_endpoint get collapsed if per-key limits are enabled
686702
SpanSlice {
687703
service: "svc-b",
688704
resource: "resource-b",
@@ -776,7 +792,59 @@ mod tests {
776792

777793
let stats_exporter = StatsExporter::<NativeCapabilities>::new(
778794
BUCKETS_DURATION,
779-
Arc::new(Mutex::new(get_collapsed_concentrator())),
795+
Arc::new(Mutex::new(get_collapsed_concentrator(false))),
796+
get_test_metadata(),
797+
Endpoint::from_url(stats_url_from_agent_url(&server.url("/")).unwrap()),
798+
NativeCapabilities::new_client(),
799+
#[cfg(feature = "stats-obfuscation")]
800+
"1",
801+
#[cfg(feature = "telemetry")]
802+
None,
803+
Some(dogstatsd_client),
804+
);
805+
806+
stats_exporter.send(true).await.unwrap();
807+
808+
let mut buf = [0u8; 256];
809+
let n = socket
810+
.recv(&mut buf)
811+
.expect("expected a DogStatsD datagram");
812+
let datagram = std::str::from_utf8(&buf[..n]).expect("valid utf-8");
813+
assert_eq!(
814+
datagram, "datadog.tracer.stats.collapsed_spans:2|c|#collapsed_spans:whole_key",
815+
"DogStatsD datagram must match the expected format"
816+
);
817+
}
818+
819+
/// Verify that `COLLAPSED_SPANS_METRIC` is emitted to DogStatsD when spans are collapsed by
820+
/// per-key limits.
821+
#[cfg(feature = "dogstatsd")]
822+
#[cfg_attr(miri, ignore)]
823+
#[tokio::test]
824+
async fn test_collapsed_spans_per_key_dogstatsd() {
825+
use std::net;
826+
827+
let server = MockServer::start_async().await;
828+
server
829+
.mock_async(|_when, then| {
830+
then.status(200).body("");
831+
})
832+
.await;
833+
834+
let socket = net::UdpSocket::bind("127.0.0.1:0").expect("failed to bind UDP socket");
835+
socket
836+
.set_read_timeout(Some(std::time::Duration::from_millis(500)))
837+
.unwrap();
838+
let addr = socket.local_addr().unwrap().to_string();
839+
840+
let dogstatsd_client = Arc::new(
841+
libdd_dogstatsd_client::new(libdd_common::Endpoint::from_slice(&addr))
842+
.expect("failed to create dogstatsd client"),
843+
);
844+
845+
let stats_exporter = StatsExporter::<NativeCapabilities>::new(
846+
BUCKETS_DURATION,
847+
Arc::new(Mutex::new(get_collapsed_concentrator(true))),
780848
get_test_metadata(),
781849
Endpoint::from_url(stats_url_from_agent_url(&server.url("/")).unwrap()),
782850
NativeCapabilities::new_client(),
@@ -790,6 +858,7 @@ mod tests {
790858
stats_exporter.send(true).await.unwrap();
791859

792860
let mut buf = [0u8; 256];
861+
793862
// Whole-key metric emitted first
794863
let n = socket
795864
.recv(&mut buf)
@@ -800,13 +869,21 @@ mod tests {
800869
"DogStatsD datagram must match the expected format"
801870
);
802871

803-
// Then comes the per-field collapse metrics
872+
// Then comes the per-key collapse telemetry
873+
let n = socket
874+
.recv(&mut buf)
875+
.expect("expected a DogStatsD datagram");
876+
let datagram = std::str::from_utf8(&buf[..n]).expect("valid utf-8");
877+
assert_eq!(
878+
datagram, "datadog.tracer.stats.collapsed_spans:1|c|#collapsed_spans:resource",
879+
"DogStatsD datagram must match the expected format"
880+
);
804881
let n = socket
805882
.recv(&mut buf)
806883
.expect("expected a DogStatsD datagram");
807884
let datagram = std::str::from_utf8(&buf[..n]).expect("valid utf-8");
808885
assert_eq!(
809-
datagram, "datadog.tracer.stats.collapsed_spans:2|c|#collapsed_spans:resource",
886+
datagram, "datadog.tracer.stats.collapsed_spans:2|c|#collapsed_spans:resource,collapsed_spans:http_endpoint",
810887
"DogStatsD datagram must match the expected format"
811888
);
812889
}
@@ -837,7 +914,7 @@ mod tests {
837914

838915
let stats_exporter = StatsExporter::<NativeCapabilities>::new(
839916
BUCKETS_DURATION,
840-
Arc::new(Mutex::new(get_collapsed_concentrator())),
917+
Arc::new(Mutex::new(get_collapsed_concentrator(true))),
841918
get_test_metadata(),
842919
Endpoint::from_url(stats_url_from_agent_url(&server.url("/")).unwrap()),
843920
NativeCapabilities::new_client(),
@@ -867,8 +944,8 @@ mod tests {
867944
"exactly one metric context (COLLAPSED_SPANS_METRIC) should be registered"
868945
);
869946
assert_eq!(
870-
stats.metric_buckets.buckets, 2,
871-
"exactly one metric bucket expected after collapsing both on whole-key and on the resource field"
947+
stats.metric_buckets.buckets, 3,
948+
"exactly 3 metric bucket expected after one whole-key collapsed-spans, one resource key collapsed-span and one resource key+http_endpoint key emissions"
872949
);
873950
}
874951
}

0 commit comments

Comments
 (0)