1use async_trait::async_trait;
16use saluki_common::collections::FastHashMap;
17use saluki_config::GenericConfiguration;
18use saluki_core::accounting::{MemoryBounds, MemoryBoundsBuilder};
19use saluki_core::{
20 components::{transforms::*, ComponentContext},
21 data_model::event::{
22 trace::{AttributeValue, Span, Trace},
23 Event,
24 },
25 topology::EventsBuffer,
26};
27use saluki_error::GenericError;
28use stringtheory::MetaString;
29use tracing::debug;
30
31mod catalog;
32mod core_sampler;
33mod errors;
34mod priority_sampler;
35mod probabilistic;
36mod rare_sampler;
37mod score_sampler;
38mod signature;
39
40use self::probabilistic::PROB_RATE_KEY;
41use crate::common::datadog::{
42 apm::ApmConfig, sample_by_rate, DECISION_MAKER_MANUAL, DECISION_MAKER_PROBABILISTIC, OTEL_TRACE_ID_META_KEY,
43 SAMPLING_PRIORITY_METRIC_KEY, TAG_DECISION_MAKER,
44};
45use crate::common::otlp::config::TracesConfig;
46
47const PRIORITY_AUTO_DROP: i32 = 0;
49const PRIORITY_AUTO_KEEP: i32 = 1;
50const PRIORITY_USER_KEEP: i32 = 2;
51
52const ERROR_SAMPLE_RATE: f64 = 1.0; const KEY_SPAN_SAMPLING_MECHANISM: &str = "_dd.span_sampling.mechanism";
56const KEY_ANALYZED_SPANS: &str = "_dd.analyzed";
57
58fn normalize_sampling_rate(rate: f64) -> f64 {
61 if rate <= 0.0 || rate >= 1.0 {
62 1.0
63 } else {
64 rate
65 }
66}
67
68#[derive(Debug)]
70pub struct TraceSamplerConfiguration {
71 apm_config: ApmConfig,
72 otlp_sampling_rate: f64,
73}
74
75impl TraceSamplerConfiguration {
76 pub fn from_configuration(config: &GenericConfiguration) -> Result<Self, GenericError> {
78 let apm_config = ApmConfig::from_configuration(config)?;
79 let otlp_traces: TracesConfig = config.try_get_typed("otlp_config.traces")?.unwrap_or_default();
80 let otlp_sampling_rate = normalize_sampling_rate(otlp_traces.probabilistic_sampler.sampling_percentage / 100.0);
81 Ok(Self {
82 apm_config,
83 otlp_sampling_rate,
84 })
85 }
86}
87
88#[async_trait]
89impl SynchronousTransformBuilder for TraceSamplerConfiguration {
90 async fn build(&self, _context: ComponentContext) -> Result<Box<dyn SynchronousTransform + Send>, GenericError> {
91 let sampler = TraceSampler {
94 sampling_rate: self.apm_config.probabilistic_sampler_sampling_percentage() / 100.0,
95 error_sampling_enabled: self.apm_config.error_sampling_enabled(),
96 error_tracking_standalone: self.apm_config.error_tracking_standalone_enabled(),
97 probabilistic_sampler_enabled: self.apm_config.probabilistic_sampler_enabled(),
98 otlp_sampling_rate: self.otlp_sampling_rate,
99 error_sampler: errors::ErrorsSampler::new(self.apm_config.errors_per_second(), ERROR_SAMPLE_RATE),
100 priority_sampler: priority_sampler::PrioritySampler::new(
101 self.apm_config.default_env().clone(),
102 ERROR_SAMPLE_RATE,
103 self.apm_config.target_traces_per_second(),
104 ),
105 no_priority_sampler: score_sampler::NoPrioritySampler::new(
106 self.apm_config.target_traces_per_second(),
107 ERROR_SAMPLE_RATE,
108 ),
109 rare_sampler: rare_sampler::RareSampler::new(
110 self.apm_config.rare_sampler_enabled(),
111 self.apm_config.rare_sampler_tps(),
112 std::time::Duration::from_secs_f64(self.apm_config.rare_sampler_cooldown_period_secs()),
113 self.apm_config.rare_sampler_cardinality(),
114 ),
115 };
116
117 Ok(Box::new(sampler))
118 }
119}
120
121impl MemoryBounds for TraceSamplerConfiguration {
122 fn specify_bounds(&self, builder: &mut MemoryBoundsBuilder) {
123 builder.minimum().with_single_value::<TraceSampler>("component struct");
124 }
125}
126
127pub struct TraceSampler {
128 sampling_rate: f64,
129 error_tracking_standalone: bool,
130 error_sampling_enabled: bool,
131 probabilistic_sampler_enabled: bool,
132 otlp_sampling_rate: f64,
133 error_sampler: errors::ErrorsSampler,
134 priority_sampler: priority_sampler::PrioritySampler,
135 no_priority_sampler: score_sampler::NoPrioritySampler,
136 rare_sampler: rare_sampler::RareSampler,
137}
138
139impl TraceSampler {
140 fn get_root_span_index(&self, trace: &Trace) -> Option<usize> {
143 let spans = trace.spans();
145 if spans.is_empty() {
146 return None;
147 }
148 let length = spans.len();
149 let mut parent_id_to_child: FastHashMap<u64, usize> = FastHashMap::default();
155
156 for i in 0..length {
157 let j = length - 1 - i;
160 if spans[j].parent_id() == 0 {
161 return Some(j);
162 }
163 parent_id_to_child.insert(spans[j].parent_id(), j);
164 }
165
166 for span in spans.iter() {
167 parent_id_to_child.remove(&span.span_id());
168 }
169
170 if parent_id_to_child.len() != 1 {
172 debug!(
173 "Didn't reliably find the root span for traceID:{:016x}{:016x}",
174 trace.trace_id_high, trace.trace_id_low,
175 );
176 }
177
178 if let Some((_, child_idx)) = parent_id_to_child.iter().next() {
181 return Some(*child_idx);
182 }
183
184 Some(length - 1)
186 }
187
188 fn get_user_priority(&self, trace: &Trace, root_span_idx: usize) -> Option<i32> {
190 if let Some(priority) = trace.priority {
192 return Some(priority);
193 }
194
195 if trace.spans().is_empty() {
196 return None;
197 }
198
199 if let Some(root) = trace.spans().get(root_span_idx) {
202 if let Some(p) = root
203 .attributes
204 .get(SAMPLING_PRIORITY_METRIC_KEY)
205 .and_then(AttributeValue::as_num)
206 {
207 return Some(p as i32);
208 }
209 }
210 let spans = trace.spans();
211 spans.iter().find_map(|span| {
212 span.attributes
213 .get(SAMPLING_PRIORITY_METRIC_KEY)
214 .and_then(AttributeValue::as_num)
215 .map(|p| p as i32)
216 })
217 }
218
219 fn sample_probabilistic(&self, trace_id: u64) -> bool {
221 probabilistic::ProbabilisticSampler::sample(trace_id, self.sampling_rate)
222 }
223
224 fn is_otlp_trace(&self, trace: &Trace, root_span_idx: usize) -> bool {
225 trace
226 .spans()
227 .get(root_span_idx)
228 .map(|span| {
229 span.attributes
230 .contains_key(&MetaString::from_static(OTEL_TRACE_ID_META_KEY))
231 })
232 .unwrap_or(false)
233 }
234
235 fn trace_contains_error(&self, trace: &Trace, consider_exception_span_events: bool) -> bool {
237 trace.spans().iter().any(|span| {
238 span.error() != 0 || (consider_exception_span_events && self.span_contains_exception_span_event(span))
239 })
240 }
241
242 fn span_contains_exception_span_event(&self, span: &Span) -> bool {
246 if let Some(has_exception) = span
247 .attributes
248 .get("_dd.span_events.has_exception")
249 .and_then(AttributeValue::as_string)
250 {
251 return has_exception == "true";
252 }
253 false
254 }
255
256 fn otlp_pre_sample(&mut self, trace: &mut Trace, root_span_idx: usize) -> Option<(i32, &'static str)> {
264 if self.probabilistic_sampler_enabled || !self.is_otlp_trace(trace, root_span_idx) {
265 return None;
266 }
267 let (priority, dm) = if let Some(user_priority) = self.get_user_priority(trace, root_span_idx) {
268 (user_priority, DECISION_MAKER_MANUAL)
269 } else {
270 let root_trace_id = trace.trace_id_low;
271 if sample_by_rate(root_trace_id, self.otlp_sampling_rate) {
272 (PRIORITY_AUTO_KEEP, DECISION_MAKER_PROBABILISTIC)
273 } else {
274 (PRIORITY_AUTO_DROP, DECISION_MAKER_PROBABILISTIC)
275 }
276 };
277 if priority == PRIORITY_AUTO_KEEP {
278 if let Some(root_span) = trace.spans_mut().get_mut(root_span_idx) {
279 root_span.attributes.remove(PROB_RATE_KEY);
280 }
281 }
282 Some((priority, dm))
283 }
284
285 fn analyzed_span_sampling(&self, trace: &mut Trace) -> bool {
289 let retained = trace.retain_spans(|_, span| span.attributes.contains_key(KEY_ANALYZED_SPANS));
290 if retained > 0 {
291 trace.dropped_trace = false;
292 trace.priority = Some(PRIORITY_USER_KEEP);
293 trace.otlp_sampling_rate = Some(self.sampling_rate);
294 true
295 } else {
296 false
297 }
298 }
299
300 fn has_analyzed_spans(&self, trace: &Trace) -> bool {
302 trace
303 .spans()
304 .iter()
305 .any(|span| span.attributes.contains_key(KEY_ANALYZED_SPANS))
306 }
307
308 fn single_span_sampling(&self, trace: &mut Trace) -> bool {
311 let retained = trace.retain_spans(|_, span| span.attributes.contains_key(KEY_SPAN_SAMPLING_MECHANISM));
312 if retained > 0 {
313 trace.dropped_trace = false;
314 trace.priority = Some(PRIORITY_USER_KEEP);
315 trace.otlp_sampling_rate = Some(self.sampling_rate);
316 true
317 } else {
318 false
319 }
320 }
321
322 fn run_samplers(&mut self, trace: &mut Trace) -> (bool, i32, &'static str, Option<usize>) {
327 if trace.spans().is_empty() {
330 return (false, PRIORITY_AUTO_DROP, "", None);
331 }
332
333 let now = std::time::SystemTime::now();
334 let Some(root_span_idx) = self.get_root_span_index(trace) else {
335 return (false, PRIORITY_AUTO_DROP, "", None);
336 };
337
338 if self.error_tracking_standalone {
341 let otlp_pre_sample = self.otlp_pre_sample(trace, root_span_idx);
342 if self.trace_contains_error(trace, true) {
343 let keep = self.error_sampler.sample_error(now, trace, root_span_idx);
344 let default_priority = if keep { PRIORITY_AUTO_KEEP } else { PRIORITY_AUTO_DROP };
345 let (priority, dm) = otlp_pre_sample.unwrap_or((default_priority, ""));
346 return (keep, priority, dm, Some(root_span_idx));
347 }
348 let (pre_priority, pre_dm) = otlp_pre_sample.unwrap_or((PRIORITY_AUTO_DROP, ""));
349 return (false, pre_priority, pre_dm, Some(root_span_idx));
350 }
351
352 let contains_error = self.trace_contains_error(trace, false);
353
354 let rare = self.rare_sampler.sample(trace, root_span_idx);
358
359 if self.probabilistic_sampler_enabled {
361 let mut prob_keep = false;
362 let mut decision_maker = "";
363
364 if rare {
365 prob_keep = true;
367 } else {
368 let root_trace_id = trace.trace_id_low;
370 if self.sample_probabilistic(root_trace_id) {
371 decision_maker = DECISION_MAKER_PROBABILISTIC;
372 prob_keep = true;
373
374 if let Some(root_span) = trace.spans_mut().get_mut(root_span_idx) {
375 root_span.attributes.insert(
376 MetaString::from(PROB_RATE_KEY),
377 AttributeValue::Float(self.sampling_rate),
378 );
379 }
380 } else if self.error_sampling_enabled && contains_error {
381 prob_keep = self.error_sampler.sample_error(now, trace, root_span_idx);
382 }
383 }
384
385 let priority = if prob_keep {
386 PRIORITY_AUTO_KEEP
387 } else {
388 PRIORITY_AUTO_DROP
389 };
390
391 return (prob_keep, priority, decision_maker, Some(root_span_idx));
392 }
393
394 let user_priority = self.get_user_priority(trace, root_span_idx);
395 if let Some(priority) = user_priority {
396 if priority < PRIORITY_AUTO_DROP {
397 return (false, priority, "", Some(root_span_idx));
399 }
400
401 if rare {
402 return (true, priority, "", Some(root_span_idx));
403 }
404
405 if self.priority_sampler.sample(now, trace, root_span_idx, priority, 0.0) {
406 return (true, priority, "", Some(root_span_idx));
407 }
408 } else if self.is_otlp_trace(trace, root_span_idx) {
409 if rare {
411 return (true, PRIORITY_AUTO_KEEP, "", Some(root_span_idx));
412 }
413
414 let root_trace_id = trace.trace_id_low;
416 if sample_by_rate(root_trace_id, self.otlp_sampling_rate) {
417 if let Some(root_span) = trace.spans_mut().get_mut(root_span_idx) {
418 root_span.attributes.remove(PROB_RATE_KEY);
419 }
420 return (
421 true,
422 PRIORITY_AUTO_KEEP,
423 DECISION_MAKER_PROBABILISTIC,
424 Some(root_span_idx),
425 );
426 }
427 } else {
428 if rare {
429 return (true, PRIORITY_AUTO_KEEP, "", Some(root_span_idx));
430 }
431 if self.no_priority_sampler.sample(now, trace, root_span_idx) {
432 return (true, PRIORITY_AUTO_KEEP, "", Some(root_span_idx));
433 }
434 }
435
436 if self.error_sampling_enabled && contains_error {
437 let keep = self.error_sampler.sample_error(now, trace, root_span_idx);
438 if keep {
439 return (true, PRIORITY_AUTO_KEEP, "", Some(root_span_idx));
440 }
441 }
442
443 (false, PRIORITY_AUTO_DROP, "", Some(root_span_idx))
445 }
446
447 fn apply_sampling_metadata(
452 &self, trace: &mut Trace, keep: bool, priority: i32, decision_maker: &str, root_span_idx: usize,
453 ) {
454 let is_otlp = self.is_otlp_trace(trace, root_span_idx);
455 let root_span_value = match trace.spans_mut().get_mut(root_span_idx) {
456 Some(span) => span,
457 None => return,
458 };
459
460 let existing_decision_maker = if decision_maker.is_empty() {
462 root_span_value
463 .attributes
464 .get(TAG_DECISION_MAKER)
465 .and_then(AttributeValue::as_string)
466 .cloned()
467 } else {
468 None
469 };
470 let decision_maker_meta = if decision_maker.is_empty() {
471 existing_decision_maker
472 } else {
473 Some(MetaString::from(decision_maker))
474 };
475
476 if priority > 0 && !(is_otlp && self.probabilistic_sampler_enabled) {
481 if let Some(dm) = decision_maker_meta.as_ref() {
482 root_span_value
483 .attributes
484 .insert(MetaString::from(TAG_DECISION_MAKER), AttributeValue::String(dm.clone()));
485 }
486 }
487
488 trace.dropped_trace = !keep;
490 trace.priority = Some(priority);
491 trace.decision_maker = if priority > 0 { decision_maker_meta } else { None };
492 trace.otlp_sampling_rate = Some(if is_otlp {
493 self.otlp_sampling_rate
494 } else {
495 self.sampling_rate
496 });
497 }
498
499 fn process_trace(&mut self, trace: &mut Trace) -> bool {
500 let (keep, priority, decision_maker, root_span_idx) = self.run_samplers(trace);
505
506 if keep || self.error_tracking_standalone {
509 if let Some(root_idx) = root_span_idx {
510 self.apply_sampling_metadata(trace, keep, priority, decision_maker, root_idx);
511 }
512 return true;
513 }
514
515 let modified = self.single_span_sampling(trace);
518 if !modified {
519 if self.analyzed_span_sampling(trace) {
521 return true;
522 }
523 } else if self.has_analyzed_spans(trace) {
524 debug!(
526 "Detected both analytics events AND single span sampling in the same trace. Single span sampling wins because App Analytics is deprecated."
527 );
528 return true;
529 }
530
531 if modified {
533 return true;
534 }
535
536 debug!("Dropping trace with priority {}", priority);
538 false
539 }
540}
541
542impl SynchronousTransform for TraceSampler {
543 fn transform_buffer(&mut self, buffer: &mut EventsBuffer) {
544 buffer.remove_if(|event| match event {
545 Event::Trace(trace) => !self.process_trace(trace),
546 _ => false,
547 });
548 }
549}
550
551#[cfg(test)]
552mod tests {
553 use std::collections::HashMap;
554
555 use saluki_core::data_model::event::trace::{AttributeValue, Span as DdSpan, Trace};
556 const PRIORITY_USER_DROP: i32 = -1;
557
558 use super::*;
559 fn create_test_sampler() -> TraceSampler {
560 TraceSampler {
561 sampling_rate: 1.0,
562 error_sampling_enabled: true,
563 error_tracking_standalone: false,
564 probabilistic_sampler_enabled: true,
565 otlp_sampling_rate: 1.0,
566 error_sampler: errors::ErrorsSampler::new(10.0, 1.0),
567 priority_sampler: priority_sampler::PrioritySampler::new(MetaString::from("agent-env"), 1.0, 10.0),
568 no_priority_sampler: score_sampler::NoPrioritySampler::new(10.0, 1.0),
569 rare_sampler: rare_sampler::RareSampler::new(false, 5.0, std::time::Duration::from_secs(300), 200),
570 }
571 }
572
573 fn create_test_span(span_id: u64, error: i32) -> DdSpan {
574 DdSpan::new(
575 MetaString::from("test-service"),
576 MetaString::from("test-operation"),
577 MetaString::from("test-resource"),
578 MetaString::from("test-type"),
579 span_id,
580 0, 0, 1000, error,
584 )
585 }
586
587 fn create_test_span_with_metrics(span_id: u64, metrics: HashMap<String, f64>) -> DdSpan {
588 let attrs: saluki_common::collections::FastHashMap<MetaString, AttributeValue> = metrics
589 .into_iter()
590 .map(|(k, v)| (MetaString::from(k), AttributeValue::Float(v)))
591 .collect();
592 create_test_span(span_id, 0).with_attributes(attrs)
593 }
594
595 #[allow(dead_code)]
596 fn create_test_span_with_meta(span_id: u64, meta: HashMap<String, String>) -> DdSpan {
597 let attrs: saluki_common::collections::FastHashMap<MetaString, AttributeValue> = meta
598 .into_iter()
599 .map(|(k, v)| (MetaString::from(k), AttributeValue::String(MetaString::from(v))))
600 .collect();
601 create_test_span(span_id, 0).with_attributes(attrs)
602 }
603
604 fn create_test_trace(spans: Vec<DdSpan>) -> Trace {
605 Trace::new(spans)
606 }
607
608 #[test]
609 fn user_priority_detection() {
610 let sampler = create_test_sampler();
611
612 let mut metrics = HashMap::new();
614 metrics.insert(SAMPLING_PRIORITY_METRIC_KEY.to_string(), 2.0);
615 let span = create_test_span_with_metrics(1, metrics);
616 let trace = create_test_trace(vec![span]);
617 let root_idx = sampler.get_root_span_index(&trace).unwrap();
618
619 assert_eq!(sampler.get_user_priority(&trace, root_idx), Some(2));
620
621 let mut metrics = HashMap::new();
623 metrics.insert(SAMPLING_PRIORITY_METRIC_KEY.to_string(), -1.0);
624 let span = create_test_span_with_metrics(1, metrics);
625 let trace = create_test_trace(vec![span]);
626 let root_idx = sampler.get_root_span_index(&trace).unwrap();
627
628 assert_eq!(sampler.get_user_priority(&trace, root_idx), Some(-1));
629
630 let span = create_test_span(1, 0);
632 let trace = create_test_trace(vec![span]);
633 let root_idx = sampler.get_root_span_index(&trace).unwrap();
634
635 assert_eq!(sampler.get_user_priority(&trace, root_idx), None);
636 }
637
638 #[test]
639 fn trace_level_priority_takes_precedence() {
640 let sampler = create_test_sampler();
641
642 let mut metrics_root = HashMap::new();
645 metrics_root.insert(SAMPLING_PRIORITY_METRIC_KEY.to_string(), 0.0);
646 let root_span = create_test_span_with_metrics(1, metrics_root);
647
648 let mut metrics_later = HashMap::new();
649 metrics_later.insert(SAMPLING_PRIORITY_METRIC_KEY.to_string(), 1.0);
650 let later_span = create_test_span_with_metrics(2, metrics_later).with_parent_id(1);
651
652 let mut trace = create_test_trace(vec![root_span, later_span]);
653 let root_idx = sampler.get_root_span_index(&trace).unwrap();
654
655 assert_eq!(sampler.get_user_priority(&trace, root_idx), Some(0));
657
658 trace.priority = Some(2);
660
661 assert_eq!(sampler.get_user_priority(&trace, root_idx), Some(2));
663
664 let span_no_priority = create_test_span(3, 0);
666 let mut trace_only_trace_level = create_test_trace(vec![span_no_priority]);
667 trace_only_trace_level.priority = Some(1);
668 let root_idx = sampler.get_root_span_index(&trace_only_trace_level).unwrap();
669
670 assert_eq!(sampler.get_user_priority(&trace_only_trace_level, root_idx), Some(1));
671 }
672
673 #[test]
674 fn manual_keep_with_trace_level_priority() {
675 let mut sampler = create_test_sampler();
676 sampler.probabilistic_sampler_enabled = false; let span = create_test_span(1, 0);
680 let mut trace = create_test_trace(vec![span]);
681 trace.priority = Some(PRIORITY_USER_KEEP);
682
683 let (keep, priority, decision_maker, _) = sampler.run_samplers(&mut trace);
684 assert!(keep);
685 assert_eq!(priority, PRIORITY_USER_KEEP);
686 assert_eq!(decision_maker, "");
687
688 let span = create_test_span(1, 0);
690 let mut trace = create_test_trace(vec![span]);
691 trace.priority = Some(PRIORITY_USER_DROP);
692
693 let (keep, priority, _, _) = sampler.run_samplers(&mut trace);
694 assert!(!keep); assert_eq!(priority, PRIORITY_USER_DROP);
696
697 let span = create_test_span(1, 0);
699 let mut trace = create_test_trace(vec![span]);
700 trace.priority = Some(PRIORITY_AUTO_KEEP);
701
702 let (keep, priority, decision_maker, _) = sampler.run_samplers(&mut trace);
703 assert!(keep);
704 assert_eq!(priority, PRIORITY_AUTO_KEEP);
705 assert_eq!(decision_maker, "");
706 }
707
708 #[test]
709 fn probabilistic_sampling_known_decisions() {
710 struct Case {
719 trace_id: u64,
720 rate: f64,
721 expected_keep: bool,
722 }
723
724 let cases = [
725 Case {
727 trace_id: 0x1234567890ABCDEF,
728 rate: 1.0,
729 expected_keep: true,
730 },
731 Case {
733 trace_id: 0x1234567890ABCDEF,
734 rate: 0.0,
735 expected_keep: false,
736 },
737 Case {
739 trace_id: 0x1234567890ABCDEF,
740 rate: 0.10,
741 expected_keep: false,
742 },
743 Case {
744 trace_id: 0x1234567890ABCDEF,
745 rate: 0.20,
746 expected_keep: true,
747 },
748 Case {
750 trace_id: 0,
751 rate: 0.50,
752 expected_keep: false,
753 },
754 Case {
755 trace_id: 0,
756 rate: 0.60,
757 expected_keep: true,
758 },
759 Case {
761 trace_id: u64::MAX,
762 rate: 0.50,
763 expected_keep: false,
764 },
765 Case {
766 trace_id: u64::MAX,
767 rate: 0.80,
768 expected_keep: true,
769 },
770 ];
771
772 for case in cases {
773 let mut sampler = create_test_sampler();
774 sampler.sampling_rate = case.rate;
775 assert_eq!(
776 sampler.sample_probabilistic(case.trace_id),
777 case.expected_keep,
778 "trace_id={:#018x} rate={}",
779 case.trace_id,
780 case.rate
781 );
782 }
783 }
784
785 #[test]
786 fn probabilistic_sampling_is_deterministic() {
787 let sampler = create_test_sampler();
791 let trace_id = 0x1234567890ABCDEF_u64;
792 assert_eq!(
793 sampler.sample_probabilistic(trace_id),
794 sampler.sample_probabilistic(trace_id)
795 );
796 }
797
798 #[test]
799 fn error_detection() {
800 let sampler = create_test_sampler();
801
802 let span_with_error = create_test_span(1, 1);
804 let trace = create_test_trace(vec![span_with_error]);
805 assert!(sampler.trace_contains_error(&trace, false));
806
807 let span_without_error = create_test_span(1, 0);
809 let trace = create_test_trace(vec![span_without_error]);
810 assert!(!sampler.trace_contains_error(&trace, false));
811 }
812
813 #[test]
814 fn sampling_priority_order() {
815 let mut sampler = create_test_sampler();
817 sampler.sampling_rate = 0.5; sampler.probabilistic_sampler_enabled = true;
819
820 let span_with_error = create_test_span(1, 1);
823 let mut trace = create_test_trace(vec![span_with_error]);
824 trace.trace_id_low = u64::MAX - 1;
825
826 let (keep, priority, decision_maker, _) = sampler.run_samplers(&mut trace);
827 assert!(keep);
828 assert_eq!(priority, PRIORITY_AUTO_KEEP);
829 assert_eq!(decision_maker, ""); let mut sampler = create_test_sampler();
833 sampler.probabilistic_sampler_enabled = false; let mut metrics = HashMap::new();
836 metrics.insert(SAMPLING_PRIORITY_METRIC_KEY.to_string(), 2.0);
837 let span = create_test_span_with_metrics(1, metrics);
838 let mut trace = create_test_trace(vec![span]);
839
840 let (keep, priority, decision_maker, _) = sampler.run_samplers(&mut trace);
841 assert!(keep);
842 assert_eq!(priority, 2); assert_eq!(decision_maker, "");
844 }
845
846 #[test]
847 fn empty_trace_handling() {
848 let mut sampler = create_test_sampler();
849 let mut trace = create_test_trace(vec![]);
850
851 let (keep, priority, _, _) = sampler.run_samplers(&mut trace);
852 assert!(!keep);
853 assert_eq!(priority, PRIORITY_AUTO_DROP);
854 }
855
856 #[test]
857 fn root_span_detection() {
858 let sampler = create_test_sampler();
859
860 let root_span = DdSpan::new(
862 MetaString::from("service"),
863 MetaString::from("operation"),
864 MetaString::from("resource"),
865 MetaString::from("type"),
866 1,
867 0, 0,
869 1000,
870 0,
871 );
872 let child_span = DdSpan::new(
873 MetaString::from("service"),
874 MetaString::from("child_op"),
875 MetaString::from("resource"),
876 MetaString::from("type"),
877 2,
878 1, 100,
880 500,
881 0,
882 );
883 let trace = create_test_trace(vec![child_span.clone(), root_span.clone()]);
885 let root_idx = sampler.get_root_span_index(&trace).unwrap();
886 assert_eq!(trace.spans()[root_idx].span_id(), 1);
887
888 let orphan_span = DdSpan::new(
890 MetaString::from("service"),
891 MetaString::from("orphan"),
892 MetaString::from("resource"),
893 MetaString::from("type"),
894 3,
895 999, 200,
897 300,
898 0,
899 );
900 let trace = create_test_trace(vec![orphan_span]);
901 let root_idx = sampler.get_root_span_index(&trace).unwrap();
902 assert_eq!(trace.spans()[root_idx].span_id(), 3);
903
904 let span1 = create_test_span(1, 0);
906 let span2 = create_test_span(2, 0);
907 let trace = create_test_trace(vec![span1, span2]);
908 let root_idx = sampler.get_root_span_index(&trace).unwrap();
910 assert_eq!(trace.spans()[root_idx].span_id(), 2);
911 }
912
913 #[test]
914 fn single_span_sampling() {
915 let mut sampler = create_test_sampler();
916
917 sampler.sampling_rate = 0.0; sampler.probabilistic_sampler_enabled = true;
920
921 let mut attrs_map = saluki_common::collections::FastHashMap::default();
923 attrs_map.insert(
924 MetaString::from(KEY_SPAN_SAMPLING_MECHANISM),
925 AttributeValue::Float(8.0),
926 );
927 let sss_span = create_test_span(1, 0).with_attributes(attrs_map.clone());
928
929 let regular_span = create_test_span(2, 0);
931
932 let mut trace = create_test_trace(vec![sss_span.clone(), regular_span]);
933
934 let modified = sampler.single_span_sampling(&mut trace);
936 assert!(modified);
937 assert_eq!(trace.spans().len(), 1); assert_eq!(trace.spans()[0].span_id(), 1); assert_eq!(trace.priority, Some(PRIORITY_USER_KEEP));
942
943 let trace_without_sss = create_test_trace(vec![create_test_span(3, 0)]);
945 let mut trace_copy = trace_without_sss.clone();
946 let modified = sampler.single_span_sampling(&mut trace_copy);
947 assert!(!modified);
948 assert_eq!(trace_copy.spans().len(), trace_without_sss.spans().len());
949 }
950
951 #[test]
952 fn analytics_events() {
953 let sampler = create_test_sampler();
954
955 let mut attrs_map = saluki_common::collections::FastHashMap::default();
957 attrs_map.insert(MetaString::from(KEY_ANALYZED_SPANS), AttributeValue::Float(1.0));
958 let analyzed_span = create_test_span(1, 0).with_attributes(attrs_map.clone());
959 let regular_span = create_test_span(2, 0);
960
961 let mut trace = create_test_trace(vec![analyzed_span.clone(), regular_span]);
962
963 let analyzed_span_ids: Vec<u64> = trace
964 .spans()
965 .iter()
966 .filter(|span| span.attributes.contains_key(KEY_ANALYZED_SPANS))
967 .map(|span| span.span_id())
968 .collect();
969 assert_eq!(analyzed_span_ids, vec![1]);
970
971 assert!(sampler.has_analyzed_spans(&trace));
972 let modified = sampler.analyzed_span_sampling(&mut trace);
973 assert!(modified);
974 assert_eq!(trace.spans().len(), 1);
975 assert_eq!(trace.spans()[0].span_id(), 1);
976 assert_eq!(trace.priority, Some(PRIORITY_USER_KEEP));
977
978 let trace_no_analytics = create_test_trace(vec![create_test_span(3, 0)]);
980 let mut trace_no_analytics_copy = trace_no_analytics.clone();
981 let analyzed_span_ids: Vec<u64> = trace_no_analytics
982 .spans()
983 .iter()
984 .filter(|span| span.attributes.contains_key(KEY_ANALYZED_SPANS))
985 .map(|span| span.span_id())
986 .collect();
987 assert!(analyzed_span_ids.is_empty());
988 assert!(!sampler.has_analyzed_spans(&trace_no_analytics));
989 let modified = sampler.analyzed_span_sampling(&mut trace_no_analytics_copy);
990 assert!(!modified);
991 assert_eq!(trace_no_analytics_copy.spans().len(), trace_no_analytics.spans().len());
992 }
993
994 #[test]
995 fn probabilistic_sampling_with_prob_rate_key() {
996 let mut sampler = create_test_sampler();
997 sampler.sampling_rate = 0.75; sampler.probabilistic_sampler_enabled = true;
999
1000 let trace_id = 12345_u64;
1002 let root_span = DdSpan::new(
1003 MetaString::from("service"),
1004 MetaString::from("operation"),
1005 MetaString::from("resource"),
1006 MetaString::from("type"),
1007 1,
1008 0, 0,
1010 1000,
1011 0,
1012 );
1013 let mut trace = create_test_trace(vec![root_span]);
1014 trace.trace_id_low = trace_id;
1015
1016 let (keep, priority, decision_maker, root_span_idx) = sampler.run_samplers(&mut trace);
1017
1018 if keep && decision_maker == DECISION_MAKER_PROBABILISTIC {
1019 assert_eq!(priority, PRIORITY_AUTO_KEEP);
1021 assert_eq!(decision_maker, DECISION_MAKER_PROBABILISTIC); let root_idx = root_span_idx.unwrap_or(0);
1025 let root_span = &trace.spans()[root_idx];
1026 assert!(root_span.attributes.contains_key(PROB_RATE_KEY));
1027 assert_eq!(
1028 root_span
1029 .attributes
1030 .get(PROB_RATE_KEY)
1031 .and_then(AttributeValue::as_float),
1032 Some(0.75)
1033 );
1034
1035 let mut trace_with_metadata = trace.clone();
1037 sampler.apply_sampling_metadata(&mut trace_with_metadata, keep, priority, decision_maker, root_idx);
1038
1039 let modified_root = &trace_with_metadata.spans()[root_idx];
1041 assert!(modified_root.attributes.contains_key(TAG_DECISION_MAKER));
1042 assert_eq!(
1043 modified_root
1044 .attributes
1045 .get(TAG_DECISION_MAKER)
1046 .and_then(AttributeValue::as_string),
1047 Some(&MetaString::from(DECISION_MAKER_PROBABILISTIC))
1048 );
1049 }
1050 }
1051
1052 fn create_top_level_span(span_id: u64) -> DdSpan {
1062 let mut attrs = saluki_common::collections::FastHashMap::default();
1063 attrs.insert(MetaString::from("_top_level"), AttributeValue::Float(1.0));
1064 create_test_span(span_id, 0).with_attributes(attrs)
1065 }
1066
1067 fn create_sampler_with_rare_enabled() -> TraceSampler {
1070 TraceSampler {
1071 rare_sampler: rare_sampler::RareSampler::new(true, 1000.0, std::time::Duration::from_secs(300), 200),
1072 ..create_test_sampler()
1073 }
1074 }
1075
1076 #[test]
1080 fn rare_sampler_catches_unsampled_trace() {
1081 let mut sampler = create_sampler_with_rare_enabled();
1082 sampler.sampling_rate = 0.0; sampler.probabilistic_sampler_enabled = true;
1084
1085 let span = create_top_level_span(1);
1086 let mut trace = create_test_trace(vec![span]);
1087
1088 let (keep, priority, decision_maker, _) = sampler.run_samplers(&mut trace);
1089 assert!(keep, "rare sampler should catch first occurrence");
1090 assert_eq!(priority, PRIORITY_AUTO_KEEP);
1091 assert_eq!(decision_maker, "", "rare sampler does not set _dd.p.dm");
1092 }
1093
1094 #[test]
1098 fn rare_sampler_sets_rare_metric_on_first_occurrence() {
1099 let mut sampler = create_sampler_with_rare_enabled();
1100 sampler.sampling_rate = 0.0;
1101 sampler.probabilistic_sampler_enabled = true;
1102
1103 let span = create_top_level_span(1);
1104 let mut trace = create_test_trace(vec![span]);
1105
1106 let (keep, _, _, root_idx) = sampler.run_samplers(&mut trace);
1107 assert!(keep);
1108 let root = &trace.spans()[root_idx.unwrap()];
1109 assert_eq!(
1110 root.attributes
1111 .get(rare_sampler::RARE_KEY)
1112 .and_then(AttributeValue::as_float),
1113 Some(1.0),
1114 "_dd.rare should be 1 on first occurrence"
1115 );
1116 }
1117
1118 #[test]
1123 fn rare_sampler_does_not_resample_within_ttl() {
1124 let mut sampler = create_sampler_with_rare_enabled();
1125 sampler.sampling_rate = 0.0;
1126 sampler.probabilistic_sampler_enabled = true;
1127
1128 let span1 = create_top_level_span(1);
1130 let mut trace1 = create_test_trace(vec![span1]);
1131 let (keep1, _, _, _) = sampler.run_samplers(&mut trace1);
1132 assert!(keep1, "first occurrence should be kept by rare sampler");
1133
1134 let span2 = create_top_level_span(2);
1137 let mut trace2 = create_test_trace(vec![span2]);
1138 let (keep2, priority2, _, _) = sampler.run_samplers(&mut trace2);
1139 assert!(!keep2, "second occurrence within TTL should be dropped");
1140 assert_eq!(priority2, PRIORITY_AUTO_DROP);
1141 }
1142
1143 #[test]
1147 fn rare_sampler_disabled_does_not_catch_unsampled() {
1148 let mut sampler = create_test_sampler(); sampler.sampling_rate = 0.0;
1150 sampler.probabilistic_sampler_enabled = true;
1151
1152 let span = create_top_level_span(1);
1153 let mut trace = create_test_trace(vec![span]);
1154
1155 let (keep, priority, _, _) = sampler.run_samplers(&mut trace);
1156 assert!(!keep, "rare disabled should not catch the trace");
1157 assert_eq!(priority, PRIORITY_AUTO_DROP);
1158 }
1159
1160 #[test]
1163 fn rare_sampler_catches_priority_auto_drop_in_legacy_path() {
1164 let mut sampler = create_sampler_with_rare_enabled();
1165 sampler.probabilistic_sampler_enabled = false;
1166
1167 let mut attrs = saluki_common::collections::FastHashMap::default();
1168 attrs.insert(MetaString::from("_top_level"), AttributeValue::Float(1.0));
1169 attrs.insert(
1170 MetaString::from(SAMPLING_PRIORITY_METRIC_KEY),
1171 AttributeValue::Float(PRIORITY_AUTO_DROP as f64),
1172 );
1173 let span = create_test_span(1, 0).with_attributes(attrs);
1174 let mut trace = create_test_trace(vec![span]);
1175
1176 let (keep, priority, decision_maker, _) = sampler.run_samplers(&mut trace);
1177 assert!(keep, "rare sampler should catch PriorityAutoDrop on first occurrence");
1178 assert_eq!(priority, PRIORITY_AUTO_DROP, "tracer-set priority should be preserved");
1179 assert_eq!(decision_maker, "");
1180 }
1181
1182 #[test]
1185 fn rare_sampler_preserves_user_keep_priority_in_legacy_path() {
1186 let mut sampler = create_sampler_with_rare_enabled();
1187 sampler.probabilistic_sampler_enabled = false;
1188
1189 let mut attrs = saluki_common::collections::FastHashMap::default();
1190 attrs.insert(MetaString::from("_top_level"), AttributeValue::Float(1.0));
1191 attrs.insert(
1192 MetaString::from(SAMPLING_PRIORITY_METRIC_KEY),
1193 AttributeValue::Float(2.0),
1194 ); let span = create_test_span(1, 0).with_attributes(attrs);
1196 let mut trace = create_test_trace(vec![span]);
1197
1198 let (keep, priority, _, _) = sampler.run_samplers(&mut trace);
1199 assert!(keep);
1200 assert_eq!(priority, 2, "UserKeep priority must not be downgraded to AutoKeep");
1201 }
1202
1203 #[test]
1205 fn probabilistic_100_percent_keeps_trace_with_decision_maker() {
1206 let mut sampler = create_test_sampler(); sampler.sampling_rate = 1.0;
1208 sampler.probabilistic_sampler_enabled = true;
1209
1210 let span = create_top_level_span(1);
1211 let mut trace = create_test_trace(vec![span]);
1212
1213 let (keep, priority, decision_maker, _) = sampler.run_samplers(&mut trace);
1214 assert!(keep);
1215 assert_eq!(priority, PRIORITY_AUTO_KEEP);
1216 assert_eq!(decision_maker, DECISION_MAKER_PROBABILISTIC);
1217 }
1218
1219 #[test]
1221 fn probabilistic_0_percent_drops_trace() {
1222 let mut sampler = create_test_sampler(); sampler.sampling_rate = 0.0;
1224 sampler.probabilistic_sampler_enabled = true;
1225 sampler.error_sampling_enabled = false;
1226
1227 let span = create_top_level_span(1);
1228 let mut trace = create_test_trace(vec![span]);
1229
1230 let (keep, priority, _, _) = sampler.run_samplers(&mut trace);
1231 assert!(!keep);
1232 assert_eq!(priority, PRIORITY_AUTO_DROP);
1233 }
1234
1235 #[test]
1238 fn rare_sampler_catches_otlp_no_priority_trace() {
1239 let mut sampler = create_sampler_with_rare_enabled();
1240 sampler.probabilistic_sampler_enabled = false;
1241 sampler.error_sampling_enabled = false;
1242 sampler.otlp_sampling_rate = 0.0;
1243
1244 let mut span = create_top_level_span(1);
1245 span.attributes.insert(
1246 MetaString::from_static(OTEL_TRACE_ID_META_KEY),
1247 AttributeValue::String(MetaString::from("00000000000000000000000000000001")),
1248 );
1249 let mut trace = create_test_trace(vec![span]);
1250
1251 let (keep, priority, decision_maker, root_idx) = sampler.run_samplers(&mut trace);
1252 assert!(
1253 keep,
1254 "rare sampler should keep OTLP trace with no priority on first occurrence"
1255 );
1256 assert_eq!(priority, PRIORITY_AUTO_KEEP);
1257 assert_eq!(decision_maker, "");
1258 assert_eq!(
1259 trace.spans()[root_idx.unwrap()]
1260 .attributes
1261 .get(rare_sampler::RARE_KEY)
1262 .and_then(AttributeValue::as_float),
1263 Some(1.0),
1264 "_dd.rare should be set to 1 on first occurrence"
1265 );
1266 }
1267
1268 #[test]
1273 fn rare_wins_over_probabilistic_no_decision_maker_tag() {
1274 let mut sampler = create_sampler_with_rare_enabled();
1275 sampler.sampling_rate = 1.0;
1276 sampler.probabilistic_sampler_enabled = true;
1277
1278 let span = create_top_level_span(1);
1279 let mut trace = create_test_trace(vec![span]);
1280
1281 let (keep, priority, decision_maker, _) = sampler.run_samplers(&mut trace);
1282 assert!(keep);
1283 assert_eq!(priority, PRIORITY_AUTO_KEEP);
1284 assert_eq!(decision_maker, "", "rare takes precedence—_dd.p.dm must not be set");
1285 }
1286
1287 #[test]
1292 fn rare_catches_error_trace_before_error_sampler() {
1293 let mut sampler = create_sampler_with_rare_enabled();
1294 sampler.probabilistic_sampler_enabled = false;
1295 sampler.error_sampling_enabled = true;
1296
1297 let span = create_top_level_span(1);
1298 let error_span = create_test_span(2, 1); let mut trace = create_test_trace(vec![span, error_span]);
1300
1301 let (keep, priority, decision_maker, _) = sampler.run_samplers(&mut trace);
1302 assert!(keep, "rare should catch the trace before the error sampler");
1303 assert_eq!(priority, PRIORITY_AUTO_KEEP);
1304 assert_eq!(decision_maker, "");
1305 }
1306
1307 #[test]
1312 fn manual_drop_short_circuits_before_rare() {
1313 let mut sampler = create_sampler_with_rare_enabled();
1314 sampler.probabilistic_sampler_enabled = false;
1315
1316 let mut attrs = saluki_common::collections::FastHashMap::default();
1317 attrs.insert(MetaString::from("_top_level"), AttributeValue::Float(1.0));
1318 attrs.insert(
1319 MetaString::from(SAMPLING_PRIORITY_METRIC_KEY),
1320 AttributeValue::Float(-1.0),
1321 ); let span = create_test_span(1, 0).with_attributes(attrs);
1323 let mut trace = create_test_trace(vec![span]);
1324
1325 let (keep, priority, _, _) = sampler.run_samplers(&mut trace);
1326 assert!(!keep, "UserDrop must be dropped even when rare would match");
1327 assert_eq!(priority, -1);
1328 }
1329
1330 fn create_sampler_with_ets() -> TraceSampler {
1334 TraceSampler {
1335 error_tracking_standalone: true,
1336 ..create_test_sampler()
1337 }
1338 }
1339
1340 #[test]
1342 fn ets_keeps_trace_with_error() {
1343 let mut sampler = create_sampler_with_ets();
1344
1345 let span = create_test_span(1, 1); let mut trace = create_test_trace(vec![span]);
1347
1348 let (keep, priority, decision_maker, _) = sampler.run_samplers(&mut trace);
1349 assert!(keep, "ETS should keep traces with errors");
1350 assert_eq!(priority, PRIORITY_AUTO_KEEP);
1351 assert_eq!(decision_maker, "", "ETS does not set a decision maker");
1352 }
1353
1354 #[test]
1356 fn ets_drops_trace_without_error() {
1357 let mut sampler = create_sampler_with_ets();
1358
1359 let span = create_test_span(1, 0); let mut trace = create_test_trace(vec![span]);
1361
1362 let (keep, priority, _, _) = sampler.run_samplers(&mut trace);
1363 assert!(!keep, "ETS should drop traces without errors");
1364 assert_eq!(priority, PRIORITY_AUTO_DROP);
1365 }
1366
1367 #[test]
1369 fn ets_forwards_dropped_trace_with_dropped_flag() {
1370 let mut sampler = create_sampler_with_ets();
1371
1372 let mut attrs = saluki_common::collections::FastHashMap::default();
1374 attrs.insert(
1375 MetaString::from(KEY_SPAN_SAMPLING_MECHANISM),
1376 AttributeValue::Float(8.0),
1377 );
1378 let span = create_test_span(1, 0).with_attributes(attrs);
1379 let mut trace = create_test_trace(vec![span]);
1380
1381 let forwarded = sampler.process_trace(&mut trace);
1382 assert!(forwarded, "ETS should forward non-error traces to intake");
1383 assert!(trace.dropped_trace, "non-error ETS trace should have DroppedTrace=true");
1384 }
1385
1386 #[test]
1388 fn ets_keeps_trace_with_exception_span_event() {
1389 let mut sampler = create_sampler_with_ets();
1390
1391 let mut attrs = saluki_common::collections::FastHashMap::default();
1393 attrs.insert(
1394 MetaString::from("_dd.span_events.has_exception"),
1395 AttributeValue::String(MetaString::from("true")),
1396 );
1397 let span = create_test_span(1, 0).with_attributes(attrs);
1398 let mut trace = create_test_trace(vec![span]);
1399
1400 let (keep, _, _, _) = sampler.run_samplers(&mut trace);
1401 assert!(keep, "ETS should treat exception span events as errors");
1402 }
1403
1404 #[test]
1406 fn ets_disabled_uses_normal_sampling() {
1407 let mut sampler = create_test_sampler(); sampler.sampling_rate = 1.0;
1409 sampler.probabilistic_sampler_enabled = true;
1410
1411 let span = create_test_span(1, 0); let mut trace = create_test_trace(vec![span]);
1413
1414 let (keep, _, decision_maker, _) = sampler.run_samplers(&mut trace);
1415 assert!(keep, "normal probabilistic sampling should keep the trace");
1416 assert_eq!(decision_maker, DECISION_MAKER_PROBABILISTIC);
1417 }
1418
1419 fn create_otlp_test_span(span_id: u64, error: i32) -> DdSpan {
1425 let mut attrs = saluki_common::collections::FastHashMap::default();
1426 attrs.insert(
1427 MetaString::from_static(OTEL_TRACE_ID_META_KEY),
1428 AttributeValue::String(MetaString::from("0000000000000000deadbeefcafebabe")),
1429 );
1430 create_test_span(span_id, error).with_attributes(attrs)
1431 }
1432
1433 fn create_sampler_with_ets_legacy() -> TraceSampler {
1434 TraceSampler {
1435 error_tracking_standalone: true,
1436 probabilistic_sampler_enabled: false,
1437 otlp_sampling_rate: 1.0,
1438 ..create_test_sampler()
1439 }
1440 }
1441
1442 #[test]
1445 fn ets_otlp_non_error_gets_presample_priority_and_dm() {
1446 let mut sampler = create_sampler_with_ets_legacy();
1447
1448 let span = create_otlp_test_span(1, 0); let mut trace = create_test_trace(vec![span]);
1450
1451 let (keep, priority, dm, _) = sampler.run_samplers(&mut trace);
1452 assert!(!keep, "ETS should drop non-error OTLP traces");
1453 assert_eq!(
1454 priority, PRIORITY_AUTO_KEEP,
1455 "OTLP pre-sampling sets priority=AutoKeep even for ETS-dropped traces"
1456 );
1457 assert_eq!(dm, DECISION_MAKER_PROBABILISTIC, "OTLP pre-sampling sets dm=-9");
1458 }
1459
1460 #[test]
1462 fn ets_otlp_error_gets_presample_priority_and_dm() {
1463 let mut sampler = create_sampler_with_ets_legacy();
1464
1465 let span = create_otlp_test_span(1, 1); let mut trace = create_test_trace(vec![span]);
1467
1468 let (keep, priority, dm, _) = sampler.run_samplers(&mut trace);
1469 assert!(keep, "ETS should keep error OTLP traces");
1470 assert_eq!(priority, PRIORITY_AUTO_KEEP, "OTLP pre-sampling sets priority=AutoKeep");
1471 assert_eq!(dm, DECISION_MAKER_PROBABILISTIC, "OTLP pre-sampling sets dm=-9");
1472 }
1473
1474 #[test]
1477 fn ets_otlp_probabilistic_path_skips_presample() {
1478 let mut sampler = create_sampler_with_ets_legacy();
1479 sampler.probabilistic_sampler_enabled = true; let span = create_otlp_test_span(1, 0); let mut trace = create_test_trace(vec![span]);
1483
1484 let (keep, priority, dm, _) = sampler.run_samplers(&mut trace);
1485 assert!(!keep, "ETS should drop non-error traces");
1486 assert_eq!(
1487 priority, PRIORITY_AUTO_DROP,
1488 "no pre-sampling when probabilistic path active"
1489 );
1490 assert_eq!(dm, "", "no dm when probabilistic path active");
1491 }
1492
1493 #[test]
1495 fn ets_non_otlp_unaffected_by_presample() {
1496 let mut sampler = create_sampler_with_ets_legacy();
1497
1498 let span = create_test_span(1, 0); let mut trace = create_test_trace(vec![span]);
1500
1501 let (keep, priority, dm, _) = sampler.run_samplers(&mut trace);
1502 assert!(!keep, "ETS should drop non-error non-OTLP traces");
1503 assert_eq!(priority, PRIORITY_AUTO_DROP, "non-OTLP traces use default ETS priority");
1504 assert_eq!(dm, "", "non-OTLP traces get no dm");
1505 }
1506
1507 #[test]
1509 fn ets_otlp_user_priority_gets_manual_dm() {
1510 let mut sampler = create_sampler_with_ets_legacy();
1511
1512 let mut span = create_otlp_test_span(1, 0);
1513 span.attributes.insert(
1514 MetaString::from(SAMPLING_PRIORITY_METRIC_KEY),
1515 AttributeValue::Float(2.0),
1516 ); let mut trace = create_test_trace(vec![span]);
1518
1519 let (keep, priority, dm, _) = sampler.run_samplers(&mut trace);
1520 assert!(!keep, "ETS drops non-error traces regardless of user priority");
1521 assert_eq!(priority, PRIORITY_USER_KEEP, "user priority is preserved");
1522 assert_eq!(dm, DECISION_MAKER_MANUAL, "user-set priority gets dm=-4");
1523 }
1524}