1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
use std::collections::BTreeMap;

use crate::event::{ObjectMap, Value};

const SAMPLING_RATE_KEY: &str = "_sample_rate";

/// This extracts the relative weights from the top level span (i.e. the span that does not have a parent).
pub(crate) fn extract_weight_from_root_span(spans: &[&ObjectMap]) -> f64 {
    // Based on https://github.com/DataDog/datadog-agent/blob/cfa750c7412faa98e87a015f8ee670e5828bbe7f/pkg/trace/stats/weight.go#L17-L26.

    // TODO this logic likely has a bug(s) that need to be root caused. The root span is not reliably found and defaults to "1.0"
    // regularly for users even when sampling is disabled in the Agent.
    // GH issue to track that: https://github.com/vectordotdev/vector/issues/14859

    if spans.is_empty() {
        return 1.0;
    }

    let mut trace_id: Option<usize> = None;

    let mut parent_id_to_child_weight = BTreeMap::<i64, f64>::new();
    let mut span_ids = Vec::<i64>::new();
    for s in spans.iter() {
        // TODO these need to change to u64 when the following issue is fixed:
        // https://github.com/vectordotdev/vector/issues/14687
        let parent_id = match s.get("parent_id") {
            Some(Value::Integer(val)) => *val,
            None => 0,
            _ => panic!("`parent_id` should be an i64"),
        };
        let span_id = match s.get("span_id") {
            Some(Value::Integer(val)) => *val,
            None => 0,
            _ => panic!("`span_id` should be an i64"),
        };
        if trace_id.is_none() {
            trace_id = match s.get("trace_id") {
                Some(Value::Integer(v)) => Some(*v as usize),
                _ => panic!("`trace_id` should be an i64"),
            }
        }
        let weight = s
            .get("metrics")
            .and_then(|m| m.as_object())
            .map(|m| match m.get(SAMPLING_RATE_KEY) {
                Some(Value::Float(v)) => {
                    let sample_rate = v.into_inner();
                    if sample_rate <= 0.0 || sample_rate > 1.0 {
                        1.0
                    } else {
                        1.0 / sample_rate
                    }
                }
                _ => 1.0,
            })
            .unwrap_or(1.0);

        // found root
        if parent_id == 0 {
            return weight;
        }

        span_ids.push(span_id);

        parent_id_to_child_weight.insert(parent_id, weight);
    }

    // Remove all spans that have a parent
    span_ids.iter().for_each(|id| {
        parent_id_to_child_weight.remove(id);
    });

    // There should be only one value remaining, the weight from the root span
    if parent_id_to_child_weight.len() != 1 {
        // TODO remove the debug print and emit the Error event as outlined in
        // https://github.com/vectordotdev/vector/issues/14859
        debug!(
            "Didn't reliably find the root span for weight calculation of trace_id {:?}.",
            trace_id
        );
    }

    *parent_id_to_child_weight
        .values()
        .next()
        .unwrap_or_else(|| {
            // TODO remove the debug print and emit the Error event as outlined in
            // https://github.com/vectordotdev/vector/issues/14859
            debug!(
                "Root span was not found. Defaulting to weight of 1.0 for trace_id {:?}.",
                trace_id
            );
            &1.0
        })
}