Skip to main content

turbo_trace_server/
span_ref.rs

1use std::{
2    cmp::max,
3    collections::VecDeque,
4    fmt::{Debug, Formatter},
5    vec,
6};
7
8use hashbrown::HashMap;
9use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator};
10use rustc_hash::FxHashSet;
11use turbo_rcstr::{RcStr, rcstr};
12
13use crate::{
14    FxIndexMap,
15    bottom_up::build_bottom_up_graph,
16    span::{
17        Span, SpanEvent, SpanEventSelfTime, SpanExtra, SpanGraphEvent, SpanIndex, SpanName,
18        SpanNames, SpanTimeData, SpanTotals,
19    },
20    span_bottom_up_ref::SpanBottomUpRef,
21    span_graph_ref::{SpanGraphEventRef, SpanGraphRef, event_map_to_list},
22    store::{SpanId, Store},
23    timestamp::Timestamp,
24};
25
26pub type GroupNameToDirectAndRecusiveSpans<'l> =
27    FxIndexMap<(&'l RcStr, &'l RcStr), (Vec<SpanIndex>, Vec<SpanIndex>)>;
28
29#[derive(Copy, Clone)]
30pub struct SpanRef<'a> {
31    pub(crate) span: &'a Span,
32    pub(crate) store: &'a Store,
33    pub(crate) index: usize,
34}
35
36impl<'a> SpanRef<'a> {
37    pub fn id(&self) -> SpanId {
38        unsafe { SpanId::new_unchecked(self.index << 1) }
39    }
40
41    pub fn index(&self) -> SpanIndex {
42        SpanIndex::new(self.index).unwrap()
43    }
44
45    pub fn parent(&self) -> Option<SpanRef<'a>> {
46        self.span.parent.map(|index| SpanRef {
47            span: &self.store.spans[index.get()],
48            store: self.store,
49            index: index.get(),
50        })
51    }
52
53    pub fn start(&self) -> Timestamp {
54        self.span.start
55    }
56
57    pub fn time_data(&self) -> &'a SpanTimeData {
58        &self.span.time_data
59    }
60
61    pub fn extra(&self) -> &'a SpanExtra {
62        self.span.extra()
63    }
64
65    pub fn names(&self) -> &'a SpanNames {
66        self.span.names()
67    }
68
69    pub fn end(&self) -> Timestamp {
70        let time_data = self.time_data();
71        *time_data.end.get_or_init(|| {
72            max(
73                time_data.self_end,
74                self.children()
75                    .map(|child| child.end())
76                    .max()
77                    .unwrap_or_default(),
78            )
79        })
80    }
81
82    pub fn is_complete(&self) -> bool {
83        self.span.is_complete
84    }
85
86    pub fn is_root(&self) -> bool {
87        self.index == 0
88    }
89
90    pub fn nice_name(&self) -> (&'a RcStr, &'a RcStr) {
91        let SpanName { category, title } = &self.names().nice_name;
92        (category, title)
93    }
94
95    pub fn group_name(&self) -> (&'a RcStr, &'a RcStr) {
96        let SpanName { category, title } = &self.names().group_name;
97        (category, title)
98    }
99
100    pub fn args(&self) -> impl Iterator<Item = (&RcStr, &RcStr)> {
101        self.span.args.iter().map(|(k, v)| (k, v))
102    }
103
104    pub fn self_time(&self) -> Timestamp {
105        self.time_data().self_time
106    }
107
108    pub fn self_allocations(&self) -> u64 {
109        // 32 bytes for the tracing itself
110        self.span.self_allocations.saturating_sub(32)
111    }
112
113    pub fn self_deallocations(&self) -> u64 {
114        self.span.self_deallocations
115    }
116
117    pub fn self_persistent_allocations(&self) -> u64 {
118        self.self_allocations()
119            .saturating_sub(self.span.self_deallocations)
120    }
121
122    pub fn self_allocation_count(&self) -> u64 {
123        // 4 allocations for the tracing itself
124        self.span.self_allocation_count.saturating_sub(4)
125    }
126
127    pub fn self_span_count(&self) -> u64 {
128        1
129    }
130
131    /// Events sorted by start time, including self time and children.
132    pub fn events(&self) -> impl DoubleEndedIterator<Item = SpanEventRef<'a>> {
133        self.span
134            .events
135            .iter()
136            .map(|event: &'a SpanEvent| match event {
137                SpanEvent::SelfTime(self_time) => SpanEventRef::SelfTime {
138                    self_time: SpanEventSelfTimeRef {
139                        store: self.store,
140                        self_time,
141                    },
142                },
143                SpanEvent::Child { index, .. } => SpanEventRef::Child {
144                    span: SpanRef {
145                        span: &self.store.spans[index.get()],
146                        store: self.store,
147                        index: index.get(),
148                    },
149                },
150            })
151    }
152
153    /// Children sorted by start time, excluding self time.
154    pub fn children(&self) -> impl DoubleEndedIterator<Item = SpanRef<'a>> + 'a + use<'a> {
155        self.span.events.iter().filter_map(|event| match event {
156            SpanEvent::SelfTime { .. } => None,
157            SpanEvent::Child { index, .. } => Some(SpanRef {
158                span: &self.store.spans[index.get()],
159                store: self.store,
160                index: index.get(),
161            }),
162        })
163    }
164
165    /// Children sorted by start time, excluding self time, in parallel.
166    pub fn children_par(&self) -> impl ParallelIterator<Item = SpanRef<'a>> + 'a {
167        self.span.events.par_iter().filter_map(|event| match event {
168            SpanEvent::SelfTime { .. } => None,
169            SpanEvent::Child { index, .. } => Some(SpanRef {
170                span: &self.store.spans[index.get()],
171                store: self.store,
172                index: index.get(),
173            }),
174        })
175    }
176
177    pub fn total_time(&self) -> Timestamp {
178        *self.time_data().total_time.get_or_init(|| {
179            self.children()
180                .map(|child| child.total_time())
181                .reduce(|a, b| a + b)
182                .unwrap_or_default()
183                + self.self_time()
184        })
185    }
186
187    /// Compute (or fetch) the bundled subtree totals. All six totals share a
188    /// single `OnceLock`, so the first call walks the subtree once and fills
189    /// every field; subsequent calls return cached values. Children's bundles
190    /// are computed recursively, so depth-many calls happen once per subtree
191    /// regardless of which field is queried first.
192    fn totals(&self) -> &'a SpanTotals {
193        self.span.totals.get_or_init(|| {
194            let mut t = SpanTotals {
195                max_depth: 0,
196                allocations: self.self_allocations(),
197                deallocations: self.self_deallocations(),
198                persistent_allocations: self.self_persistent_allocations(),
199                allocation_count: self.self_allocation_count(),
200                span_count: 1,
201            };
202            for child in self.children() {
203                let c = child.totals();
204                t.max_depth = max(t.max_depth, c.max_depth + 1);
205                t.allocations += c.allocations;
206                t.deallocations += c.deallocations;
207                t.persistent_allocations += c.persistent_allocations;
208                t.allocation_count += c.allocation_count;
209                t.span_count += c.span_count;
210            }
211            t
212        })
213    }
214
215    pub fn total_allocations(&self) -> u64 {
216        self.totals().allocations
217    }
218
219    pub fn total_deallocations(&self) -> u64 {
220        self.totals().deallocations
221    }
222
223    pub fn total_persistent_allocations(&self) -> u64 {
224        self.totals().persistent_allocations
225    }
226
227    pub fn total_allocation_count(&self) -> u64 {
228        self.totals().allocation_count
229    }
230
231    pub fn total_span_count(&self) -> u64 {
232        self.totals().span_count
233    }
234
235    pub fn corrected_self_time(&self) -> Timestamp {
236        let store = self.store;
237        *self.time_data().corrected_self_time.get_or_init(|| {
238            let mut self_time = self
239                .span
240                .events
241                .par_iter()
242                .filter_map(|event: &'a SpanEvent| {
243                    if let SpanEvent::SelfTime(self_time) = event {
244                        return Some(
245                            SpanEventSelfTimeRef { store, self_time }.corrected_self_time(),
246                        );
247                    }
248                    None
249                })
250                .sum();
251            if self.children().next().is_none() {
252                self_time = max(self_time, Timestamp::from_value(1));
253            }
254            self_time
255        })
256    }
257
258    pub fn corrected_total_time(&self) -> Timestamp {
259        *self.time_data().corrected_total_time.get_or_init(|| {
260            self.children_par()
261                .map(|child| child.corrected_total_time())
262                .sum::<Timestamp>()
263                + self.corrected_self_time()
264        })
265    }
266
267    pub fn max_depth(&self) -> u32 {
268        self.totals().max_depth
269    }
270
271    pub fn graph(&self) -> impl Iterator<Item = SpanGraphEventRef<'a>> + '_ {
272        self.extra()
273            .graph
274            .get_or_init(|| {
275                struct Entry<'a> {
276                    span: SpanRef<'a>,
277                    recursive: Vec<SpanIndex>,
278                }
279                let entries = self
280                    .children_par()
281                    .map(|span| {
282                        let name = span.group_name();
283                        let mut recursive = Vec::new();
284                        let mut queue = VecDeque::with_capacity(0);
285                        for nested_child in span.children() {
286                            let nested_name = nested_child.group_name();
287                            if name == nested_name {
288                                recursive.push(nested_child.index());
289                                queue.push_back(nested_child);
290                            }
291                        }
292                        while let Some(child) = queue.pop_front() {
293                            for nested_child in child.children() {
294                                let nested_name = nested_child.group_name();
295                                if name == nested_name {
296                                    recursive.push(nested_child.index());
297                                    queue.push_back(nested_child);
298                                }
299                            }
300                        }
301                        Entry { span, recursive }
302                    })
303                    .collect_vec_list();
304                let mut map: GroupNameToDirectAndRecusiveSpans = FxIndexMap::default();
305                for Entry {
306                    span,
307                    mut recursive,
308                } in entries.into_iter().flatten()
309                {
310                    let name = span.group_name();
311                    let (list, recursive_list) = map.entry(name).or_default();
312                    list.push(span.index());
313                    recursive_list.append(&mut recursive);
314                }
315                event_map_to_list(map)
316            })
317            .iter()
318            .map(|event| match event {
319                SpanGraphEvent::SelfTime { duration } => SpanGraphEventRef::SelfTime {
320                    duration: *duration,
321                },
322                SpanGraphEvent::Child { child } => SpanGraphEventRef::Child {
323                    graph: SpanGraphRef {
324                        graph: child.clone(),
325                        store: self.store,
326                    },
327                },
328            })
329    }
330
331    pub fn bottom_up(self) -> impl Iterator<Item = SpanBottomUpRef<'a>> {
332        self.extra()
333            .bottom_up
334            .get_or_init(|| build_bottom_up_graph([self].into_iter()))
335            .iter()
336            .map(move |bottom_up| SpanBottomUpRef {
337                bottom_up: bottom_up.clone(),
338                store: self.store,
339            })
340    }
341
342    pub fn search(&self, query: &str) -> impl Iterator<Item = SpanRef<'a>> {
343        let mut query_items = query.split(",").map(str::trim);
344        let index = self.search_index();
345        let mut result = FxHashSet::default();
346        let query = query_items.next().unwrap();
347        for (key, spans) in index {
348            if key.contains(query) {
349                result.extend(spans.iter().copied());
350            }
351        }
352        for query in query_items {
353            let mut and_result = FxHashSet::default();
354            for (key, spans) in index {
355                if key.contains(query) {
356                    and_result.extend(spans.iter().copied());
357                }
358            }
359            result.retain(|index| and_result.contains(index));
360        }
361        let store = self.store;
362        result.into_iter().map(move |index| SpanRef {
363            span: &store.spans[index.get()],
364            store,
365            index: index.get(),
366        })
367    }
368
369    fn search_index(&self) -> &HashMap<RcStr, Vec<SpanIndex>> {
370        self.extra().search_index.get_or_init(|| {
371            let mut all_spans = Vec::new();
372            all_spans.push(self.index);
373            let mut i = 0;
374            while i < all_spans.len() {
375                let index = all_spans[i];
376                let span = SpanRef {
377                    span: &self.store.spans[index],
378                    store: self.store,
379                    index,
380                };
381                for child in span.children() {
382                    all_spans.push(child.index);
383                }
384                i += 1;
385            }
386
387            enum SpanOrMap<'a> {
388                Span(SpanRef<'a>),
389                Map(HashMap<RcStr, Vec<SpanIndex>>),
390            }
391
392            /// Insert `span_index` into `index` under the given key.
393            ///
394            /// `lookup` is the string used for the hash lookup. `make_key` produces
395            /// the stored map key when a new entry is created (may differ from
396            /// `lookup`, e.g. `"name=foo"` stored under the hash of `"foo"`).
397            fn push_to_index(
398                index: &mut HashMap<RcStr, Vec<SpanIndex>>,
399                lookup: &str,
400                make_key: impl FnOnce() -> RcStr,
401                span_index: SpanIndex,
402            ) {
403                index
404                    .raw_entry_mut()
405                    .from_key(lookup)
406                    .and_modify(|_, v| v.push(span_index))
407                    .or_insert_with(|| (make_key(), vec![span_index]));
408            }
409
410            fn add_span_to_map<'a>(index: &mut HashMap<RcStr, Vec<SpanIndex>>, span: SpanRef<'a>) {
411                if span.is_root() {
412                    return;
413                }
414                let (cat, name) = span.nice_name();
415                if !cat.is_empty() {
416                    push_to_index(index, cat, || cat.clone(), span.index());
417                }
418                if !name.is_empty() {
419                    push_to_index(
420                        index,
421                        name,
422                        || RcStr::from(format!("name={name}")),
423                        span.index(),
424                    );
425                }
426                for (k, v) in span.span.args.iter() {
427                    push_to_index(
428                        index,
429                        v.as_str(),
430                        || RcStr::from(format!("{k}={v}")),
431                        span.index(),
432                    );
433                }
434                if !span.is_complete() && span.span.name != "thread" {
435                    push_to_index(
436                        index,
437                        "incomplete_span",
438                        || rcstr!("incomplete_span"),
439                        span.index(),
440                    );
441                }
442            }
443
444            let result = all_spans
445                .into_par_iter()
446                .map(|index| {
447                    SpanOrMap::Span(SpanRef {
448                        span: &self.store.spans[index],
449                        store: self.store,
450                        index,
451                    })
452                })
453                .reduce(
454                    || SpanOrMap::Map(HashMap::default()),
455                    |a, b| {
456                        let mut map = match a {
457                            SpanOrMap::Span(span) => {
458                                let mut map = HashMap::default();
459                                add_span_to_map(&mut map, span);
460                                map
461                            }
462                            SpanOrMap::Map(map) => map,
463                        };
464                        match b {
465                            SpanOrMap::Span(span) => {
466                                add_span_to_map(&mut map, span);
467                            }
468                            SpanOrMap::Map(other_map) => {
469                                for (name, value) in other_map {
470                                    map.entry(name).or_default().extend(value);
471                                }
472                            }
473                        }
474                        SpanOrMap::Map(map)
475                    },
476                );
477            match result {
478                SpanOrMap::Span(span) => {
479                    let mut map = HashMap::default();
480                    add_span_to_map(&mut map, span);
481                    map
482                }
483                SpanOrMap::Map(map) => map,
484            }
485        })
486    }
487}
488
489impl Debug for SpanRef<'_> {
490    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
491        f.debug_struct("SpanRef")
492            .field("id", &self.id())
493            .field("name", &self.nice_name())
494            .field("start", &self.start())
495            .field("end", &self.end())
496            .field("is_complete", &self.is_complete())
497            .field("self_time", &self.self_time())
498            .field("total_time", &self.total_time())
499            .field("max_depth", &self.max_depth())
500            .finish()
501    }
502}
503
504pub struct SpanEventSelfTimeRef<'a> {
505    store: &'a Store,
506    self_time: &'a SpanEventSelfTime,
507}
508
509impl<'a> SpanEventSelfTimeRef<'a> {
510    pub fn start(&self) -> Timestamp {
511        self.self_time.start
512    }
513
514    pub fn end(&self) -> Timestamp {
515        self.self_time.end()
516    }
517
518    pub fn corrected_self_time(&self) -> Timestamp {
519        *self.self_time.corrected_self_time.get_or_init(|| {
520            // `duration` is `NonZeroU64`, so zero-duration events are filtered
521            // at construction time (see `SpanEvent::self_time`).
522            let end = self.self_time.end();
523            let duration = Timestamp::from_value(self.self_time.duration.get());
524            self.store.set_max_self_time_lookup(end);
525            self.store.self_time_tree.as_ref().map_or(duration, |tree| {
526                tree.lookup_range_corrected_time(self.self_time.start, end)
527            })
528        })
529    }
530}
531
532pub enum SpanEventRef<'a> {
533    SelfTime { self_time: SpanEventSelfTimeRef<'a> },
534    Child { span: SpanRef<'a> },
535}
536
537impl SpanEventRef<'_> {
538    pub fn total_time(&self) -> Timestamp {
539        match self {
540            SpanEventRef::SelfTime {
541                self_time: event, ..
542            } => event.end().saturating_sub(event.start()),
543            SpanEventRef::Child { span } => span.total_time(),
544        }
545    }
546
547    pub fn corrected_self_time(&self) -> Timestamp {
548        match self {
549            SpanEventRef::SelfTime { self_time: event } => event.corrected_self_time(),
550            SpanEventRef::Child { span } => span.corrected_self_time(),
551        }
552    }
553}
554
555#[cfg(test)]
556mod tests {
557    use rustc_hash::FxHashSet;
558    use turbo_rcstr::RcStr;
559
560    use crate::{span::SpanArgs, span_ref::SpanRef, store::Store, timestamp::Timestamp};
561
562    fn span_ref<'a>(store: &'a Store, idx: crate::span::SpanIndex) -> SpanRef<'a> {
563        SpanRef {
564            span: &store.spans[idx.get()],
565            store,
566            index: idx.get(),
567        }
568    }
569
570    #[test]
571    fn totals_aggregate_subtree() {
572        let mut store = Store::new();
573        let mut outdated = FxHashSet::default();
574
575        // root → a → b
576        // root → c
577        let a = store.add_span(
578            None,
579            Timestamp::from_micros(0),
580            RcStr::default(),
581            RcStr::from("a"),
582            SpanArgs::new(),
583            &mut outdated,
584        );
585        let b = store.add_span(
586            Some(a),
587            Timestamp::from_micros(1),
588            RcStr::default(),
589            RcStr::from("b"),
590            SpanArgs::new(),
591            &mut outdated,
592        );
593        let c = store.add_span(
594            None,
595            Timestamp::from_micros(2),
596            RcStr::default(),
597            RcStr::from("c"),
598            SpanArgs::new(),
599            &mut outdated,
600        );
601
602        // Use values large enough that the 32-byte/4-allocation tracing
603        // overhead subtraction in `self_allocations()` / `self_allocation_count()`
604        // doesn't dominate.
605        store.add_allocation(a, 1000, 10, &mut outdated);
606        store.add_allocation(b, 500, 5, &mut outdated);
607        store.add_allocation(c, 200, 2, &mut outdated);
608
609        let a_ref = span_ref(&store, a);
610        let b_ref = span_ref(&store, b);
611        let c_ref = span_ref(&store, c);
612
613        // Sanity: per-span self_* values reflect the saturating overhead subtraction.
614        assert_eq!(a_ref.self_allocations(), 1000 - 32);
615        assert_eq!(b_ref.self_allocations(), 500 - 32);
616        assert_eq!(c_ref.self_allocations(), 200 - 32);
617
618        // Totals are the recursive subtree sum of self_*.
619        assert_eq!(
620            a_ref.total_allocations(),
621            a_ref.self_allocations() + b_ref.self_allocations()
622        );
623        assert_eq!(b_ref.total_allocations(), b_ref.self_allocations());
624        assert_eq!(c_ref.total_allocations(), c_ref.self_allocations());
625
626        // span_count is 1 + child count.
627        assert_eq!(a_ref.total_span_count(), 2);
628        assert_eq!(b_ref.total_span_count(), 1);
629        assert_eq!(c_ref.total_span_count(), 1);
630
631        // total_allocation_count similarly.
632        assert_eq!(
633            a_ref.total_allocation_count(),
634            a_ref.self_allocation_count() + b_ref.self_allocation_count()
635        );
636    }
637
638    #[test]
639    fn totals_invalidate_and_recompute() {
640        let mut store = Store::new();
641        let mut outdated = FxHashSet::default();
642        let s = store.add_span(
643            None,
644            Timestamp::from_micros(0),
645            RcStr::default(),
646            RcStr::from("s"),
647            SpanArgs::new(),
648            &mut outdated,
649        );
650        store.add_allocation(s, 1000, 10, &mut outdated);
651
652        // Cache the totals.
653        let before = span_ref(&store, s).total_allocations();
654        assert_eq!(before, 1000 - 32);
655
656        // Add more allocations and invalidate.
657        let mut outdated = FxHashSet::default();
658        store.add_allocation(s, 200, 2, &mut outdated);
659        store.invalidate_outdated_spans(&outdated);
660
661        // After invalidation, the cached totals must be recomputed from new self_*.
662        let after = span_ref(&store, s).total_allocations();
663        assert_eq!(after, 1200 - 32);
664    }
665}