1use crate::span::trace_utils::DroppedP0Stats;
7use crate::span::v1::{AttributeValue, Span, TraceChunk};
8use crate::span::{SpanText, TraceData};
9use std::collections::{HashMap, HashSet};
10use tracing::debug;
11
12const TOP_LEVEL_KEY: &str = "_top_level";
14const TRACER_TOP_LEVEL_KEY: &str = "_dd.top_level";
16const MEASURED_KEY: &str = "_dd.measured";
17const PARTIAL_VERSION_KEY: &str = "_dd.partial_version";
18const SAMPLING_SINGLE_SPAN_MECHANISM: &str = "_dd.span_sampling.mechanism";
19const SAMPLING_ANALYTICS_RATE_KEY: &str = "_dd1.sr.eausr";
20
21fn attribute_as_f64<T: TraceData>(value: &AttributeValue<T>) -> Option<f64> {
23 match value {
24 AttributeValue::Float(v) => Some(*v),
25 AttributeValue::Int(v) => Some(*v as f64),
26 _ => None,
27 }
28}
29
30fn set_top_level_span<T: TraceData>(span: &mut Span<T>) {
31 span.attributes.insert(
32 T::Text::from_static_str(TOP_LEVEL_KEY),
33 AttributeValue::Float(1.0),
34 );
35}
36
37pub fn compute_top_level_span<T: TraceData>(trace: &mut [Span<T>]) {
44 let span_id_idx: HashMap<u64, usize> = trace
45 .iter()
46 .enumerate()
47 .map(|(i, span)| (span.span_id, i))
48 .collect();
49 let top_level: Vec<usize> = trace
50 .iter()
51 .enumerate()
52 .filter(|(_, span)| {
53 span.parent_id == 0
54 || span_id_idx
55 .get(&span.parent_id)
56 .is_none_or(|&parent_idx| trace[parent_idx].service != span.service)
57 })
58 .map(|(i, _)| i)
59 .collect();
60 for i in top_level {
61 set_top_level_span(&mut trace[i]);
62 }
63}
64
65pub fn get_root_span_index<T: TraceData>(trace: &[Span<T>]) -> anyhow::Result<usize> {
67 if trace.is_empty() {
68 anyhow::bail!("Cannot find root span index in an empty trace.");
69 }
70
71 for (i, span) in trace.iter().enumerate().rev() {
74 if span.parent_id == 0 {
75 return Ok(i);
76 }
77 }
78
79 let span_ids: HashSet<_> = trace.iter().map(|span| span.span_id).collect();
80
81 let mut root_span_id = None;
82 for (i, span) in trace.iter().enumerate() {
83 if !span_ids.contains(&span.parent_id) {
85 if root_span_id.is_some() {
86 debug!("trace has multiple root spans");
87 }
88 root_span_id = Some(i);
89 }
90 }
91 Ok(match root_span_id {
92 Some(i) => i,
93 None => {
94 debug!("Could not find the root span for trace");
95 trace.len() - 1
96 }
97 })
98}
99
100pub fn has_top_level<T: TraceData>(span: &Span<T>) -> bool {
102 span.attributes
103 .get(TRACER_TOP_LEVEL_KEY)
104 .and_then(attribute_as_f64)
105 .is_some_and(|v| v == 1.0)
106 || span
107 .attributes
108 .get(TOP_LEVEL_KEY)
109 .and_then(attribute_as_f64)
110 .is_some_and(|v| v == 1.0)
111}
112
113pub fn is_measured<T: TraceData>(span: &Span<T>) -> bool {
115 span.attributes
116 .get(MEASURED_KEY)
117 .and_then(attribute_as_f64)
118 .is_some_and(|v| v == 1.0)
119}
120
121pub fn is_partial_snapshot<T: TraceData>(span: &Span<T>) -> bool {
127 span.attributes
128 .get(PARTIAL_VERSION_KEY)
129 .and_then(attribute_as_f64)
130 .is_some_and(|v| v >= 0.0)
131}
132
133pub fn drop_chunks<T: TraceData>(traces: &mut Vec<TraceChunk<T>>) -> DroppedP0Stats {
143 let mut dropped_p0_traces = 0;
144 let mut dropped_p0_spans = 0;
145
146 traces.retain_mut(|chunk| {
147 if chunk.spans.iter().any(|s| s.error) {
149 return true;
151 }
152
153 let effective_priority = if chunk.dropped_trace {
160 chunk.priority.filter(|&p| p < 0).or(Some(-1))
161 } else {
162 chunk.priority
163 };
164 if effective_priority.is_none_or(|p| p > 0) {
165 return true;
167 }
168
169 let spans_before = chunk.spans.len();
173 chunk.spans.retain(|span| {
174 span.attributes
175 .get(SAMPLING_SINGLE_SPAN_MECHANISM)
176 .and_then(attribute_as_f64)
177 .is_some_and(|m| m == 8.0)
178 || span.attributes.contains_key(SAMPLING_ANALYTICS_RATE_KEY)
179 });
180 dropped_p0_spans += spans_before - chunk.spans.len();
181 if chunk.spans.is_empty() {
182 dropped_p0_traces += 1;
184 return false;
185 }
186 true
187 });
188
189 DroppedP0Stats {
190 dropped_p0_traces,
191 dropped_p0_spans,
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198 use crate::span::v1::{SpanBytes, TraceChunkBytes};
199
200 fn create_test_span(is_top_level: bool) -> SpanBytes {
201 let mut span = SpanBytes {
202 service: "test-service".into(),
203 name: "test_name".into(),
204 resource: "test-resource".into(),
205 ..Default::default()
206 };
207 if is_top_level {
208 span.attributes
209 .insert("_top_level".into(), AttributeValue::Float(1.0));
210 }
211 span
212 }
213
214 fn create_test_span_with_ids(span_id: u64, parent_id: u64) -> SpanBytes {
215 SpanBytes {
216 service: "test-service".into(),
217 name: "test_name".into(),
218 resource: "test-resource".into(),
219 span_id,
220 parent_id,
221 ..Default::default()
222 }
223 }
224
225 #[test]
226 fn test_has_top_level() {
227 let top_level_span = create_test_span(true);
228 let not_top_level_span = create_test_span(false);
229 assert!(has_top_level(&top_level_span));
230 assert!(!has_top_level(¬_top_level_span));
231 }
232
233 #[test]
234 fn test_is_measured() {
235 let mut measured_span = create_test_span(true);
236 measured_span
237 .attributes
238 .insert(MEASURED_KEY.into(), AttributeValue::Float(1.0));
239 let not_measured_span = create_test_span(true);
240 assert!(is_measured(&measured_span));
241 assert!(!is_measured(¬_measured_span));
242 }
243
244 #[test]
245 fn test_is_partial_snapshot() {
246 let mut partial_span = create_test_span(false);
247 partial_span
248 .attributes
249 .insert(PARTIAL_VERSION_KEY.into(), AttributeValue::Int(2));
250 let not_partial_span = create_test_span(false);
251 assert!(is_partial_snapshot(&partial_span));
252 assert!(!is_partial_snapshot(¬_partial_span));
253 }
254
255 #[test]
256 fn test_compute_top_level() {
257 let mut span_with_different_service = create_test_span_with_ids(5, 2);
258 span_with_different_service.service = "another_service".into();
259 let mut trace = vec![
260 create_test_span_with_ids(1, 0),
262 create_test_span_with_ids(2, 1),
264 create_test_span_with_ids(4, 3),
266 span_with_different_service,
268 ];
269
270 compute_top_level_span(trace.as_mut_slice());
271
272 let spans_marked_as_top_level: Vec<u64> = trace
273 .iter()
274 .filter_map(|span| has_top_level(span).then_some(span.span_id))
275 .collect();
276 assert_eq!(spans_marked_as_top_level, [1, 4, 5]);
277 }
278
279 #[test]
280 fn test_get_root_span_index_from_complete_trace() {
281 let trace = vec![
282 create_test_span_with_ids(1, 0),
283 create_test_span_with_ids(2, 1),
284 create_test_span_with_ids(3, 1),
285 ];
286 assert_eq!(get_root_span_index(&trace).unwrap(), 0);
287 }
288
289 #[test]
290 fn test_get_root_span_index_root_last() {
291 let trace = vec![
292 create_test_span_with_ids(2, 1),
293 create_test_span_with_ids(3, 1),
294 create_test_span_with_ids(1, 0),
295 ];
296 assert_eq!(get_root_span_index(&trace).unwrap(), 2);
297 }
298
299 #[test]
300 fn test_get_root_span_index_from_partial_trace() {
301 let trace = vec![
303 create_test_span_with_ids(1, 99),
304 create_test_span_with_ids(2, 1),
305 ];
306 assert_eq!(get_root_span_index(&trace).unwrap(), 0);
307 }
308
309 #[test]
310 fn test_get_root_span_index_empty_trace_errors() {
311 let trace: Vec<SpanBytes> = vec![];
312 assert!(get_root_span_index(&trace).is_err());
313 }
314
315 fn chunk_with_spans(priority: Option<i32>, spans: Vec<SpanBytes>) -> TraceChunkBytes {
316 TraceChunkBytes {
317 priority,
318 spans,
319 ..Default::default()
320 }
321 }
322
323 #[test]
324 fn test_drop_chunks() {
325 let chunk_with_priority = chunk_with_spans(
326 Some(1),
327 vec![
328 SpanBytes {
329 span_id: 1,
330 ..Default::default()
331 },
332 SpanBytes {
333 span_id: 2,
334 parent_id: 1,
335 ..Default::default()
336 },
337 ],
338 );
339 let chunk_with_null_priority = chunk_with_spans(
340 Some(0),
341 vec![
342 SpanBytes {
343 span_id: 1,
344 ..Default::default()
345 },
346 SpanBytes {
347 span_id: 2,
348 parent_id: 1,
349 ..Default::default()
350 },
351 ],
352 );
353 let chunk_without_priority = chunk_with_spans(
354 None,
355 vec![
356 SpanBytes {
357 span_id: 1,
358 ..Default::default()
359 },
360 SpanBytes {
361 span_id: 2,
362 parent_id: 1,
363 ..Default::default()
364 },
365 ],
366 );
367 let chunk_with_negative_priority = chunk_with_spans(
368 Some(-1),
369 vec![
370 SpanBytes {
371 span_id: 1,
372 ..Default::default()
373 },
374 SpanBytes {
375 span_id: 2,
376 parent_id: 1,
377 ..Default::default()
378 },
379 ],
380 );
381 let chunk_with_error = chunk_with_spans(
382 Some(0),
383 vec![
384 SpanBytes {
385 span_id: 1,
386 error: true,
387 ..Default::default()
388 },
389 SpanBytes {
390 span_id: 2,
391 parent_id: 1,
392 ..Default::default()
393 },
394 ],
395 );
396 let chunk_with_a_single_span = chunk_with_spans(
397 Some(0),
398 vec![
399 SpanBytes {
400 span_id: 1,
401 ..Default::default()
402 },
403 SpanBytes {
404 span_id: 2,
405 parent_id: 1,
406 attributes: vec![(
407 SAMPLING_SINGLE_SPAN_MECHANISM.into(),
408 AttributeValue::Float(8.0),
409 )]
410 .into(),
411 ..Default::default()
412 },
413 ],
414 );
415 let chunk_with_analyzed_span = chunk_with_spans(
416 Some(0),
417 vec![
418 SpanBytes {
419 span_id: 1,
420 ..Default::default()
421 },
422 SpanBytes {
423 span_id: 2,
424 parent_id: 1,
425 attributes: vec![(
426 SAMPLING_ANALYTICS_RATE_KEY.into(),
427 AttributeValue::Float(1.0),
428 )]
429 .into(),
430 ..Default::default()
431 },
432 ],
433 );
434
435 let chunks_and_expected_sampled_spans = vec![
436 (chunk_with_priority, 2),
437 (chunk_with_null_priority, 0),
438 (chunk_without_priority, 2),
439 (chunk_with_negative_priority, 0),
440 (chunk_with_error, 2),
441 (chunk_with_a_single_span, 1),
442 (chunk_with_analyzed_span, 1),
443 ];
444
445 for (chunk, expected_count) in chunks_and_expected_sampled_spans.into_iter() {
446 let mut traces = vec![chunk];
447 drop_chunks(&mut traces);
448
449 if expected_count == 0 {
450 assert!(traces.is_empty());
451 } else {
452 assert_eq!(traces[0].spans.len(), expected_count);
453 }
454 }
455 }
456
457 #[test]
458 fn test_drop_chunks_dropped_trace_overrides_missing_or_positive_priority() {
459 let mut dropped_without_priority = chunk_with_spans(
462 None,
463 vec![SpanBytes {
464 span_id: 1,
465 ..Default::default()
466 }],
467 );
468 dropped_without_priority.dropped_trace = true;
469
470 let mut dropped_with_positive_priority = chunk_with_spans(
471 Some(1),
472 vec![SpanBytes {
473 span_id: 1,
474 ..Default::default()
475 }],
476 );
477 dropped_with_positive_priority.dropped_trace = true;
478
479 for chunk in [dropped_without_priority, dropped_with_positive_priority] {
480 let mut traces = vec![chunk];
481 drop_chunks(&mut traces);
482 assert!(
483 traces.is_empty(),
484 "dropped_trace should reject the chunk regardless of priority"
485 );
486 }
487 }
488
489 #[test]
490 fn test_drop_chunks_dropped_trace_keeps_existing_negative_priority() {
491 let mut chunk = chunk_with_spans(
494 Some(-5),
495 vec![SpanBytes {
496 span_id: 1,
497 ..Default::default()
498 }],
499 );
500 chunk.dropped_trace = true;
501
502 let mut traces = vec![chunk];
503 drop_chunks(&mut traces);
504 assert!(traces.is_empty());
505 }
506}