1use std::collections::HashMap;
4use std::time::Duration;
5
6use relay_event_schema::protocol::{Contexts, Event, Span, SpanId, TraceContext};
7use relay_protocol::Annotated;
8
9use crate::normalize::breakdowns::TimeWindowSpan;
10
11fn interval_exclusive_time(mut parent: TimeWindowSpan, intervals: &[TimeWindowSpan]) -> Duration {
16 let mut exclusive_time = Duration::new(0, 0);
17
18 for interval in intervals {
19 if interval.start >= parent.end {
21 break;
22 }
23
24 if let Ok(start_offset) = (interval.start - parent.start).to_std() {
26 exclusive_time += start_offset;
27 }
28
29 parent.start = interval.end.clamp(parent.start, parent.end);
30 }
31
32 exclusive_time + parent.duration()
34}
35
36fn set_event_exclusive_time(
37 event_interval: TimeWindowSpan,
38 contexts: &mut Contexts,
39 span_map: &HashMap<SpanId, Vec<TimeWindowSpan>>,
40) {
41 let Some(trace_context) = contexts.get_mut::<TraceContext>() else {
42 return;
43 };
44
45 if trace_context.exclusive_time.value().is_some() {
46 return;
48 }
49
50 let Some(span_id) = trace_context.span_id.value() else {
51 return;
52 };
53
54 let child_intervals = span_map
55 .get(span_id)
56 .map(|vec| vec.as_slice())
57 .unwrap_or_default();
58
59 let exclusive_time = interval_exclusive_time(event_interval, child_intervals);
60 trace_context.exclusive_time =
61 Annotated::new(relay_common::time::duration_to_millis(exclusive_time));
62}
63
64fn set_span_exclusive_time(
65 span: &mut Annotated<Span>,
66 span_map: &HashMap<SpanId, Vec<TimeWindowSpan>>,
67) {
68 let span = match span.value_mut() {
69 None => return,
70 Some(span) => span,
71 };
72
73 let span_id = match span.span_id.value() {
74 None => return,
75 Some(span_id) => span_id,
76 };
77
78 let span_interval = match (span.start_timestamp.value(), span.timestamp.value()) {
79 (Some(start), Some(end)) => TimeWindowSpan::new(*start, *end),
80 _ => return,
81 };
82
83 let child_intervals = span_map
84 .get(span_id)
85 .map(|vec| vec.as_slice())
86 .unwrap_or_default();
87
88 let exclusive_time = interval_exclusive_time(span_interval, child_intervals);
89 span.exclusive_time = Annotated::new(relay_common::time::duration_to_millis(exclusive_time));
90}
91
92pub fn compute_span_exclusive_time(event: &mut Event) {
94 let contexts = match event.contexts.value_mut() {
95 Some(contexts) => contexts,
96 _ => return,
97 };
98
99 let event_interval = match (event.start_timestamp.value(), event.timestamp.value()) {
100 (Some(start), Some(end)) => TimeWindowSpan::new(*start, *end),
101 _ => return,
102 };
103
104 let spans = event.spans.value_mut().get_or_insert_with(Vec::new);
105
106 let mut span_map = HashMap::new();
107 for span in spans.iter() {
108 let span = match span.value() {
109 None => continue,
110 Some(span) => span,
111 };
112
113 let parent_span_id = match span.parent_span_id.value() {
114 None => continue,
115 Some(parent_span_id) => parent_span_id.clone(),
116 };
117
118 let interval = match (span.start_timestamp.value(), span.timestamp.value()) {
119 (Some(start), Some(end)) => TimeWindowSpan::new(*start, *end),
120 _ => continue,
121 };
122
123 span_map
124 .entry(parent_span_id)
125 .or_insert_with(Vec::new)
126 .push(interval)
127 }
128
129 for intervals in span_map.values_mut() {
131 intervals.sort_unstable_by_key(|interval| interval.start);
132 }
133
134 set_event_exclusive_time(event_interval, contexts, &span_map);
135
136 for span in spans.iter_mut() {
137 set_span_exclusive_time(span, &span_map);
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use chrono::{TimeZone, Utc};
144 use relay_event_schema::protocol::{EventType, Timestamp};
145 use similar_asserts::assert_eq;
146
147 use super::*;
148
149 fn make_event(
150 start: Timestamp,
151 end: Timestamp,
152 span_id: &str,
153 spans: Vec<Annotated<Span>>,
154 ) -> Event {
155 Event {
156 ty: EventType::Transaction.into(),
157 start_timestamp: Annotated::new(start),
158 timestamp: Annotated::new(end),
159 contexts: {
160 let mut contexts = Contexts::new();
161 contexts.add(TraceContext {
162 trace_id: Annotated::new("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap()),
163 span_id: Annotated::new(SpanId(span_id.into())),
164 ..Default::default()
165 });
166 Annotated::new(contexts)
167 },
168 spans: spans.into(),
169 ..Default::default()
170 }
171 }
172
173 fn make_span(
174 op: &str,
175 description: &str,
176 start: Timestamp,
177 end: Timestamp,
178 span_id: &str,
179 parent_span_id: &str,
180 ) -> Annotated<Span> {
181 Annotated::new(Span {
182 op: Annotated::new(op.into()),
183 description: Annotated::new(description.into()),
184 start_timestamp: Annotated::new(start),
185 timestamp: Annotated::new(end),
186 trace_id: Annotated::new("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap()),
187 span_id: Annotated::new(SpanId(span_id.into())),
188 parent_span_id: Annotated::new(SpanId(parent_span_id.into())),
189 ..Default::default()
190 })
191 }
192
193 fn extract_exclusive_time(span: &Span) -> (&SpanId, f64) {
194 (
195 span.span_id.value().unwrap(),
196 *span.exclusive_time.value().unwrap(),
197 )
198 }
199
200 fn extract_span_exclusive_times(event: &Event) -> HashMap<&SpanId, f64> {
201 let spans = event.spans.value().unwrap();
202 let mut exclusive_times: HashMap<_, _> = spans
203 .iter()
204 .filter_map(Annotated::value)
205 .map(extract_exclusive_time)
206 .collect();
207
208 let trace_context = event.context::<TraceContext>().unwrap();
209 let transaction_span_id = trace_context.span_id.value().unwrap();
210 let transaction_exclusive_time = *trace_context.exclusive_time.value().unwrap();
211 exclusive_times.insert(transaction_span_id, transaction_exclusive_time);
212
213 exclusive_times
214 }
215
216 #[test]
217 fn test_childless_spans() {
218 let mut event = make_event(
219 Utc.timestamp_opt(1609455600, 0).unwrap().into(),
220 Utc.timestamp_opt(1609455605, 0).unwrap().into(),
221 "aaaaaaaaaaaaaaaa",
222 vec![
223 make_span(
224 "db",
225 "SELECT * FROM table;",
226 Utc.timestamp_opt(1609455601, 0).unwrap().into(),
227 Utc.timestamp_opt(1609455604, 0).unwrap().into(),
228 "bbbbbbbbbbbbbbbb",
229 "aaaaaaaaaaaaaaaa",
230 ),
231 make_span(
232 "db",
233 "SELECT * FROM table;",
234 Utc.timestamp_opt(1609455601, 0).unwrap().into(),
235 Utc.timestamp_opt(1609455603, 500_000_000).unwrap().into(),
236 "cccccccccccccccc",
237 "aaaaaaaaaaaaaaaa",
238 ),
239 make_span(
240 "db",
241 "SELECT * FROM table;",
242 Utc.timestamp_opt(1609455603, 0).unwrap().into(),
243 Utc.timestamp_opt(1609455604, 877_000_000).unwrap().into(),
244 "dddddddddddddddd",
245 "aaaaaaaaaaaaaaaa",
246 ),
247 ],
248 );
249
250 compute_span_exclusive_time(&mut event);
251
252 assert_eq!(
253 extract_span_exclusive_times(&event),
254 HashMap::from_iter([
255 (&SpanId("aaaaaaaaaaaaaaaa".to_string()), 1123.0),
256 (&SpanId("bbbbbbbbbbbbbbbb".to_string()), 3000.0),
257 (&SpanId("cccccccccccccccc".to_string()), 2500.0),
258 (&SpanId("dddddddddddddddd".to_string()), 1877.0)
259 ]),
260 );
261 }
262
263 #[test]
264 fn test_nested_spans() {
265 let mut event = make_event(
266 Utc.timestamp_opt(1609455600, 0).unwrap().into(),
267 Utc.timestamp_opt(1609455605, 0).unwrap().into(),
268 "aaaaaaaaaaaaaaaa",
269 vec![
270 make_span(
271 "db",
272 "SELECT * FROM table;",
273 Utc.timestamp_opt(1609455601, 0).unwrap().into(),
274 Utc.timestamp_opt(1609455602, 0).unwrap().into(),
275 "bbbbbbbbbbbbbbbb",
276 "aaaaaaaaaaaaaaaa",
277 ),
278 make_span(
279 "db",
280 "SELECT * FROM table;",
281 Utc.timestamp_opt(1609455601, 200_000_000).unwrap().into(),
282 Utc.timestamp_opt(1609455601, 800_000_000).unwrap().into(),
283 "cccccccccccccccc",
284 "bbbbbbbbbbbbbbbb",
285 ),
286 make_span(
287 "db",
288 "SELECT * FROM table;",
289 Utc.timestamp_opt(1609455601, 400_000_000).unwrap().into(),
290 Utc.timestamp_opt(1609455601, 600_000_000).unwrap().into(),
291 "dddddddddddddddd",
292 "cccccccccccccccc",
293 ),
294 ],
295 );
296
297 compute_span_exclusive_time(&mut event);
298
299 assert_eq!(
300 extract_span_exclusive_times(&event),
301 HashMap::from_iter([
302 (&SpanId("aaaaaaaaaaaaaaaa".to_string()), 4000.0),
303 (&SpanId("bbbbbbbbbbbbbbbb".to_string()), 400.0),
304 (&SpanId("cccccccccccccccc".to_string()), 400.0),
305 (&SpanId("dddddddddddddddd".to_string()), 200.0),
306 ])
307 );
308 }
309
310 #[test]
311 fn test_overlapping_child_spans() {
312 let mut event = make_event(
313 Utc.timestamp_opt(1609455600, 0).unwrap().into(),
314 Utc.timestamp_opt(1609455605, 0).unwrap().into(),
315 "aaaaaaaaaaaaaaaa",
316 vec![
317 make_span(
318 "db",
319 "SELECT * FROM table;",
320 Utc.timestamp_opt(1609455601, 0).unwrap().into(),
321 Utc.timestamp_opt(1609455602, 0).unwrap().into(),
322 "bbbbbbbbbbbbbbbb",
323 "aaaaaaaaaaaaaaaa",
324 ),
325 make_span(
326 "db",
327 "SELECT * FROM table;",
328 Utc.timestamp_opt(1609455601, 200_000_000).unwrap().into(),
329 Utc.timestamp_opt(1609455601, 600_000_000).unwrap().into(),
330 "cccccccccccccccc",
331 "bbbbbbbbbbbbbbbb",
332 ),
333 make_span(
334 "db",
335 "SELECT * FROM table;",
336 Utc.timestamp_opt(1609455601, 400_000_000).unwrap().into(),
337 Utc.timestamp_opt(1609455601, 800_000_000).unwrap().into(),
338 "dddddddddddddddd",
339 "bbbbbbbbbbbbbbbb",
340 ),
341 ],
342 );
343
344 compute_span_exclusive_time(&mut event);
345
346 assert_eq!(
347 extract_span_exclusive_times(&event),
348 HashMap::from_iter([
349 (&SpanId("aaaaaaaaaaaaaaaa".to_string()), 4000.0),
350 (&SpanId("bbbbbbbbbbbbbbbb".to_string()), 400.0),
351 (&SpanId("cccccccccccccccc".to_string()), 400.0),
352 (&SpanId("dddddddddddddddd".to_string()), 400.0),
353 ])
354 );
355 }
356
357 #[test]
358 fn test_child_spans_dont_intersect_parent() {
359 let mut event = make_event(
360 Utc.timestamp_opt(1609455600, 0).unwrap().into(),
361 Utc.timestamp_opt(1609455605, 0).unwrap().into(),
362 "aaaaaaaaaaaaaaaa",
363 vec![
364 make_span(
365 "db",
366 "SELECT * FROM table;",
367 Utc.timestamp_opt(1609455601, 0).unwrap().into(),
368 Utc.timestamp_opt(1609455602, 0).unwrap().into(),
369 "bbbbbbbbbbbbbbbb",
370 "aaaaaaaaaaaaaaaa",
371 ),
372 make_span(
373 "db",
374 "SELECT * FROM table;",
375 Utc.timestamp_opt(1609455600, 400_000_000).unwrap().into(),
376 Utc.timestamp_opt(1609455600, 800_000_000).unwrap().into(),
377 "cccccccccccccccc",
378 "bbbbbbbbbbbbbbbb",
379 ),
380 make_span(
381 "db",
382 "SELECT * FROM table;",
383 Utc.timestamp_opt(1609455602, 200_000_000).unwrap().into(),
384 Utc.timestamp_opt(1609455602, 600_000_000).unwrap().into(),
385 "dddddddddddddddd",
386 "bbbbbbbbbbbbbbbb",
387 ),
388 ],
389 );
390
391 compute_span_exclusive_time(&mut event);
392
393 assert_eq!(
394 extract_span_exclusive_times(&event),
395 HashMap::from_iter([
396 (&SpanId("aaaaaaaaaaaaaaaa".to_string()), 4000.0),
397 (&SpanId("bbbbbbbbbbbbbbbb".to_string()), 1000.0),
398 (&SpanId("cccccccccccccccc".to_string()), 400.0),
399 (&SpanId("dddddddddddddddd".to_string()), 400.0),
400 ])
401 );
402 }
403
404 #[test]
405 fn test_child_spans_extend_beyond_parent() {
406 let mut event = make_event(
407 Utc.timestamp_opt(1609455600, 0).unwrap().into(),
408 Utc.timestamp_opt(1609455605, 0).unwrap().into(),
409 "aaaaaaaaaaaaaaaa",
410 vec![
411 make_span(
412 "db",
413 "SELECT * FROM table;",
414 Utc.timestamp_opt(1609455601, 0).unwrap().into(),
415 Utc.timestamp_opt(1609455602, 0).unwrap().into(),
416 "bbbbbbbbbbbbbbbb",
417 "aaaaaaaaaaaaaaaa",
418 ),
419 make_span(
420 "db",
421 "SELECT * FROM table;",
422 Utc.timestamp_opt(1609455600, 800_000_000).unwrap().into(),
423 Utc.timestamp_opt(1609455601, 400_000_000).unwrap().into(),
424 "cccccccccccccccc",
425 "bbbbbbbbbbbbbbbb",
426 ),
427 make_span(
428 "db",
429 "SELECT * FROM table;",
430 Utc.timestamp_opt(1609455601, 600_000_000).unwrap().into(),
431 Utc.timestamp_opt(1609455602, 200_000_000).unwrap().into(),
432 "dddddddddddddddd",
433 "bbbbbbbbbbbbbbbb",
434 ),
435 ],
436 );
437
438 compute_span_exclusive_time(&mut event);
439
440 assert_eq!(
441 extract_span_exclusive_times(&event),
442 HashMap::from_iter([
443 (&SpanId("aaaaaaaaaaaaaaaa".to_string()), 4000.0),
444 (&SpanId("bbbbbbbbbbbbbbbb".to_string()), 200.0),
445 (&SpanId("cccccccccccccccc".to_string()), 600.0),
446 (&SpanId("dddddddddddddddd".to_string()), 600.0),
447 ])
448 );
449 }
450
451 #[test]
452 fn test_child_spans_consumes_all_of_parent() {
453 let mut event = make_event(
454 Utc.timestamp_opt(1609455600, 0).unwrap().into(),
455 Utc.timestamp_opt(1609455605, 0).unwrap().into(),
456 "aaaaaaaaaaaaaaaa",
457 vec![
458 make_span(
459 "db",
460 "SELECT * FROM table;",
461 Utc.timestamp_opt(1609455601, 0).unwrap().into(),
462 Utc.timestamp_opt(1609455602, 0).unwrap().into(),
463 "bbbbbbbbbbbbbbbb",
464 "aaaaaaaaaaaaaaaa",
465 ),
466 make_span(
467 "db",
468 "SELECT * FROM table;",
469 Utc.timestamp_opt(1609455600, 800_000_000).unwrap().into(),
470 Utc.timestamp_opt(1609455601, 600_000_000).unwrap().into(),
471 "cccccccccccccccc",
472 "bbbbbbbbbbbbbbbb",
473 ),
474 make_span(
475 "db",
476 "SELECT * FROM table;",
477 Utc.timestamp_opt(1609455601, 400_000_000).unwrap().into(),
478 Utc.timestamp_opt(1609455602, 200_000_000).unwrap().into(),
479 "dddddddddddddddd",
480 "bbbbbbbbbbbbbbbb",
481 ),
482 ],
483 );
484
485 compute_span_exclusive_time(&mut event);
486
487 assert_eq!(
488 extract_span_exclusive_times(&event),
489 HashMap::from_iter([
490 (&SpanId("aaaaaaaaaaaaaaaa".to_string()), 4000.0),
491 (&SpanId("bbbbbbbbbbbbbbbb".to_string()), 0.0),
492 (&SpanId("cccccccccccccccc".to_string()), 800.0),
493 (&SpanId("dddddddddddddddd".to_string()), 800.0),
494 ])
495 );
496 }
497
498 #[test]
499 fn test_only_immediate_child_spans_affect_calculation() {
500 let mut event = make_event(
501 Utc.timestamp_opt(1609455600, 0).unwrap().into(),
502 Utc.timestamp_opt(1609455605, 0).unwrap().into(),
503 "aaaaaaaaaaaaaaaa",
504 vec![
505 make_span(
506 "db",
507 "SELECT * FROM table;",
508 Utc.timestamp_opt(1609455601, 0).unwrap().into(),
509 Utc.timestamp_opt(1609455602, 0).unwrap().into(),
510 "bbbbbbbbbbbbbbbb",
511 "aaaaaaaaaaaaaaaa",
512 ),
513 make_span(
514 "db",
515 "SELECT * FROM table;",
516 Utc.timestamp_opt(1609455601, 600_000_000).unwrap().into(),
517 Utc.timestamp_opt(1609455602, 200_000_000).unwrap().into(),
518 "cccccccccccccccc",
519 "bbbbbbbbbbbbbbbb",
520 ),
521 make_span(
524 "db",
525 "SELECT * FROM table;",
526 Utc.timestamp_opt(1609455601, 400_000_000).unwrap().into(),
527 Utc.timestamp_opt(1609455601, 800_000_000).unwrap().into(),
528 "dddddddddddddddd",
529 "cccccccccccccccc",
530 ),
531 ],
532 );
533
534 compute_span_exclusive_time(&mut event);
535
536 assert_eq!(
537 extract_span_exclusive_times(&event),
538 HashMap::from_iter([
539 (&SpanId("aaaaaaaaaaaaaaaa".to_string()), 4000.0),
540 (&SpanId("bbbbbbbbbbbbbbbb".to_string()), 600.0),
541 (&SpanId("cccccccccccccccc".to_string()), 400.0),
542 (&SpanId("dddddddddddddddd".to_string()), 400.0),
543 ])
544 );
545 }
546}