rspack_allocative/
flamegraph.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is dual-licensed under either the MIT license found in the
5 * LICENSE-MIT file in the root directory of this source tree or the Apache
6 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
7 * of this source tree. You may select, at your option, one of the
8 * above-listed licenses.
9 */
10
11use std::collections::HashMap;
12use std::collections::HashSet;
13use std::collections::hash_map;
14use std::fmt::Write as _;
15use std::mem;
16use std::ops::Index;
17use std::ops::IndexMut;
18
19use crate::Allocative;
20use crate::global_root::roots;
21use crate::key::Key;
22use crate::visitor::NodeKind;
23use crate::visitor::Visitor;
24use crate::visitor::VisitorImpl;
25
26/// Node in flamegraph tree.
27///
28/// Can be written to flamegraph format with [`write`](FlameGraph::write).
29#[derive(Debug, Default, Clone)]
30pub struct FlameGraph {
31    children: HashMap<Key, FlameGraph>,
32    /// Total size of all children, cached.
33    children_size: usize,
34    /// Node size excluding children.
35    node_size: usize,
36}
37
38impl FlameGraph {
39    pub fn total_size(&self) -> usize {
40        self.node_size + self.children_size
41    }
42
43    /// Add another flamegraph to this one.
44    pub fn add(&mut self, other: FlameGraph) {
45        self.node_size += other.node_size;
46        for (key, child) in other.children {
47            self.add_child(key, child);
48        }
49    }
50
51    /// Add a child node to the flamegraph, merging if it already exists.
52    pub fn add_child(&mut self, key: Key, child: FlameGraph) {
53        self.children_size += child.total_size();
54        match self.children.entry(key) {
55            hash_map::Entry::Occupied(mut entry) => {
56                entry.get_mut().add(child);
57            }
58            hash_map::Entry::Vacant(entry) => {
59                entry.insert(child);
60            }
61        }
62    }
63
64    /// Add size to this node.
65    pub fn add_self(&mut self, size: usize) {
66        self.node_size += size;
67    }
68
69    fn write_flame_graph_impl(&self, stack: &[&str], w: &mut String) {
70        if self.node_size != 0 {
71            if !stack.is_empty() {
72                writeln!(w, "{} {}", stack.join(";"), self.node_size).unwrap();
73            } else {
74                // Don't care.
75            }
76        }
77        let mut stack = stack.to_vec();
78        let mut children = Vec::from_iter(self.children.iter());
79        children.sort_by_key(|(key, _)| *key);
80        for (key, child) in children {
81            stack.push(key);
82            child.write_flame_graph_impl(&stack, w);
83            stack.pop().unwrap();
84        }
85    }
86
87    /// Write flamegraph in format suitable for [`flamegraph.pl`] or [inferno].
88    ///
89    /// [flamegraph.pl]: https://github.com/brendangregg/FlameGraph
90    /// [inferno]: https://github.com/jonhoo/inferno
91    pub fn write(&self) -> String {
92        let mut r = String::new();
93        self.write_flame_graph_impl(&[], &mut r);
94        r
95    }
96}
97
98#[derive(Debug)]
99pub struct FlameGraphOutput {
100    flamegraph: FlameGraph,
101    warnings: String,
102}
103
104impl FlameGraphOutput {
105    /// Flamegraph source, can be fed to `flamegraph.pl` or `inferno`.
106    pub fn flamegraph(&self) -> &FlameGraph {
107        &self.flamegraph
108    }
109
110    /// Warnings. Text file in unspecified format.
111    pub fn warnings(&self) -> String {
112        self.warnings.clone()
113    }
114}
115
116#[derive(Default, Eq, PartialEq, Clone, Debug)]
117struct TreeData {
118    /// Size of this node including children but excluding unique/shared children.
119    /// For example for `String` this would be `size_of::<String>()`.
120    size: usize,
121    /// Size excluding children. This value is output to flamegraph for given stack.
122    /// Can be negative if nodes provides sizes incorrectly.
123    rem_size: isize,
124    /// Whether this node is `Box` something.
125    unique: bool,
126    /// Child nodes.
127    children: HashMap<Key, TreeId>,
128}
129
130impl TreeData {
131    fn inline_children_size(&self) -> isize {
132        self.size as isize - self.rem_size
133    }
134}
135
136struct TreeRef<'a> {
137    trees: &'a Trees,
138    tree_id: TreeId,
139}
140
141impl TreeRef<'_> {
142    fn write_flame_graph(&self, stack: &[&str], warnings: &mut String) -> FlameGraph {
143        let mut flame_graph = FlameGraph::default();
144        let tree = &self.trees[self.tree_id];
145        if tree.rem_size > 0 {
146            if stack.is_empty() {
147                // don't care.
148            } else {
149                flame_graph.node_size = tree.rem_size as usize;
150            }
151        } else if tree.rem_size < 0 && !stack.is_empty() {
152            writeln!(
153                warnings,
154                "Incorrect size declaration for node `{}`, size of self: {}, size of inline children: {}",
155                stack.join(";"),
156                tree.size,
157                tree.inline_children_size()
158            )
159                .unwrap();
160        }
161        let mut children: Vec<(&Key, &TreeId)> = Vec::from_iter(&tree.children);
162        let mut stack = stack.to_vec();
163        children.sort_by_key(|(k, _)| *k);
164        for (key, child) in children {
165            stack.push(key);
166            let child = TreeRef {
167                trees: self.trees,
168                tree_id: *child,
169            };
170            let child_framegraph = child.write_flame_graph(&stack, warnings);
171            flame_graph.add_child(key.clone(), child_framegraph);
172            stack.pop().unwrap();
173        }
174        flame_graph
175    }
176
177    fn to_flame_graph(&self) -> (FlameGraph, String) {
178        let mut warnings = String::new();
179        let flame_graph = self.write_flame_graph(&[], &mut warnings);
180        (flame_graph, warnings)
181    }
182}
183
184#[derive(Debug, Eq, PartialEq)]
185struct Tree {
186    trees: Trees,
187    tree_id: TreeId,
188}
189
190impl Tree {
191    fn as_ref(&self) -> TreeRef<'_> {
192        TreeRef {
193            trees: &self.trees,
194            tree_id: self.tree_id,
195        }
196    }
197
198    fn to_flame_graph(&self) -> (FlameGraph, String) {
199        self.as_ref().to_flame_graph()
200    }
201}
202
203#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
204struct TreeId(usize);
205
206#[derive(Default, Clone, Debug, Eq, PartialEq)]
207struct Trees {
208    trees: Vec<TreeData>,
209}
210
211impl Trees {
212    fn new_tree(&mut self) -> TreeId {
213        let id = TreeId(self.trees.len());
214        self.trees.push(TreeData::default());
215        id
216    }
217}
218
219impl Index<TreeId> for Trees {
220    type Output = TreeData;
221
222    fn index(&self, index: TreeId) -> &Self::Output {
223        &self.trees[index.0]
224    }
225}
226
227impl IndexMut<TreeId> for Trees {
228    fn index_mut(&mut self, index: TreeId) -> &mut Self::Output {
229        &mut self.trees[index.0]
230    }
231}
232
233#[derive(Clone, Debug)]
234struct TreeStack {
235    stack: Vec<TreeId>,
236    tree: TreeId,
237}
238
239struct TreeStackRef<'t, 's> {
240    trees: &'t mut Trees,
241    stack: &'s mut TreeStack,
242}
243
244impl<'t> TreeStackRef<'t, '_> {
245    fn current_data(&'t mut self) -> &'t mut TreeData {
246        &mut self.trees[self.stack.tree]
247    }
248
249    fn down(&mut self, key: Key) {
250        self.stack.stack.push(self.stack.tree);
251        let next_tree_id = TreeId(self.trees.trees.len());
252        let child = match self.trees[self.stack.tree].children.entry(key) {
253            hash_map::Entry::Occupied(e) => *e.get(),
254            hash_map::Entry::Vacant(e) => {
255                e.insert(next_tree_id);
256                let child = self.trees.new_tree();
257                assert_eq!(child, next_tree_id);
258                child
259            }
260        };
261        self.stack.tree = child;
262    }
263
264    #[must_use]
265    fn up(&mut self) -> bool {
266        if let Some(pop) = self.stack.stack.pop() {
267            self.stack.tree = pop;
268            true
269        } else {
270            false
271        }
272    }
273}
274
275#[derive(Eq, PartialEq, Hash, Clone, Copy, Debug)]
276struct VisitedSharedPointer(*const ());
277unsafe impl Send for VisitedSharedPointer {}
278
279/// Build a flamegraph from given root objects.
280///
281/// # Example
282///
283/// ```
284/// use allocative::Allocative;
285/// use allocative::FlameGraphBuilder;
286///
287/// #[derive(Allocative)]
288/// struct Foo {
289///     data: String,
290/// };
291///
292/// let foo1 = Foo {
293///     data: "Hello, world!".to_owned(),
294/// };
295/// let foo2 = Foo {
296///     data: "Another message!".to_owned(),
297/// };
298///
299/// let mut flamegraph = FlameGraphBuilder::default();
300/// flamegraph.visit_root(&foo1);
301/// flamegraph.visit_root(&foo2);
302/// let flamegraph_src = flamegraph.finish().flamegraph();
303/// ```
304///
305/// And now `flamegraph_src` can be fed to either [flamegraph.pl] or [inferno].
306///
307/// [flamegraph.pl]: https://github.com/brendangregg/FlameGraph
308/// [inferno]: https://github.com/jonhoo/inferno
309#[derive(Debug)]
310pub struct FlameGraphBuilder {
311    /// Visited shared pointers.
312    visited_shared: HashSet<VisitedSharedPointer>,
313    /// Tree data storage.
314    trees: Trees,
315    /// Current node we are processing in `Visitor`.
316    current: TreeStack,
317    /// Previous stack when entering shared pointer.
318    shared: Vec<TreeStack>,
319    /// Data root.
320    root: TreeId,
321    /// Is root visitor created?
322    entered_root_visitor: bool,
323}
324
325fn _assert_flame_graph_builder_is_send() {
326    fn assert_send<T: Send>() {}
327    assert_send::<FlameGraphBuilder>();
328}
329
330impl Default for FlameGraphBuilder {
331    fn default() -> FlameGraphBuilder {
332        let mut trees = Trees::default();
333        let root = trees.new_tree();
334        FlameGraphBuilder {
335            trees,
336            visited_shared: HashSet::new(),
337            current: TreeStack {
338                stack: Vec::new(),
339                tree: root,
340            },
341            shared: Vec::new(),
342            root,
343            entered_root_visitor: false,
344        }
345    }
346}
347
348impl FlameGraphBuilder {
349    pub fn root_visitor(&mut self) -> Visitor<'_> {
350        assert!(!self.entered_root_visitor);
351        self.entered_root_visitor = true;
352        Visitor {
353            visitor: self,
354            node_kind: NodeKind::Root,
355        }
356    }
357
358    /// Collect tree sizes starting from given root.
359    pub fn visit_root(&mut self, root: &dyn Allocative) {
360        let mut visitor = self.root_visitor();
361        root.visit(&mut visitor);
362        visitor.exit();
363    }
364
365    /// Collect data from global roots registered with
366    /// [`register_root`](crate::register_root).
367    pub fn visit_global_roots(&mut self) {
368        for root in roots() {
369            self.visit_root(root);
370        }
371    }
372
373    fn finish_impl(mut self) -> Tree {
374        assert!(self.shared.is_empty());
375        assert!(self.current.stack.is_empty());
376        assert!(!self.entered_root_visitor);
377        Self::update_sizes(self.root, &mut self.trees);
378        Tree {
379            trees: self.trees,
380            tree_id: self.root,
381        }
382    }
383
384    /// Finish building the flamegraph.
385    pub fn finish(self) -> FlameGraphOutput {
386        let tree = self.finish_impl();
387        let (flamegraph, warnings) = tree.to_flame_graph();
388        FlameGraphOutput {
389            flamegraph,
390            warnings,
391        }
392    }
393
394    /// Finish building the flamegraph and return the flamegraph output.
395    pub fn finish_and_write_flame_graph(self) -> String {
396        self.finish().flamegraph.write()
397    }
398
399    fn update_sizes(tree_id: TreeId, trees: &mut Trees) {
400        let tree = &mut trees[tree_id];
401        for child in tree.children.values().copied().collect::<Vec<_>>() {
402            Self::update_sizes(child, trees);
403        }
404        let tree = &mut trees[tree_id];
405        let children_size = if tree.unique {
406            0
407        } else {
408            let tree = &trees[tree_id];
409            tree.children
410                .values()
411                .map(|child| trees[*child].size)
412                .sum::<usize>()
413        };
414        let tree = &mut trees[tree_id];
415        let size = tree.size;
416        tree.rem_size = (size as isize).saturating_sub(children_size as isize);
417    }
418
419    fn current(&mut self) -> TreeStackRef<'_, '_> {
420        TreeStackRef {
421            trees: &mut self.trees,
422            stack: &mut self.current,
423        }
424    }
425
426    fn exit_impl(&mut self) {
427        assert!(self.entered_root_visitor);
428
429        let up = self.current().up();
430        if !up {
431            if let Some(shared) = self.shared.pop() {
432                self.current = shared;
433                assert!(self.current().up());
434            } else {
435                self.entered_root_visitor = false;
436            }
437        }
438    }
439}
440
441impl VisitorImpl for FlameGraphBuilder {
442    fn enter_inline_impl(&mut self, name: Key, size: usize, _parent: NodeKind) {
443        self.current().down(name);
444        self.current().current_data().size += size;
445    }
446
447    fn enter_unique_impl(&mut self, name: Key, size: usize, _parent: NodeKind) {
448        self.current().down(name);
449        self.current().current_data().size += size;
450        // TODO: deal with potential issue when node is both unique and not.
451        // TODO: record some malloc overhead.
452        self.current().current_data().unique = true;
453    }
454
455    fn enter_shared_impl(
456        &mut self,
457        name: Key,
458        size: usize,
459        ptr: *const (),
460        _parent: NodeKind,
461    ) -> bool {
462        self.current().down(name);
463        self.current().current_data().size += size;
464
465        if !self.visited_shared.insert(VisitedSharedPointer(ptr)) {
466            self.exit_impl();
467            return false;
468        }
469
470        self.shared.push(mem::replace(
471            &mut self.current,
472            TreeStack {
473                stack: Vec::new(),
474                tree: self.root,
475            },
476        ));
477        true
478    }
479
480    fn exit_inline_impl(&mut self) {
481        self.exit_impl();
482    }
483
484    fn exit_unique_impl(&mut self) {
485        self.exit_impl();
486    }
487
488    fn exit_shared_impl(&mut self) {
489        self.exit_impl();
490    }
491
492    fn exit_root_impl(&mut self) {
493        self.exit_impl();
494    }
495}
496
497#[cfg(test)]
498mod tests {
499    use crate::FlameGraph;
500    use crate::flamegraph::FlameGraphBuilder;
501    use crate::flamegraph::Tree;
502    use crate::flamegraph::Trees;
503    use crate::key::Key;
504
505    #[test]
506    fn test_empty() {
507        let mut fg = FlameGraphBuilder::default();
508        fg.root_visitor().exit();
509        let tree = fg.finish_impl();
510
511        let mut expected_trees = Trees::default();
512        let expected_id = expected_trees.new_tree();
513        let expected = Tree {
514            trees: expected_trees,
515            tree_id: expected_id,
516        };
517
518        assert_eq!(expected, tree);
519        assert_eq!("", tree.to_flame_graph().0.write());
520    }
521
522    #[test]
523    fn test_simple() {
524        let mut fg = FlameGraphBuilder::default();
525        fg.root_visitor().visit_simple(Key::new("a"), 10);
526        let tree = fg.finish_impl();
527
528        let mut expected = Trees::default();
529        let expected_root = expected.new_tree();
530        let expected_child = expected.new_tree();
531        expected[expected_root].size = 0;
532        expected[expected_root].rem_size = -10;
533        expected[expected_root]
534            .children
535            .insert(Key::new("a"), expected_child);
536        expected[expected_child].size = 10;
537        expected[expected_child].rem_size = 10;
538        let expected = Tree {
539            trees: expected,
540            tree_id: expected_root,
541        };
542        assert_eq!(expected, tree);
543        assert_eq!("a 10\n", tree.to_flame_graph().0.write());
544    }
545
546    #[test]
547    fn test_unique() {
548        let mut fg = FlameGraphBuilder::default();
549        let mut visitor = fg.root_visitor();
550        let mut s = visitor.enter(Key::new("Struct"), 10);
551        s.visit_simple(Key::new("a"), 3);
552        let mut un = s.enter_unique(Key::new("p"), 6);
553        un.visit_simple(Key::new("x"), 13);
554        un.exit();
555        s.exit();
556        visitor.exit();
557
558        let tree = fg.finish_impl();
559
560        assert_eq!(
561            "\
562                Struct 1\n\
563                Struct;a 3\n\
564                Struct;p 6\n\
565                Struct;p;x 13\n\
566            ",
567            tree.to_flame_graph().0.write(),
568            "{tree:#?}",
569        );
570    }
571
572    #[test]
573    fn test_shared() {
574        let p = 10;
575
576        let mut fg = FlameGraphBuilder::default();
577        let mut visitor = fg.root_visitor();
578
579        for _ in 0..2 {
580            let mut s = visitor.enter(Key::new("Struct"), 10);
581            s.visit_simple(Key::new("a"), 3);
582            {
583                let sh = s.enter_shared(Key::new("p"), 6, &p as *const i32 as *const ());
584                if let Some(mut sh) = sh {
585                    sh.visit_simple(Key::new("Shared"), 13);
586                    sh.exit();
587                }
588            }
589            s.exit();
590        }
591
592        visitor.exit();
593
594        let tree = fg.finish_impl();
595
596        assert_eq!(
597            "\
598            Shared 13\n\
599            Struct 2\n\
600            Struct;a 6\n\
601            Struct;p 12\n\
602        ",
603            tree.to_flame_graph().0.write(),
604            "{tree:#?}",
605        );
606    }
607
608    #[test]
609    fn test_inline_children_too_large() {
610        let mut fg = FlameGraphBuilder::default();
611        let mut visitor = fg.root_visitor();
612        let mut child_visitor = visitor.enter(Key::new("a"), 10);
613        child_visitor.visit_simple(Key::new("b"), 13);
614        child_visitor.exit();
615        visitor.exit();
616        let output = fg.finish();
617        assert_eq!("a;b 13\n", output.flamegraph().write());
618        assert_eq!(
619            "Incorrect size declaration for node `a`, size of self: 10, size of inline children: 13\n",
620            output.warnings()
621        );
622    }
623
624    #[test]
625    fn test_flamegraph_add() {
626        let mut a = FlameGraph::default();
627
628        let mut b_1 = FlameGraph::default();
629        b_1.add_self(10);
630
631        let mut b = FlameGraph::default();
632        b.add_child(Key::new("x"), b_1);
633
634        a.add(b);
635        assert_eq!(10, a.total_size());
636    }
637}