Skip to main content

libdd_trace_stats/span_concentrator/
stat_span.rs

1// Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/
2// SPDX-License-Identifier: Apache-2.0
3
4//! This module implements a common interface for spans used to compute stats. It is used to
5//! support both trace-utils' Span and pb::Span.
6
7use libdd_trace_protobuf::pb;
8use libdd_trace_utils::span::v1::{AttributeValue, Span as SpanV1, SpanKind, TraceChunk};
9// trace_utils (v04), trace_utils_v1, and trace_utils_pb (aliased below) all expose functions
10// with the same names (is_measured, is_partial_snapshot, has_top_level), one per span
11// representation.
12use libdd_trace_utils::span::{trace_utils, trace_utils_v1, v04::Span, TraceData};
13use libdd_trace_utils::trace_utils as trace_utils_pb;
14use std::borrow::Borrow;
15
16/// Common interface for spans used in stats computation
17pub trait StatSpan<'a> {
18    /// Returns the service name
19    fn service(&'a self) -> &'a str;
20    /// Returns the resource name
21    fn resource(&'a self) -> &'a str;
22    /// Returns the operation name
23    fn name(&'a self) -> &'a str;
24    /// Returns the span type
25    fn r#type(&'a self) -> &'a str;
26    /// Returns the start timestamp
27    fn start(&'a self) -> i64;
28    /// Returns the duration
29    fn duration(&'a self) -> i64;
30    /// Returns true if the span is an error
31    fn is_error(&'a self) -> bool;
32    /// Returns true if the span is a trace root
33    fn is_trace_root(&'a self) -> bool;
34    /// Returns true if the span is measured
35    fn is_measured(&'a self) -> bool;
36    /// Returns true if the span is a partial snapshot
37    fn is_partial_snapshot(&'a self) -> bool;
38    /// Returns true if the span has a top level key set
39    fn has_top_level(&'a self) -> bool;
40    /// Returns the value of a meta field
41    fn get_meta(&'a self, key: &str) -> Option<&'a str>;
42    /// Returns the value of a metrics field
43    fn get_metrics(&'a self, key: &str) -> Option<f64>;
44}
45
46impl<'a, T: TraceData> StatSpan<'a> for Span<T> {
47    fn service(&'a self) -> &'a str {
48        self.service.borrow()
49    }
50
51    fn resource(&'a self) -> &'a str {
52        self.resource.borrow()
53    }
54
55    fn name(&'a self) -> &'a str {
56        self.name.borrow()
57    }
58
59    fn r#type(&'a self) -> &'a str {
60        self.r#type.borrow()
61    }
62
63    fn start(&'a self) -> i64 {
64        self.start
65    }
66
67    fn duration(&'a self) -> i64 {
68        self.duration
69    }
70
71    fn is_error(&'a self) -> bool {
72        self.error != 0
73    }
74
75    fn is_trace_root(&'a self) -> bool {
76        self.parent_id == 0
77    }
78
79    fn is_measured(&'a self) -> bool {
80        trace_utils::is_measured(self)
81    }
82
83    fn is_partial_snapshot(&'a self) -> bool {
84        trace_utils::is_partial_snapshot(self)
85    }
86
87    fn has_top_level(&'a self) -> bool {
88        trace_utils::has_top_level(self)
89    }
90
91    fn get_meta(&'a self, key: &str) -> Option<&'a str> {
92        self.meta.get(key).map(|v| v.borrow())
93    }
94
95    fn get_metrics(&'a self, key: &str) -> Option<f64> {
96        self.metrics.get(key).copied()
97    }
98}
99
100impl<'a, T: TraceData> StatSpan<'a> for SpanV1<T> {
101    fn service(&'a self) -> &'a str {
102        self.service.borrow()
103    }
104
105    fn resource(&'a self) -> &'a str {
106        self.resource.borrow()
107    }
108
109    fn name(&'a self) -> &'a str {
110        self.name.borrow()
111    }
112
113    fn r#type(&'a self) -> &'a str {
114        self.r#type.borrow()
115    }
116
117    fn start(&'a self) -> i64 {
118        self.start
119    }
120
121    fn duration(&'a self) -> i64 {
122        self.duration
123    }
124
125    fn is_error(&'a self) -> bool {
126        self.error
127    }
128
129    fn is_trace_root(&'a self) -> bool {
130        self.parent_id == 0
131    }
132
133    fn is_measured(&'a self) -> bool {
134        trace_utils_v1::is_measured(self)
135    }
136
137    fn is_partial_snapshot(&'a self) -> bool {
138        trace_utils_v1::is_partial_snapshot(self)
139    }
140
141    fn has_top_level(&'a self) -> bool {
142        trace_utils_v1::has_top_level(self)
143    }
144
145    fn get_meta(&'a self, key: &str) -> Option<&'a str> {
146        match self.attributes.get(key) {
147            Some(AttributeValue::String(s)) => Some(s.borrow()),
148            // `span_kind` is a dedicated field rather than an attribute; expose it under the same
149            // "span.kind" key that v0.4 spans (and is_span_eligible) look for. `Internal` is the
150            // wire-level default and indistinguishable from "unset", so it's treated as no value
151            // here, leaving room for a chunk-level fallback (see `ChunkSpanView`).
152            _ if key == "span.kind" && self.span_kind != SpanKind::Internal => {
153                Some(self.span_kind.as_meta_str())
154            }
155            _ => None,
156        }
157    }
158
159    fn get_metrics(&'a self, key: &str) -> Option<f64> {
160        match self.attributes.get(key) {
161            Some(AttributeValue::Float(v)) => Some(*v),
162            Some(AttributeValue::Int(v)) => Some(*v as f64),
163            _ => None,
164        }
165    }
166}
167
168/// Wraps a V1 span together with its enclosing chunk.
169///
170/// `TraceChunk.attributes` holds values common to every span in the chunk (e.g. peer tags), and
171/// `TraceChunk.origin` holds the `_dd.origin` value for the whole chunk. `get_meta`/`get_metrics`
172/// fall back to these chunk-level values whenever the span doesn't have its own value for a
173/// given key.
174pub struct ChunkSpanView<'a, T: TraceData> {
175    pub span: &'a SpanV1<T>,
176    pub chunk: &'a TraceChunk<T>,
177}
178
179impl<'a, T: TraceData> StatSpan<'a> for ChunkSpanView<'a, T> {
180    fn service(&'a self) -> &'a str {
181        self.span.service()
182    }
183
184    fn resource(&'a self) -> &'a str {
185        self.span.resource()
186    }
187
188    fn name(&'a self) -> &'a str {
189        self.span.name()
190    }
191
192    fn r#type(&'a self) -> &'a str {
193        self.span.r#type()
194    }
195
196    fn start(&'a self) -> i64 {
197        self.span.start()
198    }
199
200    fn duration(&'a self) -> i64 {
201        self.span.duration()
202    }
203
204    fn is_error(&'a self) -> bool {
205        self.span.is_error()
206    }
207
208    fn is_trace_root(&'a self) -> bool {
209        self.span.is_trace_root()
210    }
211
212    fn is_measured(&'a self) -> bool {
213        self.span.is_measured()
214    }
215
216    fn is_partial_snapshot(&'a self) -> bool {
217        self.span.is_partial_snapshot()
218    }
219
220    fn has_top_level(&'a self) -> bool {
221        self.span.has_top_level()
222    }
223
224    fn get_meta(&'a self, key: &str) -> Option<&'a str> {
225        self.span.get_meta(key).or_else(|| {
226            // `origin` is a dedicated chunk field rather than a chunk attribute; expose it
227            // under the same key aggregation.rs's TAG_ORIGIN looks for.
228            if key == "_dd.origin" {
229                let origin = self.chunk.origin.borrow();
230                if !origin.is_empty() {
231                    return Some(origin);
232                }
233            }
234            match self.chunk.attributes.get(key) {
235                Some(AttributeValue::String(s)) => Some(s.borrow()),
236                _ => None,
237            }
238        })
239    }
240
241    fn get_metrics(&'a self, key: &str) -> Option<f64> {
242        self.span
243            .get_metrics(key)
244            .or_else(|| match self.chunk.attributes.get(key) {
245                Some(AttributeValue::Float(v)) => Some(*v),
246                Some(AttributeValue::Int(v)) => Some(*v as f64),
247                _ => None,
248            })
249    }
250}
251
252impl<'a> StatSpan<'a> for pb::Span {
253    fn service(&'a self) -> &'a str {
254        self.service.as_str()
255    }
256
257    fn resource(&'a self) -> &'a str {
258        self.resource.as_str()
259    }
260
261    fn name(&'a self) -> &'a str {
262        self.name.as_str()
263    }
264
265    fn r#type(&'a self) -> &'a str {
266        self.r#type.as_str()
267    }
268
269    fn start(&'a self) -> i64 {
270        self.start
271    }
272
273    fn duration(&'a self) -> i64 {
274        self.duration
275    }
276
277    fn is_error(&'a self) -> bool {
278        self.error != 0
279    }
280
281    fn is_trace_root(&'a self) -> bool {
282        self.parent_id == 0
283    }
284
285    fn is_measured(&'a self) -> bool {
286        trace_utils_pb::is_measured(self)
287    }
288
289    fn is_partial_snapshot(&'a self) -> bool {
290        trace_utils_pb::is_partial_snapshot(self)
291    }
292
293    fn has_top_level(&'a self) -> bool {
294        trace_utils_pb::has_top_level(self)
295    }
296
297    fn get_meta(&'a self, key: &str) -> Option<&'a str> {
298        self.meta.get(key).map(|v| v.as_str())
299    }
300
301    fn get_metrics(&'a self, key: &str) -> Option<f64> {
302        self.metrics.get(key).copied()
303    }
304}