Skip to main content

turbo_trace_server/
viewer.rs

1use std::cmp::{Reverse, max};
2
3use either::Either;
4use itertools::Itertools;
5use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
6use rustc_hash::{FxHashMap, FxHashSet};
7use serde::Serialize;
8use turbo_rcstr::rcstr;
9
10use crate::{
11    server::ViewRect,
12    span_bottom_up_ref::SpanBottomUpRef,
13    span_graph_ref::{SpanGraphEventRef, SpanGraphRef},
14    span_ref::{SpanEventRef, SpanRef},
15    store::{SpanId, Store},
16    timestamp::Timestamp,
17    u64_empty_string,
18};
19
20const EXTRA_WIDTH_PERCENTAGE: u64 = 50;
21const EXTRA_HEIGHT: u64 = 5;
22
23#[derive(Default)]
24pub struct Viewer {
25    span_options: FxHashMap<SpanId, SpanOptions>,
26}
27
28#[derive(Clone, Copy, Debug)]
29pub enum ValueMode {
30    Duration,
31    Cpu,
32    Allocations,
33    Deallocations,
34    PersistentAllocations,
35    AllocationCount,
36    Count,
37    AllocationsPerTime,
38    PersistentAllocationsPerTime,
39    AllocationCountPerTime,
40}
41
42impl ValueMode {
43    fn secondary(&self) -> ValueMode {
44        match self {
45            ValueMode::Duration => ValueMode::Cpu,
46            ValueMode::Cpu => ValueMode::Duration,
47            ValueMode::Allocations => ValueMode::PersistentAllocations,
48            ValueMode::Deallocations => ValueMode::PersistentAllocations,
49            ValueMode::PersistentAllocations => ValueMode::Allocations,
50            ValueMode::AllocationCount => ValueMode::Allocations,
51            ValueMode::Count => ValueMode::Count,
52            ValueMode::AllocationsPerTime => ValueMode::PersistentAllocationsPerTime,
53            ValueMode::PersistentAllocationsPerTime => ValueMode::AllocationsPerTime,
54            ValueMode::AllocationCountPerTime => ValueMode::AllocationsPerTime,
55        }
56    }
57
58    fn value_from_span(&self, span: &SpanRef<'_>) -> u64 {
59        match self {
60            ValueMode::Duration => *span.corrected_total_time(),
61            ValueMode::Cpu => *span.total_time(),
62            ValueMode::Allocations => span.total_allocations(),
63            ValueMode::Deallocations => span.total_deallocations(),
64            ValueMode::PersistentAllocations => span.total_persistent_allocations(),
65            ValueMode::AllocationCount => span.total_allocation_count(),
66            ValueMode::Count => span.total_span_count(),
67            ValueMode::AllocationsPerTime => {
68                value_over_time(span.total_allocations(), span.corrected_total_time())
69            }
70            ValueMode::AllocationCountPerTime => {
71                value_over_time(span.total_allocation_count(), span.corrected_total_time())
72            }
73            ValueMode::PersistentAllocationsPerTime => value_over_time(
74                span.total_persistent_allocations(),
75                span.corrected_total_time(),
76            ),
77        }
78    }
79
80    fn value_from_event(&self, event: &SpanEventRef<'_>) -> u64 {
81        match self {
82            ValueMode::Duration => *event.corrected_self_time(),
83            ValueMode::Cpu => *event.total_time(),
84            _ => match event {
85                SpanEventRef::Child { span } => self.value_from_span(span),
86                SpanEventRef::SelfTime { .. } => 0,
87            },
88        }
89    }
90
91    fn value_from_graph(&self, graph: &SpanGraphRef<'_>) -> u64 {
92        match self {
93            ValueMode::Duration => *graph.corrected_total_time(),
94            ValueMode::Cpu => *graph.total_time(),
95            ValueMode::Allocations => graph.total_allocations(),
96            ValueMode::Deallocations => graph.total_deallocations(),
97            ValueMode::PersistentAllocations => graph.total_persistent_allocations(),
98            ValueMode::AllocationCount => graph.total_allocation_count(),
99            ValueMode::Count => graph.total_span_count(),
100            ValueMode::AllocationsPerTime => {
101                value_over_time(graph.total_allocations(), graph.corrected_total_time())
102            }
103            ValueMode::AllocationCountPerTime => {
104                value_over_time(graph.total_allocation_count(), graph.corrected_total_time())
105            }
106            ValueMode::PersistentAllocationsPerTime => value_over_time(
107                graph.total_persistent_allocations(),
108                graph.corrected_total_time(),
109            ),
110        }
111    }
112
113    fn value_from_graph_event(&self, event: &SpanGraphEventRef<'_>) -> u64 {
114        match self {
115            ValueMode::Duration => *event.corrected_total_time(),
116            ValueMode::Cpu => *event.total_time(),
117            ValueMode::Allocations => event.total_allocations(),
118            ValueMode::Deallocations => event.total_deallocations(),
119            ValueMode::PersistentAllocations => event.total_persistent_allocations(),
120            ValueMode::AllocationCount => event.total_allocation_count(),
121            ValueMode::Count => event.total_span_count(),
122            ValueMode::AllocationsPerTime => {
123                value_over_time(event.total_allocations(), event.corrected_total_time())
124            }
125            ValueMode::AllocationCountPerTime => {
126                value_over_time(event.total_allocation_count(), event.corrected_total_time())
127            }
128            ValueMode::PersistentAllocationsPerTime => value_over_time(
129                event.total_persistent_allocations(),
130                event.corrected_total_time(),
131            ),
132        }
133    }
134
135    fn value_from_bottom_up(&self, bottom_up: &SpanBottomUpRef<'_>) -> u64 {
136        match self {
137            ValueMode::Duration => *bottom_up.corrected_self_time(),
138            ValueMode::Cpu => *bottom_up.self_time(),
139            ValueMode::Allocations => bottom_up.self_allocations(),
140            ValueMode::Deallocations => bottom_up.self_deallocations(),
141            ValueMode::PersistentAllocations => bottom_up.self_persistent_allocations(),
142            ValueMode::AllocationCount => bottom_up.self_allocation_count(),
143            ValueMode::Count => bottom_up.self_span_count(),
144            ValueMode::AllocationsPerTime => value_over_time(
145                bottom_up.self_allocations(),
146                bottom_up.corrected_self_time(),
147            ),
148            ValueMode::AllocationCountPerTime => value_over_time(
149                bottom_up.self_allocation_count(),
150                bottom_up.corrected_self_time(),
151            ),
152            ValueMode::PersistentAllocationsPerTime => value_over_time(
153                bottom_up.self_persistent_allocations(),
154                bottom_up.corrected_self_time(),
155            ),
156        }
157    }
158
159    fn value_from_bottom_up_span(&self, bottom_up_span: &SpanRef<'_>) -> u64 {
160        match self {
161            ValueMode::Duration => *bottom_up_span.corrected_self_time(),
162            ValueMode::Cpu => *bottom_up_span.self_time(),
163            ValueMode::Allocations => bottom_up_span.self_allocations(),
164            ValueMode::Deallocations => bottom_up_span.self_deallocations(),
165            ValueMode::PersistentAllocations => bottom_up_span.self_persistent_allocations(),
166            ValueMode::AllocationCount => bottom_up_span.self_allocation_count(),
167            ValueMode::Count => bottom_up_span.self_span_count(),
168            ValueMode::AllocationsPerTime => value_over_time(
169                bottom_up_span.self_allocations(),
170                bottom_up_span.corrected_self_time(),
171            ),
172            ValueMode::AllocationCountPerTime => value_over_time(
173                bottom_up_span.self_allocation_count(),
174                bottom_up_span.corrected_self_time(),
175            ),
176            ValueMode::PersistentAllocationsPerTime => value_over_time(
177                bottom_up_span.self_persistent_allocations(),
178                bottom_up_span.corrected_self_time(),
179            ),
180        }
181    }
182}
183
184/// this is unfortunately int division but itll have to do.
185///
186/// cases where count per time is very low is probably not important
187fn value_over_time(value: u64, time: Timestamp) -> u64 {
188    value.checked_div(*time).unwrap_or(0)
189}
190
191#[derive(Clone, Copy, Debug, PartialEq, Eq)]
192pub enum SortMode {
193    ExecutionOrder,
194    Value,
195    Name,
196}
197
198#[derive(Clone, Copy, Debug)]
199pub enum ViewMode {
200    RawSpans { sort_mode: SortMode },
201    Aggregated { sort_mode: SortMode },
202    BottomUp { sort_mode: SortMode },
203    AggregatedBottomUp { sort_mode: SortMode },
204}
205
206impl ViewMode {
207    fn as_spans(self) -> Self {
208        match self {
209            ViewMode::RawSpans { sort_mode } => ViewMode::RawSpans { sort_mode },
210            ViewMode::Aggregated { sort_mode } => ViewMode::RawSpans { sort_mode },
211            ViewMode::BottomUp { sort_mode } => ViewMode::BottomUp { sort_mode },
212            ViewMode::AggregatedBottomUp { sort_mode } => ViewMode::BottomUp { sort_mode },
213        }
214    }
215
216    fn as_bottom_up(self) -> Self {
217        match self {
218            ViewMode::RawSpans { sort_mode } => ViewMode::BottomUp { sort_mode },
219            ViewMode::Aggregated { sort_mode } => ViewMode::AggregatedBottomUp { sort_mode },
220            ViewMode::BottomUp { sort_mode } => ViewMode::BottomUp { sort_mode },
221            ViewMode::AggregatedBottomUp { sort_mode } => {
222                ViewMode::AggregatedBottomUp { sort_mode }
223            }
224        }
225    }
226
227    fn aggregate_children(&self) -> bool {
228        match self {
229            ViewMode::RawSpans { .. } => false,
230            ViewMode::Aggregated { .. } => true,
231            ViewMode::BottomUp { .. } => false,
232            ViewMode::AggregatedBottomUp { .. } => true,
233        }
234    }
235
236    fn bottom_up(&self) -> bool {
237        match self {
238            ViewMode::RawSpans { .. } => false,
239            ViewMode::Aggregated { .. } => false,
240            ViewMode::BottomUp { .. } => true,
241            ViewMode::AggregatedBottomUp { .. } => true,
242        }
243    }
244
245    fn sort_children(&self) -> SortMode {
246        match self {
247            ViewMode::RawSpans { sort_mode } => *sort_mode,
248            ViewMode::Aggregated { sort_mode } => *sort_mode,
249            ViewMode::BottomUp { sort_mode } => *sort_mode,
250            ViewMode::AggregatedBottomUp { sort_mode } => *sort_mode,
251        }
252    }
253}
254
255#[derive(Default)]
256struct SpanOptions {
257    view_mode: Option<(ViewMode, bool)>,
258}
259
260pub struct Update {
261    pub lines: Vec<ViewLineUpdate>,
262    pub max: u64,
263}
264
265#[derive(Serialize, Debug)]
266#[serde(rename_all = "camelCase")]
267pub struct ViewLineUpdate {
268    y: u64,
269    spans: Vec<ViewSpan>,
270}
271
272#[derive(Serialize, Debug)]
273#[serde(rename_all = "camelCase")]
274pub struct ViewSpan {
275    #[serde(with = "u64_empty_string")]
276    id: u64,
277    #[serde(rename = "x")]
278    start: u64,
279    #[serde(rename = "w")]
280    width: u64,
281    #[serde(rename = "cat")]
282    category: String,
283    #[serde(rename = "t")]
284    text: String,
285    #[serde(rename = "c")]
286    count: u64,
287    #[serde(rename = "k")]
288    kind: u8,
289    #[serde(rename = "s")]
290    start_in_parent: u32,
291    #[serde(rename = "e")]
292    end_in_parent: u32,
293    #[serde(rename = "v")]
294    secondary: u64,
295}
296
297#[derive(Debug)]
298enum QueueItem<'a> {
299    Span(SpanRef<'a>),
300    SpanGraph(SpanGraphRef<'a>),
301    SpanBottomUp(SpanBottomUpRef<'a>),
302    SpanBottomUpSpan(SpanRef<'a>),
303}
304
305impl QueueItem<'_> {
306    fn value(&self, value_mode: ValueMode) -> u64 {
307        match self {
308            QueueItem::Span(span) => value_mode.value_from_span(span),
309            QueueItem::SpanGraph(span_graph) => value_mode.value_from_graph(span_graph),
310            QueueItem::SpanBottomUp(span_bottom_up) => {
311                value_mode.value_from_bottom_up(span_bottom_up)
312            }
313            QueueItem::SpanBottomUpSpan(span) => value_mode.value_from_bottom_up_span(span),
314        }
315    }
316
317    fn max_depth(&self) -> u32 {
318        match self {
319            QueueItem::Span(span) => span.max_depth(),
320            QueueItem::SpanGraph(span_graph) => span_graph.max_depth(),
321            QueueItem::SpanBottomUp(span_bottom_up) => span_bottom_up.max_depth(),
322            QueueItem::SpanBottomUpSpan(span) => span.max_depth(),
323        }
324    }
325}
326
327#[derive(Debug, PartialEq, Eq)]
328enum FilterMode {
329    SelectedItem,
330    Parent,
331    Child,
332}
333
334#[derive(Debug)]
335struct QueueItemWithState<'a> {
336    item: QueueItem<'a>,
337    line_index: usize,
338    start: u64,
339    placeholder: bool,
340    view_mode: ViewMode,
341    filtered: Option<FilterMode>,
342}
343
344struct ChildItem<'a> {
345    item: QueueItemWithState<'a>,
346    depth: u32,
347    pixel_range: (u64, u64),
348}
349
350impl Viewer {
351    pub fn new() -> Self {
352        Self::default()
353    }
354
355    pub fn set_view_mode(&mut self, id: SpanId, view_mode: Option<(ViewMode, bool)>) {
356        self.span_options.entry(id).or_default().view_mode = view_mode;
357    }
358
359    pub fn compute_update(&mut self, store: &Store, view_rect: &ViewRect) -> Update {
360        let mut highlighted_spans: FxHashSet<SpanId> = FxHashSet::default();
361        let mut highlighted_span_parents: FxHashSet<SpanId> = FxHashSet::default();
362        let search_mode = !view_rect.query.is_empty();
363        let (query, focus_mode) = if let Some(query) = view_rect.query.strip_suffix('!') {
364            (query, true)
365        } else {
366            (view_rect.query.as_str(), false)
367        };
368
369        let default_view_mode = view_rect.view_mode.as_str();
370        let (default_view_mode, default_sort_mode) =
371            if let Some(s) = default_view_mode.strip_suffix("-sorted-by-name") {
372                (s, SortMode::Name)
373            } else if let Some(s) = default_view_mode.strip_suffix("-sorted-by-value") {
374                (s, SortMode::Value)
375            } else if let Some(s) = default_view_mode.strip_suffix("-sorted") {
376                (s, SortMode::Value)
377            } else {
378                (default_view_mode, SortMode::ExecutionOrder)
379            };
380        let (default_view_mode, with_root) = match default_view_mode {
381            "aggregated" => (
382                ViewMode::Aggregated {
383                    sort_mode: default_sort_mode,
384                },
385                false,
386            ),
387            "root-aggregated" => (
388                ViewMode::Aggregated {
389                    sort_mode: default_sort_mode,
390                },
391                true,
392            ),
393            "raw-spans" => (
394                ViewMode::RawSpans {
395                    sort_mode: default_sort_mode,
396                },
397                false,
398            ),
399            "bottom-up" => (
400                ViewMode::BottomUp {
401                    sort_mode: default_sort_mode,
402                },
403                false,
404            ),
405            "aggregated-bottom-up" => (
406                ViewMode::AggregatedBottomUp {
407                    sort_mode: default_sort_mode,
408                },
409                false,
410            ),
411            "root-aggregated-bottom-up" => (
412                ViewMode::AggregatedBottomUp {
413                    sort_mode: default_sort_mode,
414                },
415                true,
416            ),
417            _ => (
418                ViewMode::Aggregated {
419                    sort_mode: default_sort_mode,
420                },
421                false,
422            ),
423        };
424
425        let value_mode = match view_rect.value_mode.as_str() {
426            "duration" => ValueMode::Duration,
427            "cpu" => ValueMode::Cpu,
428            "allocations" => ValueMode::Allocations,
429            "deallocations" => ValueMode::Deallocations,
430            "persistent-deallocations" => ValueMode::PersistentAllocations,
431            "allocation-count" => ValueMode::AllocationCount,
432            "allocations-per-time" => ValueMode::AllocationsPerTime,
433            "allocation-count-per-time" => ValueMode::AllocationCountPerTime,
434            "persistent-allocations-per-time" => ValueMode::PersistentAllocationsPerTime,
435            "count" => ValueMode::Count,
436            _ => ValueMode::Duration,
437        };
438
439        if !store.has_time_info() && matches!(value_mode, ValueMode::Duration) {
440            return Update {
441                lines: vec![ViewLineUpdate {
442                    spans: vec![ViewSpan {
443                        id: 0,
444                        start: 0,
445                        width: 1,
446                        category: "info".to_string(),
447                        text: "No time info in trace".to_string(),
448                        count: 1,
449                        kind: 0,
450                        start_in_parent: 0,
451                        end_in_parent: 0,
452                        secondary: 0,
453                    }],
454                    y: 0,
455                }],
456                max: 1,
457            };
458        }
459
460        let mut queue = Vec::new();
461
462        let root_spans = if with_root {
463            vec![store.root_span()]
464        } else {
465            let mut root_spans = store.root_spans().collect::<Vec<_>>();
466            root_spans.sort_by_key(|span| span.start());
467            root_spans
468        };
469
470        let mut children = Vec::new();
471        let mut current = 0;
472        let offset = root_spans
473            .iter()
474            .min_by_key(|span| span.start())
475            .map_or(Timestamp::ZERO, |span| span.start());
476        root_spans.par_iter().for_each(|span| {
477            span.max_depth();
478            QueueItem::Span(*span).value(value_mode);
479        });
480        for span in root_spans {
481            if matches!(value_mode, ValueMode::Duration) {
482                // Move current to start if needed.
483                current = max(current, *(span.start() - offset));
484            }
485            if add_child_item(
486                &mut children,
487                &mut current,
488                view_rect,
489                0,
490                default_view_mode,
491                value_mode,
492                QueueItem::Span(span),
493                Some(if search_mode {
494                    FilterMode::Parent
495                } else {
496                    FilterMode::SelectedItem
497                }),
498            ) && search_mode
499            {
500                let mut has_results = false;
501                for mut result in span.search(query) {
502                    has_results = true;
503                    highlighted_spans.insert(result.id());
504                    while let Some(parent) = result.parent() {
505                        result = parent;
506                        if !highlighted_span_parents.insert(result.id()) {
507                            break;
508                        }
509                    }
510                }
511                if has_results {
512                    highlighted_spans.insert(span.id());
513                } else {
514                    children.last_mut().unwrap().item.filtered = None;
515                }
516            }
517        }
518        enqueue_children(children, &mut queue);
519        queue.par_iter().for_each(|item| {
520            let QueueItem::Span(span) = item.item else {
521                return;
522            };
523            let view_mode = if span.is_complete() {
524                item.view_mode
525            } else {
526                item.view_mode.as_spans()
527            };
528
529            match (view_mode.bottom_up(), view_mode.aggregate_children()) {
530                (false, false) => {}
531                (false, true) => {
532                    span.graph()
533                        .collect::<Vec<_>>()
534                        .par_iter()
535                        .for_each(|event| {
536                            value_mode.value_from_graph_event(event);
537                        });
538                }
539                (true, false) => {
540                    span.bottom_up()
541                        .collect::<Vec<_>>()
542                        .par_iter()
543                        .for_each(|bu| {
544                            bu.spans().collect::<Vec<_>>().par_iter().for_each(|span| {
545                                value_mode.value_from_bottom_up_span(span);
546                            });
547                        });
548                }
549                (true, true) => {
550                    span.bottom_up()
551                        .collect::<Vec<_>>()
552                        .par_iter()
553                        .for_each(|bu| {
554                            value_mode.value_from_bottom_up(bu);
555                        });
556                }
557            }
558        });
559
560        let mut lines: Vec<Vec<LineEntry<'_>>> = vec![];
561
562        while let Some(QueueItemWithState {
563            item: span,
564            line_index,
565            start,
566            placeholder,
567            view_mode,
568            mut filtered,
569        }) = queue.pop()
570        {
571            let line = get_line(&mut lines, line_index);
572            let width = span.value(value_mode);
573            let secondary = span.value(value_mode.secondary());
574
575            let skipped_by_focus =
576                focus_mode && matches!(filtered, Some(FilterMode::Parent) | None);
577
578            let get_filter_mode = |span: SpanId| {
579                if focus_mode
580                    && matches!(filtered, Some(FilterMode::SelectedItem | FilterMode::Child))
581                {
582                    Some(FilterMode::Child)
583                } else if search_mode {
584                    if highlighted_spans.contains(&span) {
585                        Some(FilterMode::SelectedItem)
586                    } else if highlighted_span_parents.contains(&span) {
587                        Some(FilterMode::Parent)
588                    } else {
589                        None
590                    }
591                } else {
592                    Some(FilterMode::SelectedItem)
593                }
594            };
595
596            // compute children
597            let mut children = Vec::new();
598            let mut current = start;
599            let child_line_index = if skipped_by_focus {
600                line_index
601            } else {
602                line_index + 1
603            };
604            match &span {
605                QueueItem::Span(span) => {
606                    let (selected_view_mode, inherit) = (!span.is_root())
607                        .then(|| self.span_options.get(&span.id()).and_then(|o| o.view_mode))
608                        .flatten()
609                        .unwrap_or_else(|| {
610                            (
611                                if span.is_complete() {
612                                    view_mode
613                                } else {
614                                    view_mode.as_spans()
615                                },
616                                false,
617                            )
618                        });
619
620                    let view_mode = if inherit {
621                        selected_view_mode
622                    } else {
623                        view_mode
624                    };
625
626                    let selected_view_mode =
627                        if search_mode && highlighted_span_parents.contains(&span.id()) {
628                            selected_view_mode.as_spans()
629                        } else {
630                            selected_view_mode
631                        };
632
633                    if selected_view_mode.bottom_up() {
634                        let bottom_up = span.bottom_up();
635                        if selected_view_mode.aggregate_children() {
636                            let bottom_up = match selected_view_mode.sort_children() {
637                                SortMode::Value => {
638                                    Either::Left(bottom_up.sorted_by_cached_key(|child| {
639                                        Reverse(value_mode.value_from_bottom_up(child))
640                                    }))
641                                }
642                                SortMode::Name => {
643                                    Either::Left(bottom_up.sorted_by_cached_key(|child| {
644                                        let (cat, title) = child.nice_name();
645                                        (title.to_string(), cat.to_string())
646                                    }))
647                                }
648                                SortMode::ExecutionOrder => Either::Right(bottom_up),
649                            };
650                            for child in bottom_up {
651                                // TODO search
652                                add_child_item(
653                                    &mut children,
654                                    &mut current,
655                                    view_rect,
656                                    child_line_index,
657                                    view_mode,
658                                    value_mode,
659                                    QueueItem::SpanBottomUp(child),
660                                    Some(FilterMode::SelectedItem),
661                                );
662                            }
663                        } else {
664                            let bottom_up = bottom_up
665                                .flat_map(|bottom_up| bottom_up.spans().collect::<Vec<_>>());
666                            let bottom_up = match selected_view_mode.sort_children() {
667                                SortMode::Value => {
668                                    Either::Left(bottom_up.sorted_by_cached_key(|child| {
669                                        Reverse(value_mode.value_from_bottom_up_span(child))
670                                    }))
671                                }
672                                SortMode::Name => {
673                                    Either::Left(bottom_up.sorted_by_cached_key(|child| {
674                                        let (cat, title) = child.nice_name();
675                                        (title.to_string(), cat.to_string())
676                                    }))
677                                }
678                                SortMode::ExecutionOrder => Either::Right(bottom_up),
679                            };
680                            for child in bottom_up {
681                                let filtered = get_filter_mode(child.id());
682                                add_child_item(
683                                    &mut children,
684                                    &mut current,
685                                    view_rect,
686                                    child_line_index,
687                                    view_mode,
688                                    value_mode,
689                                    QueueItem::SpanBottomUpSpan(child),
690                                    filtered,
691                                );
692                            }
693                        }
694                    } else if !selected_view_mode.aggregate_children() {
695                        let spans = match selected_view_mode.sort_children() {
696                            SortMode::Value => {
697                                Either::Left(span.events().sorted_by_cached_key(|child| {
698                                    Reverse(value_mode.value_from_event(child))
699                                }))
700                            }
701                            SortMode::Name => {
702                                Either::Left(span.events().sorted_by_cached_key(|child| {
703                                    let (cat, title) = match child {
704                                        SpanEventRef::Child { span } => span.nice_name(),
705                                        SpanEventRef::SelfTime { .. } => (&rcstr!(""), &rcstr!("")),
706                                    };
707                                    (title.to_string(), cat.to_string())
708                                }))
709                            }
710                            SortMode::ExecutionOrder => Either::Right(span.events()),
711                        };
712                        for child in spans {
713                            match child {
714                                SpanEventRef::SelfTime { .. } => {
715                                    current += value_mode.value_from_event(&child);
716                                }
717                                SpanEventRef::Child { span: child } => {
718                                    let filtered = get_filter_mode(child.id());
719                                    add_child_item(
720                                        &mut children,
721                                        &mut current,
722                                        view_rect,
723                                        child_line_index,
724                                        view_mode,
725                                        value_mode,
726                                        QueueItem::Span(child),
727                                        filtered,
728                                    );
729                                }
730                            }
731                        }
732                    } else {
733                        let events = match selected_view_mode.sort_children() {
734                            SortMode::Value => {
735                                Either::Left(span.graph().sorted_by_cached_key(|child| {
736                                    Reverse(value_mode.value_from_graph_event(child))
737                                }))
738                            }
739                            SortMode::Name => {
740                                Either::Left(span.graph().sorted_by_cached_key(|child| {
741                                    let (cat, title) = match child {
742                                        SpanGraphEventRef::Child { graph } => graph.nice_name(),
743                                        SpanGraphEventRef::SelfTime { .. } => {
744                                            (&rcstr!(""), &rcstr!(""))
745                                        }
746                                    };
747                                    (title.to_string(), cat.to_string())
748                                }))
749                            }
750                            SortMode::ExecutionOrder => Either::Right(span.graph()),
751                        };
752                        for event in events {
753                            let filtered = if search_mode {
754                                None
755                            } else {
756                                Some(FilterMode::SelectedItem)
757                            };
758                            match event {
759                                SpanGraphEventRef::SelfTime { duration: _ } => {}
760                                SpanGraphEventRef::Child { graph } => {
761                                    add_child_item(
762                                        &mut children,
763                                        &mut current,
764                                        view_rect,
765                                        child_line_index,
766                                        view_mode,
767                                        value_mode,
768                                        QueueItem::SpanGraph(graph),
769                                        filtered,
770                                    );
771                                }
772                            }
773                        }
774                    }
775                }
776                QueueItem::SpanGraph(span_graph) => {
777                    let (selected_view_mode, inherit) = self
778                        .span_options
779                        .get(&span_graph.id())
780                        .and_then(|o| o.view_mode)
781                        .unwrap_or((view_mode, false));
782
783                    let view_mode = if inherit {
784                        selected_view_mode
785                    } else {
786                        view_mode
787                    };
788                    if selected_view_mode.bottom_up() {
789                        let bottom_up = span_graph.bottom_up();
790                        if selected_view_mode.aggregate_children() {
791                            let bottom_up = match selected_view_mode.sort_children() {
792                                SortMode::Value => {
793                                    Either::Left(bottom_up.sorted_by_cached_key(|child| {
794                                        Reverse(value_mode.value_from_bottom_up(child))
795                                    }))
796                                }
797                                SortMode::Name => {
798                                    Either::Left(bottom_up.sorted_by_cached_key(|child| {
799                                        let (cat, title) = child.nice_name();
800                                        (title.to_string(), cat.to_string())
801                                    }))
802                                }
803                                SortMode::ExecutionOrder => Either::Right(bottom_up),
804                            };
805                            for child in bottom_up {
806                                // TODO search
807                                add_child_item(
808                                    &mut children,
809                                    &mut current,
810                                    view_rect,
811                                    child_line_index,
812                                    view_mode,
813                                    value_mode,
814                                    QueueItem::SpanBottomUp(child),
815                                    Some(FilterMode::SelectedItem),
816                                );
817                            }
818                        } else {
819                            let bottom_up = bottom_up
820                                .flat_map(|bottom_up| bottom_up.spans().collect::<Vec<_>>());
821                            let bottom_up = match selected_view_mode.sort_children() {
822                                SortMode::Value => {
823                                    Either::Left(bottom_up.sorted_by_cached_key(|child| {
824                                        Reverse(value_mode.value_from_bottom_up_span(child))
825                                    }))
826                                }
827                                SortMode::Name => {
828                                    Either::Left(bottom_up.sorted_by_cached_key(|child| {
829                                        let (cat, title) = child.nice_name();
830                                        (title.to_string(), cat.to_string())
831                                    }))
832                                }
833                                SortMode::ExecutionOrder => {
834                                    Either::Right(bottom_up.sorted_by_key(|child| child.start()))
835                                }
836                            };
837                            for child in bottom_up {
838                                let filtered = get_filter_mode(child.id());
839                                add_child_item(
840                                    &mut children,
841                                    &mut current,
842                                    view_rect,
843                                    child_line_index,
844                                    view_mode,
845                                    value_mode,
846                                    QueueItem::SpanBottomUpSpan(child),
847                                    filtered,
848                                );
849                            }
850                        }
851                    } else if !selected_view_mode.aggregate_children() && span_graph.count() > 1 {
852                        let spans = match selected_view_mode.sort_children() {
853                            SortMode::Value => {
854                                Either::Left(span_graph.root_spans().sorted_by_cached_key(
855                                    |child| Reverse(value_mode.value_from_span(child)),
856                                ))
857                            }
858                            SortMode::Name => Either::Left(
859                                span_graph.root_spans().sorted_by_cached_key(|child| {
860                                    let (cat, title) = child.nice_name();
861                                    (title.to_string(), cat.to_string())
862                                }),
863                            ),
864                            SortMode::ExecutionOrder => Either::Right(
865                                span_graph.root_spans().sorted_by_key(|child| child.start()),
866                            ),
867                        };
868                        for child in spans {
869                            let filtered = get_filter_mode(child.id());
870                            add_child_item(
871                                &mut children,
872                                &mut current,
873                                view_rect,
874                                child_line_index,
875                                view_mode,
876                                value_mode,
877                                QueueItem::Span(child),
878                                filtered,
879                            );
880                        }
881                    } else {
882                        let events = match selected_view_mode.sort_children() {
883                            SortMode::Value => {
884                                Either::Left(span_graph.events().sorted_by_cached_key(|child| {
885                                    Reverse(value_mode.value_from_graph_event(child))
886                                }))
887                            }
888                            SortMode::Name => {
889                                Either::Left(span_graph.events().sorted_by_cached_key(|child| {
890                                    let (cat, title) = match child {
891                                        SpanGraphEventRef::Child { graph } => graph.nice_name(),
892                                        SpanGraphEventRef::SelfTime { .. } => {
893                                            (&rcstr!(""), &rcstr!(""))
894                                        }
895                                    };
896                                    (title.to_string(), cat.to_string())
897                                }))
898                            }
899                            SortMode::ExecutionOrder => Either::Right(span_graph.events()),
900                        };
901                        for child in events {
902                            if let SpanGraphEventRef::Child { graph } = child {
903                                let filtered = if search_mode {
904                                    None
905                                } else {
906                                    Some(FilterMode::SelectedItem)
907                                };
908                                add_child_item(
909                                    &mut children,
910                                    &mut current,
911                                    view_rect,
912                                    child_line_index,
913                                    view_mode,
914                                    value_mode,
915                                    QueueItem::SpanGraph(graph),
916                                    filtered,
917                                );
918                            }
919                        }
920                    }
921                }
922                QueueItem::SpanBottomUp(bottom_up) => {
923                    let view_mode = self
924                        .span_options
925                        .get(&bottom_up.id())
926                        .and_then(|o| o.view_mode)
927                        .map(|(v, _)| v.as_bottom_up())
928                        .unwrap_or(view_mode);
929
930                    if view_mode.aggregate_children() {
931                        let bottom_up = match view_mode.sort_children() {
932                            SortMode::Value => {
933                                Either::Left(bottom_up.children().sorted_by_cached_key(|child| {
934                                    Reverse(value_mode.value_from_bottom_up(child))
935                                }))
936                            }
937                            SortMode::Name => {
938                                Either::Left(bottom_up.children().sorted_by_cached_key(|child| {
939                                    let (cat, title) = child.nice_name();
940                                    (title.to_string(), cat.to_string())
941                                }))
942                            }
943                            SortMode::ExecutionOrder => Either::Right(bottom_up.children()),
944                        };
945                        for child in bottom_up {
946                            // TODO search
947                            add_child_item(
948                                &mut children,
949                                &mut current,
950                                view_rect,
951                                child_line_index,
952                                view_mode,
953                                value_mode,
954                                QueueItem::SpanBottomUp(child),
955                                Some(FilterMode::SelectedItem),
956                            );
957                        }
958                    } else {
959                        let spans = match view_mode.sort_children() {
960                            SortMode::Value => {
961                                Either::Left(bottom_up.spans().sorted_by_cached_key(|child| {
962                                    Reverse(value_mode.value_from_bottom_up_span(child))
963                                }))
964                            }
965                            SortMode::Name => {
966                                Either::Left(bottom_up.spans().sorted_by_cached_key(|child| {
967                                    let (cat, title) = child.nice_name();
968                                    (title.to_string(), cat.to_string())
969                                }))
970                            }
971                            SortMode::ExecutionOrder => Either::Right(
972                                bottom_up.spans().sorted_by_key(|child| child.start()),
973                            ),
974                        };
975                        for child in spans {
976                            let filtered = get_filter_mode(child.id());
977                            add_child_item(
978                                &mut children,
979                                &mut current,
980                                view_rect,
981                                child_line_index,
982                                view_mode,
983                                value_mode,
984                                QueueItem::SpanBottomUpSpan(child),
985                                filtered,
986                            );
987                        }
988                    }
989                }
990                QueueItem::SpanBottomUpSpan(_) => {
991                    // no children
992                }
993            }
994
995            // When span size is smaller than a pixel, we only show the deepest child.
996            if placeholder {
997                let child = children
998                    .into_iter()
999                    .max_by_key(|ChildItem { item, depth, .. }| (item.filtered.is_some(), *depth));
1000                if let Some(ChildItem {
1001                    item: mut entry, ..
1002                }) = child
1003                {
1004                    entry.placeholder = true;
1005                    queue.push(entry);
1006                }
1007
1008                if !skipped_by_focus {
1009                    // add span to line
1010                    line.push(LineEntry {
1011                        start,
1012                        width,
1013                        secondary: 0,
1014                        ty: LineEntryType::Placeholder(filtered),
1015                    });
1016                }
1017            } else {
1018                // add children to queue
1019                enqueue_children(children, &mut queue);
1020
1021                // check if we should filter based on width or count
1022                if !skipped_by_focus {
1023                    let count = match &span {
1024                        QueueItem::Span(_) => 1,
1025                        QueueItem::SpanGraph(span_graph) => span_graph.count(),
1026                        QueueItem::SpanBottomUp(bottom_up) => bottom_up.count(),
1027                        QueueItem::SpanBottomUpSpan(_) => 1,
1028                    };
1029
1030                    if let Some(false) = view_rect.count_filter.as_ref().map(|filter| match filter
1031                        .op
1032                    {
1033                        crate::server::Op::Gt => count > filter.value as usize,
1034                        crate::server::Op::Lt => count < filter.value as usize,
1035                    }) {
1036                        filtered = Some(FilterMode::SelectedItem)
1037                    }
1038
1039                    if let Some(false) = view_rect.value_filter.as_ref().map(|filter| match filter
1040                        .op
1041                    {
1042                        crate::server::Op::Gt => width > filter.value,
1043                        crate::server::Op::Lt => width < filter.value,
1044                    }) {
1045                        filtered = Some(FilterMode::SelectedItem)
1046                    }
1047
1048                    // add span to line
1049                    line.push(LineEntry {
1050                        start,
1051                        width,
1052                        secondary,
1053                        ty: match span {
1054                            QueueItem::Span(span) => LineEntryType::Span { span, filtered },
1055                            QueueItem::SpanGraph(span_graph) => {
1056                                LineEntryType::SpanGraph(span_graph, filtered)
1057                            }
1058                            QueueItem::SpanBottomUp(bottom_up) => {
1059                                LineEntryType::SpanBottomUp(bottom_up, filtered)
1060                            }
1061                            QueueItem::SpanBottomUpSpan(bottom_up_span) => {
1062                                LineEntryType::SpanBottomUpSpan(bottom_up_span, filtered)
1063                            }
1064                        },
1065                    });
1066                }
1067            }
1068        }
1069
1070        let lines = lines
1071            .into_iter()
1072            .enumerate()
1073            .map(|(y, line)| ViewLineUpdate {
1074                y: y as u64,
1075                spans: line
1076                    .into_iter()
1077                    .map(|entry| match entry.ty {
1078                        LineEntryType::Placeholder(filtered) => ViewSpan {
1079                            id: 0,
1080                            start: entry.start,
1081                            width: entry.width,
1082                            category: String::new(),
1083                            text: String::new(),
1084                            count: 1,
1085                            kind: match filtered {
1086                                Some(_) => 1,
1087                                None => 11,
1088                            },
1089                            start_in_parent: 0,
1090                            end_in_parent: 0,
1091                            secondary: 0,
1092                        },
1093                        LineEntryType::Span { span, filtered } => {
1094                            let (category, text) = span.nice_name();
1095                            let mut start_in_parent = 0;
1096                            let mut end_in_parent = 0;
1097                            if let Some(parent) = span.parent() {
1098                                let parent_start = parent.start();
1099                                let parent_duration = parent.end() - parent_start;
1100                                if !parent_duration.is_zero() {
1101                                    start_in_parent = ((span.start() - parent_start) * 10000
1102                                        / parent_duration)
1103                                        as u32;
1104                                    end_in_parent = ((span.end() - parent_start) * 10000
1105                                        / parent_duration)
1106                                        as u32;
1107                                } else {
1108                                    start_in_parent = 0;
1109                                    end_in_parent = 10000;
1110                                }
1111                            }
1112                            ViewSpan {
1113                                id: if !span.is_root() {
1114                                    span.id().get() as u64
1115                                } else {
1116                                    Default::default()
1117                                },
1118                                start: entry.start,
1119                                width: entry.width,
1120                                category: category.to_string(),
1121                                text: text.to_string(),
1122                                count: 1,
1123                                kind: match filtered {
1124                                    Some(_) => 0,
1125                                    None => 10,
1126                                },
1127                                start_in_parent,
1128                                end_in_parent,
1129                                secondary: entry.secondary,
1130                            }
1131                        }
1132                        LineEntryType::SpanGraph(graph, filtered) => {
1133                            let (category, text) = graph.nice_name();
1134                            ViewSpan {
1135                                id: graph.id().get() as u64,
1136                                start: entry.start,
1137                                width: entry.width,
1138                                category: category.to_string(),
1139                                text: text.to_string(),
1140                                count: graph.count() as u64,
1141                                kind: match filtered {
1142                                    Some(_) => 0,
1143                                    None => 10,
1144                                },
1145                                start_in_parent: 0,
1146                                end_in_parent: 0,
1147                                secondary: entry.secondary,
1148                            }
1149                        }
1150                        LineEntryType::SpanBottomUp(bottom_up, filtered) => {
1151                            let (category, text) = bottom_up.nice_name();
1152                            ViewSpan {
1153                                id: bottom_up.id().get() as u64,
1154                                start: entry.start,
1155                                width: entry.width,
1156                                category: category.to_string(),
1157                                text: text.to_string(),
1158                                count: bottom_up.count() as u64,
1159                                kind: match filtered {
1160                                    Some(_) => 2,
1161                                    None => 12,
1162                                },
1163                                start_in_parent: 0,
1164                                end_in_parent: 0,
1165                                secondary: entry.secondary,
1166                            }
1167                        }
1168                        LineEntryType::SpanBottomUpSpan(bottom_up_span, filtered) => {
1169                            let (category, text) = bottom_up_span.nice_name();
1170                            ViewSpan {
1171                                id: bottom_up_span.id().get() as u64,
1172                                start: entry.start,
1173                                width: entry.width,
1174                                category: category.to_string(),
1175                                text: text.to_string(),
1176                                count: 1,
1177                                kind: match filtered {
1178                                    Some(_) => 2,
1179                                    None => 12,
1180                                },
1181                                start_in_parent: 0,
1182                                end_in_parent: 0,
1183                                secondary: entry.secondary,
1184                            }
1185                        }
1186                    })
1187                    .collect(),
1188            })
1189            .collect();
1190
1191        Update {
1192            lines,
1193            max: max(1, current),
1194        }
1195    }
1196}
1197
1198#[allow(clippy::too_many_arguments)]
1199fn add_child_item<'a>(
1200    children: &mut Vec<ChildItem<'a>>,
1201    current: &mut u64,
1202    view_rect: &ViewRect,
1203    line_index: usize,
1204    view_mode: ViewMode,
1205    value_mode: ValueMode,
1206    child: QueueItem<'a>,
1207    filtered: Option<FilterMode>,
1208) -> bool {
1209    let child_width = child.value(value_mode);
1210    let max_depth = child.max_depth();
1211    let pixel1 = *current * view_rect.horizontal_pixels / view_rect.width;
1212    let pixel2 = ((*current + child_width) * view_rect.horizontal_pixels).div_ceil(view_rect.width);
1213    let start = *current;
1214    *current += child_width;
1215
1216    // filter by view rect (vertical)
1217    if line_index > (view_rect.y + view_rect.height + EXTRA_HEIGHT) as usize {
1218        return false;
1219    }
1220
1221    if line_index > 0 {
1222        // filter by view rect (horizontal)
1223        if start > view_rect.x + view_rect.width * (100 + EXTRA_WIDTH_PERCENTAGE) / 100 {
1224            return false;
1225        }
1226        if *current
1227            < view_rect
1228                .x
1229                .saturating_sub(view_rect.width * EXTRA_WIDTH_PERCENTAGE / 100)
1230        {
1231            return false;
1232        }
1233    }
1234
1235    children.push(ChildItem {
1236        item: QueueItemWithState {
1237            item: child,
1238            line_index,
1239            start,
1240            placeholder: false,
1241            view_mode,
1242            filtered,
1243        },
1244        depth: max_depth,
1245        pixel_range: (pixel1, pixel2),
1246    });
1247
1248    true
1249}
1250
1251const MIN_VISIBLE_PIXEL_SIZE: u64 = 3;
1252
1253fn enqueue_children<'a>(mut children: Vec<ChildItem<'a>>, queue: &mut Vec<QueueItemWithState<'a>>) {
1254    children.reverse();
1255    let mut last_pixel = u64::MAX;
1256    let mut last_max_depth = 0;
1257    for ChildItem {
1258        item: mut entry,
1259        depth: max_depth,
1260        pixel_range: (pixel1, pixel2),
1261    } in children
1262    {
1263        if last_pixel <= pixel1 + MIN_VISIBLE_PIXEL_SIZE {
1264            if last_max_depth < max_depth {
1265                queue.pop();
1266                entry.placeholder = true;
1267            } else {
1268                if let Some(entry) = queue.last_mut() {
1269                    entry.placeholder = true;
1270                }
1271                continue;
1272            }
1273        };
1274        queue.push(entry);
1275        last_max_depth = max_depth;
1276        last_pixel = pixel2;
1277    }
1278}
1279
1280fn get_line<T: Default>(lines: &mut Vec<T>, i: usize) -> &mut T {
1281    if i >= lines.len() {
1282        lines.resize_with(i + 1, || Default::default());
1283    }
1284    &mut lines[i]
1285}
1286
1287struct LineEntry<'a> {
1288    start: u64,
1289    width: u64,
1290    secondary: u64,
1291    ty: LineEntryType<'a>,
1292}
1293
1294enum LineEntryType<'a> {
1295    Placeholder(Option<FilterMode>),
1296    Span {
1297        span: SpanRef<'a>,
1298        filtered: Option<FilterMode>,
1299    },
1300    SpanGraph(SpanGraphRef<'a>, Option<FilterMode>),
1301    SpanBottomUp(SpanBottomUpRef<'a>, Option<FilterMode>),
1302    SpanBottomUpSpan(SpanRef<'a>, Option<FilterMode>),
1303}