Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions datadog-opentelemetry/src/core/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
142 changes: 127 additions & 15 deletions datadog-opentelemetry/src/sampling/datadog_sampler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -458,6 +458,49 @@ 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` → `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<String> {
if rate.is_nan() || !(0.0..=1.0).contains(&rate) {
return None;
}

if rate == 0.0 {
return Some("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
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 {
pub is_keep: bool,
pub trace_root_info: Option<TraceRootSamplingInfo>,
Expand Down Expand Up @@ -503,11 +546,17 @@ impl DdSamplingResult {
match mechanism {
mechanism::AGENT_RATE_BY_SERVICE => {
result.push(KeyValue::new(SAMPLING_AGENT_RATE_TAG_KEY, 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));
if let Some(rate_str) = format_sampling_rate(root_info.rate) {
result.push(KeyValue::new(SAMPLING_KNUTH_RATE_TAG_KEY, rate_str));
}
}
_ => {}
}
Expand Down Expand Up @@ -932,13 +981,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() {
Expand Down Expand Up @@ -969,13 +1019,22 @@ 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;
}
_ => {}
}
}

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;
Expand All @@ -993,7 +1052,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;
Expand Down Expand Up @@ -1028,24 +1087,40 @@ 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 {
Expand All @@ -1057,6 +1132,43 @@ mod tests {
}
}

#[test]
fn test_format_sampling_rate() {
// Exact values
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), 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),
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), 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]
fn test_should_sample_parent_context() {
let sampler = DatadogSampler::new(vec![], 100, create_empty_resource_arc());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading