fxprof_processed_profile/
stack_table.rs

1use serde::ser::{Serialize, SerializeMap, Serializer};
2
3use crate::fast_hash_map::FastHashMap;
4
5/// The stack table stores the tree of stack nodes of a thread. The shape of the tree is encoded in
6/// the prefix column: Root stack nodes have null as their prefix, and every non-root stack has the
7/// stack index of its "caller" / "parent" as its prefix. Every stack node also has a frame and a
8/// category. A "call stack" is a list of frames. Every stack index in the stack table represents
9/// such a call stack; the "list of frames" is obtained by walking the path in the tree from the
10/// root to the given stack node.
11///
12/// Stacks are used in the thread's samples; each sample refers to a stack index. Stacks can be
13/// shared between samples.
14///
15/// With this representation, every sample only needs to store a single integer to identify the
16/// sample's stack. We take advantage of the fact that many call stacks in the profile have a
17/// shared prefix; storing these stacks as a tree saves a lot of space compared to storing them as
18/// actual lists of frames.
19///
20/// The category of a stack node is always non-null and is derived from a stack's frame and its
21/// prefix. Frames can have null categories, stacks cannot. If a stack's frame has a null category,
22/// the stack inherits the category of its prefix stack. Root stacks whose frame has a null stack
23/// have their category set to the "default category". (The default category is currently defined
24/// as the category in the profile's category list whose color is "grey", and such a category is
25/// required to be present.)
26///
27/// You could argue that the stack table's category column is derived data and as such doesn't need
28/// to be stored in the profile itself. This is true, but storing this information in the stack
29/// table makes it a lot easier to carry it through various transforms that we apply to threads.
30/// For example, here's a case where a stack's category is not recoverable from any other
31/// information in the transformed thread:
32///
33/// In the call path
34///   someJSFunction [JS] -> Node.insertBefore [DOM] -> nsAttrAndChildArray::InsertChildAt,
35///
36/// the stack node for nsAttrAndChildArray::InsertChildAt should inherit the category DOM from its
37/// "Node.insertBefore" prefix stack. And it should keep the DOM category even if you apply the
38/// "Merge node into calling function" transform to Node.insertBefore. This transform removes the
39/// stack node "Node.insertBefore" from the stackTable, so the information about the DOM category
40/// would be lost if it wasn't inherited into the nsAttrAndChildArray::InsertChildAt stack before
41/// transforms are applied.
42#[derive(Debug, Clone, Default)]
43pub struct StackTable {
44    stack_prefixes: Vec<Option<usize>>,
45    stack_frames: Vec<usize>,
46
47    // (parent stack, frame_index) -> stack index
48    index: FastHashMap<(Option<usize>, usize), usize>,
49}
50
51impl StackTable {
52    pub fn new() -> Self {
53        Default::default()
54    }
55
56    pub fn index_for_stack(&mut self, prefix: Option<usize>, frame: usize) -> usize {
57        match self.index.get(&(prefix, frame)) {
58            Some(stack) => *stack,
59            None => {
60                let stack = self.stack_prefixes.len();
61                self.stack_prefixes.push(prefix);
62                self.stack_frames.push(frame);
63                self.index.insert((prefix, frame), stack);
64                stack
65            }
66        }
67    }
68}
69
70impl Serialize for StackTable {
71    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
72        let len = self.stack_prefixes.len();
73        let mut map = serializer.serialize_map(Some(3))?;
74        map.serialize_entry("length", &len)?;
75        map.serialize_entry("prefix", &self.stack_prefixes)?;
76        map.serialize_entry("frame", &self.stack_frames)?;
77        map.end()
78    }
79}