use crate::api;
#[derive(Clone, Debug)]
pub enum Sampler {
Always,
Never,
Parent,
Probability(f64),
}
impl api::Sampler for Sampler {
fn should_sample(
&self,
parent_context: Option<&api::SpanContext>,
trace_id: api::TraceId,
_span_id: api::SpanId,
_name: &str,
_span_kind: &api::SpanKind,
_attributes: &[api::KeyValue],
_links: &[api::Link],
) -> api::SamplingResult {
let decision = match self {
Sampler::Always => api::SamplingDecision::RecordAndSampled,
Sampler::Never => api::SamplingDecision::NotRecord,
Sampler::Parent => {
if parent_context.map(|ctx| ctx.is_sampled()).unwrap_or(false) {
api::SamplingDecision::RecordAndSampled
} else {
api::SamplingDecision::NotRecord
}
}
Sampler::Probability(prob) => {
if *prob >= 1.0 || parent_context.map(|ctx| ctx.is_sampled()).unwrap_or(false) {
api::SamplingDecision::RecordAndSampled
} else {
let prob_upper_bound = (prob.max(0.0) * (1u64 << 63) as f64) as u64;
let rnd_from_trace_id = (trace_id.to_u128() as u64) >> 1;
if rnd_from_trace_id < prob_upper_bound {
api::SamplingDecision::RecordAndSampled
} else {
api::SamplingDecision::NotRecord
}
}
}
};
api::SamplingResult {
decision,
attributes: Vec::new(),
}
}
}
#[cfg(test)]
mod tests {
use crate::api::{self, Sampler as _};
use crate::sdk::Sampler;
use rand::Rng;
#[rustfmt::skip]
fn sampler_data() -> Vec<(&'static str, Sampler, f64, bool, bool)> {
vec![
("never_sample", Sampler::Never, 0.0, false, false),
("always_sample", Sampler::Always, 1.0, false, false),
("probability_-1", Sampler::Probability(-1.0), 0.0, false, false),
("probability_.25", Sampler::Probability(0.25), 0.25, false, false),
("probability_.50", Sampler::Probability(0.50), 0.5, false, false),
("probability_.75", Sampler::Probability(0.75), 0.75, false, false),
("probability_2.0", Sampler::Probability(2.0), 1.0, false, false),
("unsampled_parent_with_probability_-1", Sampler::Probability(-1.0), 0.0, true, false),
("unsampled_parent_with_probability_.25", Sampler::Probability(0.25), 0.25, true, false),
("unsampled_parent_with_probability_.50", Sampler::Probability(0.50), 0.5, true, false),
("unsampled_parent_with_probability_.75", Sampler::Probability(0.75), 0.75, true, false),
("unsampled_parent_with_probability_2.0", Sampler::Probability(2.0), 1.0, true, false),
("sampled_parent_with_probability_-1", Sampler::Probability(-1.0), 1.0, true, true),
("sampled_parent_with_probability_.25", Sampler::Probability(0.25), 1.0, true, true),
("sampled_parent_with_probability_2.0", Sampler::Probability(2.0), 1.0, true, true),
("sampled_parent_span_with_never_sample", Sampler::Never, 0.0, true, true),
]
}
#[test]
fn sampling() {
let total = 10_000;
let mut rng = rand::thread_rng();
for (name, sampler, expectation, parent, sample_parent) in sampler_data() {
let mut sampled = 0;
for _ in 0..total {
let parent_context = if parent {
let trace_flags = if sample_parent {
api::TRACE_FLAG_SAMPLED
} else {
0
};
Some(api::SpanContext::new(
api::TraceId::from_u128(1),
api::SpanId::from_u64(1),
trace_flags,
false,
))
} else {
None
};
let trace_id = api::TraceId::from_u128(rng.gen());
if sampler
.should_sample(
parent_context.as_ref(),
trace_id,
api::SpanId::from_u64(1),
name,
&api::SpanKind::Internal,
&[],
&[],
)
.decision
== api::SamplingDecision::RecordAndSampled
{
sampled += 1;
}
}
let mut tolerance = 0.0;
let got = sampled as f64 / total as f64;
if expectation > 0.0 && expectation < 1.0 {
let z = 4.75342;
tolerance = z * (got * (1.0 - got) / total as f64).sqrt();
}
let diff = (got - expectation).abs();
assert!(
diff <= tolerance,
"{} got {:?} (diff: {}), expected {} (w/tolerance: {})",
name,
got,
diff,
expectation,
tolerance
);
}
}
}