From 8c4fe7875f8c782b15fd98caaaa9e32a4c024428 Mon Sep 17 00:00:00 2001 From: bm1549 Date: Tue, 10 Mar 2026 21:44:29 -0400 Subject: [PATCH 1/6] Add _dd.p.ksr propagated tag for Knuth sampling rate Co-Authored-By: Claude Opus 4.6 --- datadog-opentelemetry/src/core/constants.rs | 3 + .../src/sampling/datadog_sampler.rs | 119 +++++++++++++++--- .../test_remote_config_sampling_rates.json | 1 + .../test_sampling_extraction.json | 1 + 4 files changed, 109 insertions(+), 15 deletions(-) diff --git a/datadog-opentelemetry/src/core/constants.rs b/datadog-opentelemetry/src/core/constants.rs index f8c14284..909f94ef 100644 --- a/datadog-opentelemetry/src/core/constants.rs +++ b/datadog-opentelemetry/src/core/constants.rs @@ -24,3 +24,6 @@ pub const SAMPLING_AGENT_RATE_TAG_KEY: &str = "_dd.agent_psr"; /// Rate limiter effective rate metric key. pub const RL_EFFECTIVE_RATE: &str = "_dd.limit_psr"; + +/// Knuth Sampling Rate propagated tag key. +pub const SAMPLING_KNUTH_RATE_TAG_KEY: &str = "_dd.p.ksr"; diff --git a/datadog-opentelemetry/src/sampling/datadog_sampler.rs b/datadog-opentelemetry/src/sampling/datadog_sampler.rs index 07e1bc7b..001d325a 100644 --- a/datadog-opentelemetry/src/sampling/datadog_sampler.rs +++ b/datadog-opentelemetry/src/sampling/datadog_sampler.rs @@ -4,7 +4,7 @@ use crate::core::configuration::SamplingRuleConfig; use crate::core::constants::{ RL_EFFECTIVE_RATE, SAMPLING_AGENT_RATE_TAG_KEY, SAMPLING_DECISION_MAKER_TAG_KEY, - SAMPLING_PRIORITY_TAG_KEY, SAMPLING_RULE_RATE_TAG_KEY, + SAMPLING_KNUTH_RATE_TAG_KEY, SAMPLING_PRIORITY_TAG_KEY, SAMPLING_RULE_RATE_TAG_KEY, }; use crate::core::sampling::{mechanism, SamplingMechanism, SamplingPriority}; @@ -458,6 +458,43 @@ impl DatadogSampler { } } +/// Formats a sampling rate with up to 6 significant digits, stripping trailing zeros. +/// +/// This matches the Go behavior of `strconv.FormatFloat(rate, 'g', 6, 64)`. +/// +/// # Examples +/// - `1.0` → `"1"` +/// - `0.5` → `"0.5"` +/// - `0.7654321` → `"0.765432"` +/// - `0.100000` → `"0.1"` +fn format_sampling_rate(rate: f64) -> String { + if rate == 0.0 { + return "0".to_string(); + } + + let digits = 6_i32; + let magnitude = rate.abs().log10().floor() as i32; + let scale = 10f64.powi(digits - 1 - magnitude); + let rounded = (rate * scale).round() / scale; + + // Determine decimal places needed for 6 significant digits + let decimal_places = if magnitude >= digits - 1 { + 0 + } else { + (digits - 1 - magnitude) as usize + }; + + let s = format!("{:.prec$}", rounded, prec = decimal_places); + // Strip trailing zeros after decimal point + if s.contains('.') { + let s = s.trim_end_matches('0'); + let s = s.trim_end_matches('.'); + s.to_string() + } else { + s + } +} + pub(crate) struct DdSamplingResult { pub is_keep: bool, pub trace_root_info: Option, @@ -503,11 +540,19 @@ impl DdSamplingResult { match mechanism { mechanism::AGENT_RATE_BY_SERVICE => { result.push(KeyValue::new(SAMPLING_AGENT_RATE_TAG_KEY, root_info.rate)); + result.push(KeyValue::new( + SAMPLING_KNUTH_RATE_TAG_KEY, + format_sampling_rate(root_info.rate), + )); } mechanism::REMOTE_USER_TRACE_SAMPLING_RULE | mechanism::REMOTE_DYNAMIC_TRACE_SAMPLING_RULE | mechanism::LOCAL_USER_TRACE_SAMPLING_RULE => { result.push(KeyValue::new(SAMPLING_RULE_RATE_TAG_KEY, root_info.rate)); + result.push(KeyValue::new( + SAMPLING_KNUTH_RATE_TAG_KEY, + format_sampling_rate(root_info.rate), + )); } _ => {} } @@ -932,13 +977,14 @@ mod tests { let attrs = sampling_result.to_dd_sampling_tags(); - // Verify the number of attributes - assert_eq!(attrs.len(), 3); + // Verify the number of attributes (decision_maker + priority + rule_rate + ksr) + assert_eq!(attrs.len(), 4); // Check individual attributes let mut found_decision_maker = false; let mut found_priority = false; let mut found_rule_rate = false; + let mut found_ksr = false; for attr in &attrs { match attr.key.as_str() { @@ -969,6 +1015,14 @@ mod tests { assert_eq!(value_float, sample_rate); found_rule_rate = true; } + SAMPLING_KNUTH_RATE_TAG_KEY => { + let value_str = match &attr.value { + opentelemetry::Value::String(s) => s.to_string(), + _ => panic!("Expected string value for ksr tag"), + }; + assert_eq!(value_str, "0.5"); + found_ksr = true; + } _ => {} } } @@ -976,6 +1030,7 @@ mod tests { assert!(found_decision_maker, "Missing decision maker tag"); assert!(found_priority, "Missing priority tag"); assert!(found_rule_rate, "Missing rule rate tag"); + assert!(found_ksr, "Missing knuth sampling rate tag"); // Test with rate limiting let rate_limit = 100; @@ -993,7 +1048,7 @@ mod tests { let attrs_with_limit = sampling_result.to_dd_sampling_tags(); // With rate limiting, there should be one more attribute - assert_eq!(attrs_with_limit.len(), 4); + assert_eq!(attrs_with_limit.len(), 5); // Check for rate limit attribute let mut found_limit = false; @@ -1028,24 +1083,36 @@ mod tests { let agent_attrs = sampling_result.to_dd_sampling_tags(); - // Verify the number of attributes (should be 3) - assert_eq!(agent_attrs.len(), 3); + // Verify the number of attributes (should be 4: decision_maker + priority + agent_rate + ksr) + assert_eq!(agent_attrs.len(), 4); - // Check for agent rate tag specifically + // Check for agent rate tag and ksr tag let mut found_agent_rate = false; + let mut found_ksr = false; for attr in &agent_attrs { - if attr.key.as_str() == SAMPLING_AGENT_RATE_TAG_KEY { - let value_float = match attr.value { - opentelemetry::Value::F64(f) => f, - _ => panic!("Expected float value for agent rate tag"), - }; - assert_eq!(value_float, agent_rate); - found_agent_rate = true; - break; + match attr.key.as_str() { + SAMPLING_AGENT_RATE_TAG_KEY => { + let value_float = match attr.value { + opentelemetry::Value::F64(f) => f, + _ => panic!("Expected float value for agent rate tag"), + }; + assert_eq!(value_float, agent_rate); + found_agent_rate = true; + } + SAMPLING_KNUTH_RATE_TAG_KEY => { + let value_str = match &attr.value { + opentelemetry::Value::String(s) => s.to_string(), + _ => panic!("Expected string value for ksr tag"), + }; + assert_eq!(value_str, "0.75"); + found_ksr = true; + } + _ => {} } } assert!(found_agent_rate, "Missing agent rate tag"); + assert!(found_ksr, "Missing knuth sampling rate tag for agent mechanism"); // Also check that the SAMPLING_RULE_RATE_TAG_KEY is NOT present for agent mechanism for attr in &agent_attrs { @@ -1057,6 +1124,28 @@ mod tests { } } + #[test] + fn test_format_sampling_rate() { + // Exact values + assert_eq!(format_sampling_rate(1.0), "1"); + assert_eq!(format_sampling_rate(0.5), "0.5"); + assert_eq!(format_sampling_rate(0.1), "0.1"); + assert_eq!(format_sampling_rate(0.0), "0"); + + // Trailing zeros should be stripped + assert_eq!(format_sampling_rate(0.100000), "0.1"); + assert_eq!(format_sampling_rate(0.500000), "0.5"); + + // Truncation to 6 significant digits + assert_eq!(format_sampling_rate(0.7654321), "0.765432"); + assert_eq!(format_sampling_rate(0.123456789), "0.123457"); + + // Small values + assert_eq!(format_sampling_rate(0.001), "0.001"); + assert_eq!(format_sampling_rate(0.75), "0.75"); + assert_eq!(format_sampling_rate(0.999999), "0.999999"); + } + #[test] fn test_should_sample_parent_context() { let sampler = DatadogSampler::new(vec![], 100, create_empty_resource_arc()); diff --git a/datadog-opentelemetry/tests/snapshots/opentelemetry_api/test_remote_config_sampling_rates.json b/datadog-opentelemetry/tests/snapshots/opentelemetry_api/test_remote_config_sampling_rates.json index ec27ff93..50cef39c 100644 --- a/datadog-opentelemetry/tests/snapshots/opentelemetry_api/test_remote_config_sampling_rates.json +++ b/datadog-opentelemetry/tests/snapshots/opentelemetry_api/test_remote_config_sampling_rates.json @@ -9,6 +9,7 @@ "type": "custom", "meta": { "_dd.p.dm": "-11", + "_dd.p.ksr": "1", "otel.scope.name": "test", "otel.status_code": "Unset", "otel.trace_id": "68b1de6200000000578668d18e7e6e97", diff --git a/datadog-opentelemetry/tests/snapshots/opentelemetry_api/test_sampling_extraction.json b/datadog-opentelemetry/tests/snapshots/opentelemetry_api/test_sampling_extraction.json index c72c3407..4e958163 100644 --- a/datadog-opentelemetry/tests/snapshots/opentelemetry_api/test_sampling_extraction.json +++ b/datadog-opentelemetry/tests/snapshots/opentelemetry_api/test_sampling_extraction.json @@ -9,6 +9,7 @@ "type": "web", "meta": { "_dd.p.dm": "-3", + "_dd.p.ksr": "1", "otel.scope.name": "test", "otel.status_code": "Unset", "otel.trace_id": "684068c7000000007eaf83ddeb833e92", From fc461fe35ac7f05d854d89c91f970867bd3112f7 Mon Sep 17 00:00:00 2001 From: bm1549 Date: Tue, 10 Mar 2026 22:11:08 -0400 Subject: [PATCH 2/6] style: fix rustfmt formatting in datadog_sampler.rs Expand assert! macro call to multi-line format required by rustfmt. Co-Authored-By: Claude Sonnet 4.6 --- datadog-opentelemetry/src/sampling/datadog_sampler.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/datadog-opentelemetry/src/sampling/datadog_sampler.rs b/datadog-opentelemetry/src/sampling/datadog_sampler.rs index 001d325a..09f39733 100644 --- a/datadog-opentelemetry/src/sampling/datadog_sampler.rs +++ b/datadog-opentelemetry/src/sampling/datadog_sampler.rs @@ -1112,7 +1112,10 @@ mod tests { } assert!(found_agent_rate, "Missing agent rate tag"); - assert!(found_ksr, "Missing knuth sampling rate tag for agent mechanism"); + assert!( + found_ksr, + "Missing knuth sampling rate tag for agent mechanism" + ); // Also check that the SAMPLING_RULE_RATE_TAG_KEY is NOT present for agent mechanism for attr in &agent_attrs { From cc7d3fef3f52e10a49a1136ccf905304fb41f725 Mon Sep 17 00:00:00 2001 From: bm1549 Date: Tue, 10 Mar 2026 22:25:15 -0400 Subject: [PATCH 3/6] style: wrap long comment line for rustfmt compliance Co-Authored-By: Claude Opus 4.6 --- datadog-opentelemetry/src/sampling/datadog_sampler.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/datadog-opentelemetry/src/sampling/datadog_sampler.rs b/datadog-opentelemetry/src/sampling/datadog_sampler.rs index 09f39733..4cef4b27 100644 --- a/datadog-opentelemetry/src/sampling/datadog_sampler.rs +++ b/datadog-opentelemetry/src/sampling/datadog_sampler.rs @@ -1083,7 +1083,8 @@ mod tests { let agent_attrs = sampling_result.to_dd_sampling_tags(); - // Verify the number of attributes (should be 4: decision_maker + priority + agent_rate + ksr) + // Verify the number of attributes (should be 4: decision_maker + priority + + // agent_rate + ksr) assert_eq!(agent_attrs.len(), 4); // Check for agent rate tag and ksr tag From 7a5c5b7948af8ce6684834df13764e39e0fe17f2 Mon Sep 17 00:00:00 2001 From: bm1549 Date: Wed, 18 Mar 2026 13:54:46 -0400 Subject: [PATCH 4/6] fix(sampling): make format_sampling_rate defensive with Option return type Return None for invalid rates (negative, >1.0, NaN, infinity) instead of producing garbage output. Update call sites to skip the tag when None. Co-Authored-By: Claude Sonnet 4.6 --- .../src/sampling/datadog_sampler.rs | 76 ++++++++++++------- 1 file changed, 49 insertions(+), 27 deletions(-) diff --git a/datadog-opentelemetry/src/sampling/datadog_sampler.rs b/datadog-opentelemetry/src/sampling/datadog_sampler.rs index 4cef4b27..df06ceb5 100644 --- a/datadog-opentelemetry/src/sampling/datadog_sampler.rs +++ b/datadog-opentelemetry/src/sampling/datadog_sampler.rs @@ -463,13 +463,19 @@ impl DatadogSampler { /// This matches the Go behavior of `strconv.FormatFloat(rate, 'g', 6, 64)`. /// /// # Examples -/// - `1.0` → `"1"` -/// - `0.5` → `"0.5"` -/// - `0.7654321` → `"0.765432"` -/// - `0.100000` → `"0.1"` -fn format_sampling_rate(rate: f64) -> String { +/// - `1.0` → `Some("1")` +/// - `0.5` → `Some("0.5")` +/// - `0.7654321` → `Some("0.765432")` +/// - `0.100000` → `Some("0.1")` +/// - `-0.1` → `None` +/// - `1.1` → `None` +fn format_sampling_rate(rate: f64) -> Option { + if rate.is_nan() || rate < 0.0 || rate > 1.0 { + return None; + } + if rate == 0.0 { - return "0".to_string(); + return Some("0".to_string()); } let digits = 6_i32; @@ -486,13 +492,13 @@ fn format_sampling_rate(rate: f64) -> String { let s = format!("{:.prec$}", rounded, prec = decimal_places); // Strip trailing zeros after decimal point - if s.contains('.') { + Some(if s.contains('.') { let s = s.trim_end_matches('0'); let s = s.trim_end_matches('.'); s.to_string() } else { s - } + }) } pub(crate) struct DdSamplingResult { @@ -540,19 +546,17 @@ impl DdSamplingResult { match mechanism { mechanism::AGENT_RATE_BY_SERVICE => { result.push(KeyValue::new(SAMPLING_AGENT_RATE_TAG_KEY, root_info.rate)); - result.push(KeyValue::new( - SAMPLING_KNUTH_RATE_TAG_KEY, - format_sampling_rate(root_info.rate), - )); + if let Some(rate_str) = format_sampling_rate(root_info.rate) { + result.push(KeyValue::new(SAMPLING_KNUTH_RATE_TAG_KEY, rate_str)); + } } mechanism::REMOTE_USER_TRACE_SAMPLING_RULE | mechanism::REMOTE_DYNAMIC_TRACE_SAMPLING_RULE | mechanism::LOCAL_USER_TRACE_SAMPLING_RULE => { result.push(KeyValue::new(SAMPLING_RULE_RATE_TAG_KEY, root_info.rate)); - result.push(KeyValue::new( - SAMPLING_KNUTH_RATE_TAG_KEY, - format_sampling_rate(root_info.rate), - )); + if let Some(rate_str) = format_sampling_rate(root_info.rate) { + result.push(KeyValue::new(SAMPLING_KNUTH_RATE_TAG_KEY, rate_str)); + } } _ => {} } @@ -1131,23 +1135,41 @@ mod tests { #[test] fn test_format_sampling_rate() { // Exact values - assert_eq!(format_sampling_rate(1.0), "1"); - assert_eq!(format_sampling_rate(0.5), "0.5"); - assert_eq!(format_sampling_rate(0.1), "0.1"); - assert_eq!(format_sampling_rate(0.0), "0"); + assert_eq!(format_sampling_rate(1.0), Some("1".to_string())); + assert_eq!(format_sampling_rate(0.5), Some("0.5".to_string())); + assert_eq!(format_sampling_rate(0.1), Some("0.1".to_string())); + assert_eq!(format_sampling_rate(0.0), Some("0".to_string())); // Trailing zeros should be stripped - assert_eq!(format_sampling_rate(0.100000), "0.1"); - assert_eq!(format_sampling_rate(0.500000), "0.5"); + assert_eq!(format_sampling_rate(0.100000), Some("0.1".to_string())); + assert_eq!(format_sampling_rate(0.500000), Some("0.5".to_string())); // Truncation to 6 significant digits - assert_eq!(format_sampling_rate(0.7654321), "0.765432"); - assert_eq!(format_sampling_rate(0.123456789), "0.123457"); + assert_eq!( + format_sampling_rate(0.7654321), + Some("0.765432".to_string()) + ); + assert_eq!( + format_sampling_rate(0.123456789), + Some("0.123457".to_string()) + ); // Small values - assert_eq!(format_sampling_rate(0.001), "0.001"); - assert_eq!(format_sampling_rate(0.75), "0.75"); - assert_eq!(format_sampling_rate(0.999999), "0.999999"); + assert_eq!(format_sampling_rate(0.001), Some("0.001".to_string())); + + // Boundary values + assert_eq!(format_sampling_rate(0.75), Some("0.75".to_string())); + assert_eq!( + format_sampling_rate(0.999999), + Some("0.999999".to_string()) + ); + + // Invalid rates + assert_eq!(format_sampling_rate(-0.1), None); + assert_eq!(format_sampling_rate(1.1), None); + assert_eq!(format_sampling_rate(f64::NAN), None); + assert_eq!(format_sampling_rate(f64::INFINITY), None); + assert_eq!(format_sampling_rate(f64::NEG_INFINITY), None); } #[test] From 12c7177841e0b08f63ab816fd1e0b9985d332fa7 Mon Sep 17 00:00:00 2001 From: bm1549 Date: Wed, 18 Mar 2026 13:57:42 -0400 Subject: [PATCH 5/6] style: use RangeInclusive::contains for rate bounds check Fix clippy::manual_range_contains lint. Co-Authored-By: Claude Sonnet 4.6 --- datadog-opentelemetry/src/sampling/datadog_sampler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datadog-opentelemetry/src/sampling/datadog_sampler.rs b/datadog-opentelemetry/src/sampling/datadog_sampler.rs index df06ceb5..b725c786 100644 --- a/datadog-opentelemetry/src/sampling/datadog_sampler.rs +++ b/datadog-opentelemetry/src/sampling/datadog_sampler.rs @@ -470,7 +470,7 @@ impl DatadogSampler { /// - `-0.1` → `None` /// - `1.1` → `None` fn format_sampling_rate(rate: f64) -> Option { - if rate.is_nan() || rate < 0.0 || rate > 1.0 { + if rate.is_nan() || !(0.0..=1.0).contains(&rate) { return None; } From 4a7e376ade3421b87aff0d5c96c5a6cd45104036 Mon Sep 17 00:00:00 2001 From: bm1549 Date: Wed, 18 Mar 2026 15:41:38 -0400 Subject: [PATCH 6/6] style: fix rustfmt formatting in test assertions Co-Authored-By: Claude Sonnet 4.6 --- datadog-opentelemetry/src/sampling/datadog_sampler.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/datadog-opentelemetry/src/sampling/datadog_sampler.rs b/datadog-opentelemetry/src/sampling/datadog_sampler.rs index b725c786..b51a5536 100644 --- a/datadog-opentelemetry/src/sampling/datadog_sampler.rs +++ b/datadog-opentelemetry/src/sampling/datadog_sampler.rs @@ -1159,10 +1159,7 @@ mod tests { // Boundary values assert_eq!(format_sampling_rate(0.75), Some("0.75".to_string())); - assert_eq!( - format_sampling_rate(0.999999), - Some("0.999999".to_string()) - ); + assert_eq!(format_sampling_rate(0.999999), Some("0.999999".to_string())); // Invalid rates assert_eq!(format_sampling_rate(-0.1), None);