use opentelemetry_api::Key;
use opentelemetry_api::KeyValue;
use opentelemetry_api::OrderMap;
use opentelemetry_api::Value;
use opentelemetry_api::trace::Link;
use opentelemetry_api::trace::SamplingDecision;
use opentelemetry_api::trace::SamplingResult;
use opentelemetry_api::trace::SpanKind;
use opentelemetry_api::trace::TraceId;
use opentelemetry_sdk::trace::ShouldSample;
use crate::plugins::telemetry::tracing::datadog_exporter::DatadogTraceState;
use crate::plugins::telemetry::tracing::datadog_exporter::propagator::SamplingPriority;
#[derive(Debug, Clone)]
pub(crate) struct DatadogAgentSampling {
pub(crate) sampler: opentelemetry::sdk::trace::Sampler,
pub(crate) parent_based_sampler: bool,
}
impl DatadogAgentSampling {
pub(crate) fn new(
sampler: opentelemetry::sdk::trace::Sampler,
parent_based_sampler: bool,
) -> Self {
Self {
sampler,
parent_based_sampler,
}
}
}
impl ShouldSample for DatadogAgentSampling {
fn should_sample(
&self,
parent_context: Option<&opentelemetry_api::Context>,
trace_id: TraceId,
name: &str,
span_kind: &SpanKind,
attributes: &OrderMap<Key, Value>,
links: &[Link],
) -> SamplingResult {
let mut result = self.sampler.should_sample(
parent_context,
trace_id,
name,
span_kind,
attributes,
links,
);
match result.decision {
SamplingDecision::Drop | SamplingDecision::RecordOnly => {
result.decision = SamplingDecision::RecordOnly;
if !self.parent_based_sampler || result.trace_state.sampling_priority().is_none() {
result.trace_state = result
.trace_state
.with_priority_sampling(SamplingPriority::AutoReject)
}
}
SamplingDecision::RecordAndSample => {
if !self.parent_based_sampler || result.trace_state.sampling_priority().is_none() {
result.trace_state = result
.trace_state
.with_priority_sampling(SamplingPriority::AutoKeep)
}
}
}
result.trace_state = result.trace_state.with_measuring(true);
result.attributes.push(KeyValue::new(
"sampling.priority",
Value::I64(
result
.trace_state
.sampling_priority()
.expect("sampling priority")
.as_i64(),
),
));
result
}
}
#[cfg(test)]
mod tests {
use buildstructor::Builder;
use opentelemetry::sdk::trace::Sampler;
use opentelemetry::trace::TraceState;
use opentelemetry_api::Context;
use opentelemetry_api::Key;
use opentelemetry_api::OrderMap;
use opentelemetry_api::Value;
use opentelemetry_api::trace::Link;
use opentelemetry_api::trace::SamplingDecision;
use opentelemetry_api::trace::SamplingResult;
use opentelemetry_api::trace::SpanContext;
use opentelemetry_api::trace::SpanId;
use opentelemetry_api::trace::SpanKind;
use opentelemetry_api::trace::TraceContextExt;
use opentelemetry_api::trace::TraceFlags;
use opentelemetry_api::trace::TraceId;
use opentelemetry_sdk::trace::ShouldSample;
use crate::plugins::telemetry::tracing::datadog::DatadogAgentSampling;
use crate::plugins::telemetry::tracing::datadog_exporter::DatadogTraceState;
use crate::plugins::telemetry::tracing::datadog_exporter::propagator::SamplingPriority;
#[derive(Debug, Clone, Builder)]
struct StubSampler {
decision: SamplingDecision,
}
impl ShouldSample for StubSampler {
fn should_sample(
&self,
_parent_context: Option<&Context>,
_trace_id: TraceId,
_name: &str,
_span_kind: &SpanKind,
_attributes: &OrderMap<Key, Value>,
_links: &[Link],
) -> SamplingResult {
SamplingResult {
decision: self.decision.clone(),
attributes: Vec::new(),
trace_state: Default::default(),
}
}
}
#[test]
fn test_should_sample_drop() {
let sampler = StubSampler::builder()
.decision(SamplingDecision::Drop)
.build();
let datadog_sampler =
DatadogAgentSampling::new(Sampler::ParentBased(Box::new(sampler)), false);
let result = datadog_sampler.should_sample(
None,
TraceId::from_u128(1),
"test_span",
&SpanKind::Internal,
&OrderMap::new(),
&[],
);
assert_eq!(result.decision, SamplingDecision::RecordOnly);
assert_eq!(
result.trace_state.sampling_priority(),
Some(SamplingPriority::AutoReject)
);
assert!(
result
.attributes
.iter()
.any(|kv| kv.key.as_str() == "sampling.priority"
&& kv.value == Value::I64(SamplingPriority::AutoReject.as_i64()))
);
assert!(result.trace_state.measuring_enabled());
}
#[test]
fn test_should_sample_record_only() {
let sampler = StubSampler::builder()
.decision(SamplingDecision::RecordOnly)
.build();
let datadog_sampler =
DatadogAgentSampling::new(Sampler::ParentBased(Box::new(sampler)), false);
let result = datadog_sampler.should_sample(
None,
TraceId::from_u128(1),
"test_span",
&SpanKind::Internal,
&OrderMap::new(),
&[],
);
assert_eq!(result.decision, SamplingDecision::RecordOnly);
assert_eq!(
result.trace_state.sampling_priority(),
Some(SamplingPriority::AutoReject)
);
assert!(
result
.attributes
.iter()
.any(|kv| kv.key.as_str() == "sampling.priority"
&& kv.value == Value::I64(SamplingPriority::AutoReject.as_i64()))
);
assert!(result.trace_state.measuring_enabled());
}
#[test]
fn test_should_sample_record_and_sample() {
let sampler = StubSampler::builder()
.decision(SamplingDecision::RecordAndSample)
.build();
let datadog_sampler =
DatadogAgentSampling::new(Sampler::ParentBased(Box::new(sampler)), false);
let result = datadog_sampler.should_sample(
None,
TraceId::from_u128(1),
"test_span",
&SpanKind::Internal,
&OrderMap::new(),
&[],
);
assert_eq!(result.decision, SamplingDecision::RecordAndSample);
assert_eq!(
result.trace_state.sampling_priority(),
Some(SamplingPriority::AutoKeep)
);
assert!(
result
.attributes
.iter()
.any(|kv| kv.key.as_str() == "sampling.priority"
&& kv.value == Value::I64(SamplingPriority::AutoKeep.as_i64()))
);
assert!(result.trace_state.measuring_enabled());
}
#[test]
fn test_should_sample_with_parent_based_sampler() {
let sampler = StubSampler::builder()
.decision(SamplingDecision::RecordAndSample)
.build();
let datadog_sampler =
DatadogAgentSampling::new(Sampler::ParentBased(Box::new(sampler)), true);
let result = datadog_sampler.should_sample(
Some(&Context::new()),
TraceId::from_u128(1),
"test_span",
&SpanKind::Internal,
&OrderMap::new(),
&[],
);
assert_eq!(result.decision, SamplingDecision::RecordAndSample);
assert_eq!(
result.trace_state.sampling_priority(),
Some(SamplingPriority::AutoKeep)
);
assert!(
result
.attributes
.iter()
.any(|kv| kv.key.as_str() == "sampling.priority"
&& kv.value == Value::I64(SamplingPriority::AutoKeep.as_i64()))
);
assert!(result.trace_state.measuring_enabled());
}
#[test]
fn test_trace_state_already_populated_record_and_sample() {
let sampler = StubSampler::builder()
.decision(SamplingDecision::RecordAndSample)
.build();
let datadog_sampler =
DatadogAgentSampling::new(Sampler::ParentBased(Box::new(sampler)), true);
let result = datadog_sampler.should_sample(
Some(&Context::new().with_remote_span_context(SpanContext::new(
TraceId::from_u128(1),
SpanId::from_u64(1),
TraceFlags::SAMPLED,
true,
TraceState::default().with_priority_sampling(SamplingPriority::UserReject),
))),
TraceId::from_u128(1),
"test_span",
&SpanKind::Internal,
&OrderMap::new(),
&[],
);
assert_eq!(result.decision, SamplingDecision::RecordAndSample);
assert_eq!(
result.trace_state.sampling_priority(),
Some(SamplingPriority::UserReject)
);
assert!(
result
.attributes
.iter()
.any(|kv| kv.key.as_str() == "sampling.priority"
&& kv.value == Value::I64(SamplingPriority::UserReject.as_i64()))
);
assert!(result.trace_state.measuring_enabled());
}
#[test]
fn test_trace_state_already_populated_record_drop() {
let sampler = StubSampler::builder()
.decision(SamplingDecision::Drop)
.build();
let datadog_sampler =
DatadogAgentSampling::new(Sampler::ParentBased(Box::new(sampler)), true);
let result = datadog_sampler.should_sample(
Some(&Context::new().with_remote_span_context(SpanContext::new(
TraceId::from_u128(1),
SpanId::from_u64(1),
TraceFlags::default(),
true,
TraceState::default().with_priority_sampling(SamplingPriority::UserReject),
))),
TraceId::from_u128(1),
"test_span",
&SpanKind::Internal,
&OrderMap::new(),
&[],
);
assert_eq!(result.decision, SamplingDecision::RecordOnly);
assert_eq!(
result.trace_state.sampling_priority(),
Some(SamplingPriority::UserReject)
);
assert!(
result
.attributes
.iter()
.any(|kv| kv.key.as_str() == "sampling.priority"
&& kv.value == Value::I64(SamplingPriority::UserReject.as_i64()))
);
assert!(result.trace_state.measuring_enabled());
}
}