Skip to main content

servers/http/jaeger/v3/
summary.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::{BTreeMap, HashSet};
16
17use opentelemetry_proto::tonic::common::v1::any_value;
18use opentelemetry_proto::tonic::trace::v1::status::StatusCode;
19use opentelemetry_proto::tonic::trace::v1::{Span, TracesData};
20use serde_json::{Value, json};
21
22use crate::otlp::trace::KEY_SERVICE_NAME;
23
24pub fn summarize(traces: TracesData) -> Vec<Value> {
25    let mut by_trace: BTreeMap<&[u8], Vec<(&str, &Span)>> = BTreeMap::new();
26    for resource in &traces.resource_spans {
27        let service = resource
28            .resource
29            .as_ref()
30            .and_then(|resource| {
31                resource
32                    .attributes
33                    .iter()
34                    .find(|attr| attr.key == KEY_SERVICE_NAME)
35                    .and_then(|attr| attr.value.as_ref())
36                    .and_then(|value| match &value.value {
37                        Some(any_value::Value::StringValue(name)) => Some(name.as_str()),
38                        _ => None,
39                    })
40            })
41            .unwrap_or_default();
42        for scope in &resource.scope_spans {
43            for span in &scope.spans {
44                by_trace
45                    .entry(&span.trace_id)
46                    .or_default()
47                    .push((service, span));
48            }
49        }
50    }
51    by_trace
52        .into_iter()
53        .map(|(trace_id, spans)| {
54            let ids: HashSet<_> = spans
55                .iter()
56                .map(|(_, span)| span.span_id.as_slice())
57                .collect();
58            let mut services: BTreeMap<&str, (usize, usize)> = BTreeMap::new();
59            let mut min_start = u64::MAX;
60            let mut max_end = 0;
61            let mut errors = 0;
62            let mut orphans = 0;
63            let mut root: Option<(&str, &Span)> = None;
64            for &(service, span) in &spans {
65                let stats = services.entry(service).or_default();
66                stats.0 += 1;
67                if span
68                    .status
69                    .as_ref()
70                    .is_some_and(|status| status.code == StatusCode::Error as i32)
71                {
72                    stats.1 += 1;
73                    errors += 1;
74                }
75                min_start = min_start.min(span.start_time_unix_nano);
76                max_end = max_end.max(span.end_time_unix_nano);
77                if span.parent_span_id.iter().all(|byte| *byte == 0) {
78                    if root.is_none_or(|(_, root)| {
79                        span.start_time_unix_nano < root.start_time_unix_nano
80                    }) {
81                        root = Some((service, span));
82                    }
83                } else if !ids.contains(span.parent_span_id.as_slice()) {
84                    orphans += 1;
85                }
86            }
87            let (root_service, root_operation) =
88                root.map_or(("", ""), |(service, span)| (service, span.name.as_str()));
89            let services: Vec<_> = services
90                .into_iter()
91                .map(|(name, (spans, errors))| {
92                    json!({
93                        "name": name, "spanCount": spans, "errorSpanCount": errors,
94                    })
95                })
96                .collect();
97            json!({
98                "traceId": hex::encode(trace_id),
99                "rootServiceName": root_service,
100                "rootOperationName": root_operation,
101                "minStartTimeUnixNano": min_start.to_string(),
102                "maxEndTimeUnixNano": max_end.to_string(),
103                "spanCount": spans.len(),
104                "errorSpanCount": errors,
105                "orphanSpanCount": orphans,
106                "services": services,
107            })
108        })
109        .collect()
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn test_summary_roots_and_orphans() {
118        let traces: TracesData = serde_json::from_value(json!({"resourceSpans":[
119            {"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"b"}}]},"scopeSpans":[{"spans":[
120                {"traceId":"00000000000000000000000000000001","spanId":"0000000000000001","name":"late-root","startTimeUnixNano":"20","endTimeUnixNano":"50"},
121                {"traceId":"00000000000000000000000000000001","spanId":"0000000000000002","parentSpanId":"0000000000000099","name":"orphan","startTimeUnixNano":"1","endTimeUnixNano":"60","status":{"code":2}},
122                {"traceId":"00000000000000000000000000000002","spanId":"0000000000000003","parentSpanId":"0000000000000001","startTimeUnixNano":"1","endTimeUnixNano":"2"}
123            ]}]},
124            {"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"a"}}]},"scopeSpans":[{"spans":[
125                {"traceId":"00000000000000000000000000000001","spanId":"0000000000000004","parentSpanId":"0000000000000000","name":"early-root","startTimeUnixNano":"10","endTimeUnixNano":"20"},
126                {"traceId":"00000000000000000000000000000001","spanId":"0000000000000005","parentSpanId":"0000000000000004","startTimeUnixNano":"11","endTimeUnixNano":"15","status":{"code":2}}
127            ]}]}
128        ]})).unwrap();
129        let summaries = summarize(traces);
130        assert_eq!(
131            summaries[0],
132            json!({
133                "traceId":"00000000000000000000000000000001", "rootServiceName":"a", "rootOperationName":"early-root",
134                "minStartTimeUnixNano":"1", "maxEndTimeUnixNano":"60", "spanCount":4, "errorSpanCount":2, "orphanSpanCount":1,
135                "services":[{"name":"a","spanCount":2,"errorSpanCount":1},{"name":"b","spanCount":2,"errorSpanCount":1}]
136            })
137        );
138        assert_eq!(summaries[1]["orphanSpanCount"], 1);
139        assert_eq!(summaries[1]["rootServiceName"], "");
140        assert_eq!(summaries[1]["rootOperationName"], "");
141    }
142}