use super::{v04::Span, SpanText, TraceData};
use std::collections::HashMap;
const TOP_LEVEL_KEY: &str = "_top_level";
const TRACER_TOP_LEVEL_KEY: &str = "_dd.top_level";
const MEASURED_KEY: &str = "_dd.measured";
const PARTIAL_VERSION_KEY: &str = "_dd.partial_version";
fn set_top_level_span<T>(span: &mut Span<T>, is_top_level: bool)
where
T: TraceData,
{
if is_top_level {
span.metrics
.insert(T::Text::from_static_str(TOP_LEVEL_KEY), 1.0);
} else {
span.metrics.remove(TOP_LEVEL_KEY);
}
}
pub fn compute_top_level_span<T>(trace: &mut [Span<T>])
where
T: TraceData,
{
let mut span_id_idx: HashMap<u64, usize> = HashMap::new();
for (i, span) in trace.iter().enumerate() {
span_id_idx.insert(span.span_id, i);
}
for span_idx in 0..trace.len() {
let parent_id = trace[span_idx].parent_id;
if parent_id == 0 {
set_top_level_span(&mut trace[span_idx], true);
continue;
}
match span_id_idx.get(&parent_id).map(|i| &trace[*i].service) {
Some(parent_span_service) => {
if !(parent_span_service == &trace[span_idx].service) {
set_top_level_span(&mut trace[span_idx], true)
}
}
None => {
set_top_level_span(&mut trace[span_idx], true)
}
}
}
}
pub fn has_top_level<T: TraceData>(span: &Span<T>) -> bool {
span.metrics
.get(TRACER_TOP_LEVEL_KEY)
.is_some_and(|v| *v == 1.0)
|| span.metrics.get(TOP_LEVEL_KEY).is_some_and(|v| *v == 1.0)
}
pub fn is_measured<T: TraceData>(span: &Span<T>) -> bool {
span.metrics.get(MEASURED_KEY).is_some_and(|v| *v == 1.0)
}
pub fn is_partial_snapshot<T: TraceData>(span: &Span<T>) -> bool {
span.metrics
.get(PARTIAL_VERSION_KEY)
.is_some_and(|v| *v >= 0.0)
}
pub struct DroppedP0Stats {
pub dropped_p0_traces: usize,
pub dropped_p0_spans: usize,
}
const SAMPLING_PRIORITY_KEY: &str = "_sampling_priority_v1";
const SAMPLING_SINGLE_SPAN_MECHANISM: &str = "_dd.span_sampling.mechanism";
const SAMPLING_ANALYTICS_RATE_KEY: &str = "_dd1.sr.eausr";
pub fn drop_chunks<T>(traces: &mut Vec<Vec<Span<T>>>) -> DroppedP0Stats
where
T: TraceData,
{
let mut dropped_p0_traces = 0;
let mut dropped_p0_spans = 0;
traces.retain_mut(|chunk| {
if chunk.iter().any(|s| s.error == 1) {
return true;
}
let chunk_priority = chunk
.iter()
.find_map(|s| s.metrics.get(SAMPLING_PRIORITY_KEY));
if chunk_priority.is_none_or(|p| *p > 0.0) {
return true;
}
let mut sampled_indexes = Vec::new();
for (index, span) in chunk.iter().enumerate() {
if span
.metrics
.get(SAMPLING_SINGLE_SPAN_MECHANISM)
.is_some_and(|m| *m == 8.0)
|| span.metrics.contains_key(SAMPLING_ANALYTICS_RATE_KEY)
{
sampled_indexes.push(index);
}
}
dropped_p0_spans += chunk.len() - sampled_indexes.len();
if sampled_indexes.is_empty() {
dropped_p0_traces += 1;
return false;
}
let sampled_spans = sampled_indexes
.iter()
.map(|i| std::mem::take(&mut chunk[*i]))
.collect();
*chunk = sampled_spans;
true
});
DroppedP0Stats {
dropped_p0_traces,
dropped_p0_spans,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::span::v04::SpanBytes;
fn create_test_span(
trace_id: u64,
span_id: u64,
parent_id: u64,
start: i64,
is_top_level: bool,
) -> SpanBytes {
let mut span = SpanBytes {
trace_id: trace_id as u128,
span_id,
service: "test-service".into(),
name: "test_name".into(),
resource: "test-resource".into(),
parent_id,
start,
duration: 5,
error: 0,
meta: HashMap::from([
("service".into(), "test-service".into()),
("env".into(), "test-env".into()),
("runtime-id".into(), "test-runtime-id-value".into()),
]),
metrics: HashMap::new(),
r#type: "".into(),
meta_struct: HashMap::new(),
span_links: vec![],
span_events: vec![],
};
if is_top_level {
span.metrics.insert("_top_level".into(), 1.0);
span.meta
.insert("_dd.origin".into(), "cloudfunction".into());
span.meta.insert("origin".into(), "cloudfunction".into());
span.meta
.insert("functionname".into(), "dummy_function_name".into());
}
span
}
#[test]
fn test_has_top_level() {
let top_level_span = create_test_span(123, 1234, 12, 1, true);
let not_top_level_span = create_test_span(123, 1234, 12, 1, false);
assert!(has_top_level(&top_level_span));
assert!(!has_top_level(¬_top_level_span));
}
#[test]
fn test_is_measured() {
let mut measured_span = create_test_span(123, 1234, 12, 1, true);
measured_span.metrics.insert(MEASURED_KEY.into(), 1.0);
let not_measured_span = create_test_span(123, 1234, 12, 1, true);
assert!(is_measured(&measured_span));
assert!(!is_measured(¬_measured_span));
}
#[test]
fn test_compute_top_level() {
let mut span_with_different_service = create_test_span(123, 5, 2, 1, false);
span_with_different_service.service = "another_service".into();
let mut trace = vec![
create_test_span(123, 1, 0, 1, false),
create_test_span(123, 2, 1, 1, false),
create_test_span(123, 4, 3, 1, false),
span_with_different_service,
];
compute_top_level_span(trace.as_mut_slice());
let spans_marked_as_top_level: Vec<u64> = trace
.iter()
.filter_map(|span| {
if has_top_level(span) {
Some(span.span_id)
} else {
None
}
})
.collect();
assert_eq!(spans_marked_as_top_level, [1, 4, 5])
}
#[test]
fn test_drop_chunks() {
let chunk_with_priority = vec![
SpanBytes {
span_id: 1,
metrics: HashMap::from([
(SAMPLING_PRIORITY_KEY.into(), 1.0),
(TRACER_TOP_LEVEL_KEY.into(), 1.0),
]),
..Default::default()
},
SpanBytes {
span_id: 2,
parent_id: 1,
..Default::default()
},
];
let chunk_with_null_priority = vec![
SpanBytes {
span_id: 1,
metrics: HashMap::from([
(SAMPLING_PRIORITY_KEY.into(), 0.0),
(TRACER_TOP_LEVEL_KEY.into(), 1.0),
]),
..Default::default()
},
SpanBytes {
span_id: 2,
parent_id: 1,
..Default::default()
},
];
let chunk_without_priority = vec![
SpanBytes {
span_id: 1,
metrics: HashMap::from([(TRACER_TOP_LEVEL_KEY.into(), 1.0)]),
..Default::default()
},
SpanBytes {
span_id: 2,
parent_id: 1,
..Default::default()
},
];
let chunk_with_multiple_top_level = vec![
SpanBytes {
span_id: 1,
metrics: HashMap::from([
(SAMPLING_PRIORITY_KEY.into(), -1.0),
(TRACER_TOP_LEVEL_KEY.into(), 1.0),
]),
..Default::default()
},
SpanBytes {
span_id: 2,
parent_id: 1,
..Default::default()
},
SpanBytes {
span_id: 4,
parent_id: 3,
metrics: HashMap::from([(TRACER_TOP_LEVEL_KEY.into(), 1.0)]),
..Default::default()
},
];
let chunk_with_error = vec![
SpanBytes {
span_id: 1,
error: 1,
metrics: HashMap::from([
(SAMPLING_PRIORITY_KEY.into(), 0.0),
(TRACER_TOP_LEVEL_KEY.into(), 1.0),
]),
..Default::default()
},
SpanBytes {
span_id: 2,
parent_id: 1,
..Default::default()
},
];
let chunk_with_a_single_span = vec![
SpanBytes {
span_id: 1,
metrics: HashMap::from([
(SAMPLING_PRIORITY_KEY.into(), 0.0),
(TRACER_TOP_LEVEL_KEY.into(), 1.0),
]),
..Default::default()
},
SpanBytes {
span_id: 2,
parent_id: 1,
metrics: HashMap::from([(SAMPLING_SINGLE_SPAN_MECHANISM.into(), 8.0)]),
..Default::default()
},
];
let chunk_with_analyzed_span = vec![
SpanBytes {
span_id: 1,
metrics: HashMap::from([
(SAMPLING_PRIORITY_KEY.into(), 0.0),
(TRACER_TOP_LEVEL_KEY.into(), 1.0),
]),
..Default::default()
},
SpanBytes {
span_id: 2,
parent_id: 1,
metrics: HashMap::from([(SAMPLING_ANALYTICS_RATE_KEY.into(), 1.0)]),
..Default::default()
},
];
let chunks_and_expected_sampled_spans = vec![
(chunk_with_priority, 2),
(chunk_with_null_priority, 0),
(chunk_without_priority, 2),
(chunk_with_multiple_top_level, 0),
(chunk_with_error, 2),
(chunk_with_a_single_span, 1),
(chunk_with_analyzed_span, 1),
];
for (chunk, expected_count) in chunks_and_expected_sampled_spans.into_iter() {
let mut traces = vec![chunk];
drop_chunks(&mut traces);
if expected_count == 0 {
assert!(traces.is_empty());
} else {
assert_eq!(traces[0].len(), expected_count);
}
}
}
}