1use tracing::debug;
7
8use super::{v04::Span, SpanText, TraceData};
9use std::collections::{HashMap, HashSet};
10
11const TOP_LEVEL_KEY: &str = "_top_level";
13const TRACER_TOP_LEVEL_KEY: &str = "_dd.top_level";
15const MEASURED_KEY: &str = "_dd.measured";
16const PARTIAL_VERSION_KEY: &str = "_dd.partial_version";
17
18fn set_top_level_span<T>(span: &mut Span<T>)
19where
20 T: TraceData,
21{
22 span.metrics
23 .insert(T::Text::from_static_str(TOP_LEVEL_KEY), 1.0);
24}
25
26pub fn compute_top_level_span<T>(trace: &mut [Span<T>])
33where
34 T: TraceData,
35{
36 let mut span_id_idx: HashMap<u64, usize> = HashMap::new();
37 for (i, span) in trace.iter().enumerate() {
38 span_id_idx.insert(span.span_id, i);
39 }
40 for span_idx in 0..trace.len() {
41 let parent_id = trace[span_idx].parent_id;
42 if parent_id == 0 {
43 set_top_level_span(&mut trace[span_idx]);
44 continue;
45 }
46 match span_id_idx.get(&parent_id).map(|i| &trace[*i].service) {
47 Some(parent_span_service) => {
48 if !(parent_span_service == &trace[span_idx].service) {
49 set_top_level_span(&mut trace[span_idx])
51 }
52 }
53 None => {
54 set_top_level_span(&mut trace[span_idx])
56 }
57 }
58 }
59}
60
61pub fn get_root_span_index<T>(trace: &[Span<T>]) -> anyhow::Result<usize>
62where
63 T: TraceData,
64{
65 if trace.is_empty() {
66 anyhow::bail!("Cannot find root span index in an empty trace.");
67 }
68
69 for (i, span) in trace.iter().enumerate().rev() {
72 if span.parent_id == 0 {
73 return Ok(i);
74 }
75 }
76
77 let span_ids: HashSet<_> = trace.iter().map(|span| span.span_id).collect();
78
79 let mut root_span_id = None;
80 for (i, span) in trace.iter().enumerate() {
81 if !span_ids.contains(&span.parent_id) {
83 if root_span_id.is_some() {
84 debug!(
85 trace_id = &trace[0].trace_id,
86 "trace has multiple root spans"
87 );
88 }
89 root_span_id = Some(i);
90 }
91 }
92 Ok(match root_span_id {
93 Some(i) => i,
94 None => {
95 debug!(
96 trace_id = &trace[0].trace_id,
97 "Could not find the root span for trace"
98 );
99 trace.len() - 1
100 }
101 })
102}
103
104pub fn has_top_level<T: TraceData>(span: &Span<T>) -> bool {
106 span.metrics
107 .get(TRACER_TOP_LEVEL_KEY)
108 .is_some_and(|v| *v == 1.0)
109 || span.metrics.get(TOP_LEVEL_KEY).is_some_and(|v| *v == 1.0)
110}
111
112pub fn is_measured<T: TraceData>(span: &Span<T>) -> bool {
114 span.metrics.get(MEASURED_KEY).is_some_and(|v| *v == 1.0)
115}
116
117pub fn is_partial_snapshot<T: TraceData>(span: &Span<T>) -> bool {
123 span.metrics
124 .get(PARTIAL_VERSION_KEY)
125 .is_some_and(|v| *v >= 0.0)
126}
127
128pub struct DroppedP0Stats {
129 pub dropped_p0_traces: usize,
130 pub dropped_p0_spans: usize,
131}
132
133const SAMPLING_PRIORITY_KEY: &str = "_sampling_priority_v1";
135const SAMPLING_SINGLE_SPAN_MECHANISM: &str = "_dd.span_sampling.mechanism";
136const SAMPLING_ANALYTICS_RATE_KEY: &str = "_dd1.sr.eausr";
137
138pub fn drop_chunks<T>(traces: &mut Vec<Vec<Span<T>>>) -> DroppedP0Stats
149where
150 T: TraceData,
151{
152 let mut dropped_p0_traces = 0;
153 let mut dropped_p0_spans = 0;
154
155 traces.retain_mut(|chunk| {
156 if chunk.iter().any(|s| s.error == 1) {
158 return true;
160 }
161
162 let chunk_priority = chunk
164 .iter()
165 .find_map(|s| s.metrics.get(SAMPLING_PRIORITY_KEY));
166 if chunk_priority.is_none_or(|p| *p > 0.0) {
167 return true;
169 }
170
171 let mut sampled_indexes = Vec::new();
174 for (index, span) in chunk.iter().enumerate() {
175 if span
176 .metrics
177 .get(SAMPLING_SINGLE_SPAN_MECHANISM)
178 .is_some_and(|m| *m == 8.0)
179 || span.metrics.contains_key(SAMPLING_ANALYTICS_RATE_KEY)
180 {
181 sampled_indexes.push(index);
183 }
184 }
185 dropped_p0_spans += chunk.len() - sampled_indexes.len();
186 if sampled_indexes.is_empty() {
187 dropped_p0_traces += 1;
189 return false;
190 }
191 let sampled_spans = sampled_indexes
192 .iter()
193 .map(|i| std::mem::take(&mut chunk[*i]))
194 .collect();
195 *chunk = sampled_spans;
196 true
197 });
198
199 DroppedP0Stats {
200 dropped_p0_traces,
201 dropped_p0_spans,
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use crate::span::v04::{SpanBytes, VecMap};
209
210 fn create_test_span(
211 trace_id: u64,
212 span_id: u64,
213 parent_id: u64,
214 start: i64,
215 is_top_level: bool,
216 ) -> SpanBytes {
217 let mut span = SpanBytes {
218 trace_id: trace_id as u128,
219 span_id,
220 service: "test-service".into(),
221 name: "test_name".into(),
222 resource: "test-resource".into(),
223 parent_id,
224 start,
225 duration: 5,
226 error: 0,
227 meta: vec![
228 ("service".into(), "test-service".into()),
229 ("env".into(), "test-env".into()),
230 ("runtime-id".into(), "test-runtime-id-value".into()),
231 ]
232 .into(),
233 metrics: VecMap::new(),
234 r#type: "".into(),
235 meta_struct: VecMap::new(),
236 span_links: vec![],
237 span_events: vec![],
238 };
239 if is_top_level {
240 span.metrics.insert("_top_level".into(), 1.0);
241 span.meta
242 .insert("_dd.origin".into(), "cloudfunction".into());
243 span.meta.insert("origin".into(), "cloudfunction".into());
244 span.meta
245 .insert("functionname".into(), "dummy_function_name".into());
246 }
247 span
248 }
249
250 #[test]
251 fn test_has_top_level() {
252 let top_level_span = create_test_span(123, 1234, 12, 1, true);
253 let not_top_level_span = create_test_span(123, 1234, 12, 1, false);
254 assert!(has_top_level(&top_level_span));
255 assert!(!has_top_level(¬_top_level_span));
256 }
257
258 #[test]
259 fn test_is_measured() {
260 let mut measured_span = create_test_span(123, 1234, 12, 1, true);
261 measured_span.metrics.insert(MEASURED_KEY.into(), 1.0);
262 let not_measured_span = create_test_span(123, 1234, 12, 1, true);
263 assert!(is_measured(&measured_span));
264 assert!(!is_measured(¬_measured_span));
265 }
266
267 #[test]
268 fn test_compute_top_level() {
269 let mut span_with_different_service = create_test_span(123, 5, 2, 1, false);
270 span_with_different_service.service = "another_service".into();
271 let mut trace = vec![
272 create_test_span(123, 1, 0, 1, false),
274 create_test_span(123, 2, 1, 1, false),
276 create_test_span(123, 4, 3, 1, false),
279 span_with_different_service,
282 ];
283
284 compute_top_level_span(trace.as_mut_slice());
285
286 let spans_marked_as_top_level: Vec<u64> = trace
287 .iter()
288 .filter_map(|span| {
289 if has_top_level(span) {
290 Some(span.span_id)
291 } else {
292 None
293 }
294 })
295 .collect();
296 assert_eq!(spans_marked_as_top_level, [1, 4, 5])
297 }
298
299 #[test]
300 fn test_drop_chunks() {
301 let chunk_with_priority = vec![
302 SpanBytes {
303 span_id: 1,
304 metrics: vec![
305 (SAMPLING_PRIORITY_KEY.into(), 1.0),
306 (TRACER_TOP_LEVEL_KEY.into(), 1.0),
307 ]
308 .into(),
309 ..Default::default()
310 },
311 SpanBytes {
312 span_id: 2,
313 parent_id: 1,
314 ..Default::default()
315 },
316 ];
317 let chunk_with_null_priority = vec![
318 SpanBytes {
319 span_id: 1,
320 metrics: vec![
321 (SAMPLING_PRIORITY_KEY.into(), 0.0),
322 (TRACER_TOP_LEVEL_KEY.into(), 1.0),
323 ]
324 .into(),
325 ..Default::default()
326 },
327 SpanBytes {
328 span_id: 2,
329 parent_id: 1,
330 ..Default::default()
331 },
332 ];
333 let chunk_without_priority = vec![
334 SpanBytes {
335 span_id: 1,
336 metrics: vec![(TRACER_TOP_LEVEL_KEY.into(), 1.0)].into(),
337 ..Default::default()
338 },
339 SpanBytes {
340 span_id: 2,
341 parent_id: 1,
342 ..Default::default()
343 },
344 ];
345 let chunk_with_multiple_top_level = vec![
346 SpanBytes {
347 span_id: 1,
348 metrics: vec![
349 (SAMPLING_PRIORITY_KEY.into(), -1.0),
350 (TRACER_TOP_LEVEL_KEY.into(), 1.0),
351 ]
352 .into(),
353 ..Default::default()
354 },
355 SpanBytes {
356 span_id: 2,
357 parent_id: 1,
358 ..Default::default()
359 },
360 SpanBytes {
361 span_id: 4,
362 parent_id: 3,
363 metrics: vec![(TRACER_TOP_LEVEL_KEY.into(), 1.0)].into(),
364 ..Default::default()
365 },
366 ];
367 let chunk_with_error = vec![
368 SpanBytes {
369 span_id: 1,
370 error: 1,
371 metrics: vec![
372 (SAMPLING_PRIORITY_KEY.into(), 0.0),
373 (TRACER_TOP_LEVEL_KEY.into(), 1.0),
374 ]
375 .into(),
376 ..Default::default()
377 },
378 SpanBytes {
379 span_id: 2,
380 parent_id: 1,
381 ..Default::default()
382 },
383 ];
384 let chunk_with_a_single_span = vec![
385 SpanBytes {
386 span_id: 1,
387 metrics: vec![
388 (SAMPLING_PRIORITY_KEY.into(), 0.0),
389 (TRACER_TOP_LEVEL_KEY.into(), 1.0),
390 ]
391 .into(),
392 ..Default::default()
393 },
394 SpanBytes {
395 span_id: 2,
396 parent_id: 1,
397 metrics: vec![(SAMPLING_SINGLE_SPAN_MECHANISM.into(), 8.0)].into(),
398 ..Default::default()
399 },
400 ];
401 let chunk_with_analyzed_span = vec![
402 SpanBytes {
403 span_id: 1,
404 metrics: vec![
405 (SAMPLING_PRIORITY_KEY.into(), 0.0),
406 (TRACER_TOP_LEVEL_KEY.into(), 1.0),
407 ]
408 .into(),
409 ..Default::default()
410 },
411 SpanBytes {
412 span_id: 2,
413 parent_id: 1,
414 metrics: vec![(SAMPLING_ANALYTICS_RATE_KEY.into(), 1.0)].into(),
415 ..Default::default()
416 },
417 ];
418
419 let chunks_and_expected_sampled_spans = vec![
420 (chunk_with_priority, 2),
421 (chunk_with_null_priority, 0),
422 (chunk_without_priority, 2),
423 (chunk_with_multiple_top_level, 0),
424 (chunk_with_error, 2),
425 (chunk_with_a_single_span, 1),
426 (chunk_with_analyzed_span, 1),
427 ];
428
429 for (chunk, expected_count) in chunks_and_expected_sampled_spans.into_iter() {
430 let mut traces = vec![chunk];
431 drop_chunks(&mut traces);
432
433 if expected_count == 0 {
434 assert!(traces.is_empty());
435 } else {
436 assert_eq!(traces[0].len(), expected_count);
437 }
438 }
439 }
440}