stak_profiler/record/
stack.rs

1use crate::{Error, FRAME_SEPARATOR};
2use core::{
3    fmt::{self, Display, Formatter},
4    str::FromStr,
5};
6
7/// A stack.
8#[derive(Debug, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub struct Stack {
10    frames: Vec<Option<String>>,
11}
12
13impl Stack {
14    /// Creates a stack.
15    pub const fn new(frames: Vec<Option<String>>) -> Self {
16        Self { frames }
17    }
18
19    /// Returns a stack.
20    pub fn frames(&self) -> impl Iterator<Item = Option<&str>> {
21        self.frames.iter().map(Option::as_deref)
22    }
23
24    /// Reverses frames.
25    pub fn reverse_frames(&mut self) {
26        self.frames.reverse();
27    }
28
29    /// Collapses frames.
30    pub fn collapse_frames(&mut self) {
31        let mut frames = vec![];
32
33        for frame in self.frames.drain(..) {
34            if frames.last() != Some(&frame) {
35                frames.push(frame);
36            }
37        }
38
39        self.frames = frames;
40    }
41
42    /// Displays a stack with a fixed local names.
43    pub fn display_local<'a>(&'a self, local_name: &'a str) -> impl Display + 'a {
44        StackDisplay::new(self, local_name)
45    }
46}
47
48impl FromStr for Stack {
49    type Err = Error;
50
51    fn from_str(string: &str) -> Result<Self, Self::Err> {
52        Ok(Self::new(
53            string
54                .split(FRAME_SEPARATOR)
55                .map(|frame| (!frame.is_empty()).then_some(frame.to_owned()))
56                .collect(),
57        ))
58    }
59}
60
61impl Display for Stack {
62    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
63        write!(formatter, "{}", self.display_local(""))
64    }
65}
66
67pub struct StackDisplay<'a> {
68    stack: &'a Stack,
69    local_name: &'a str,
70}
71
72impl<'a> StackDisplay<'a> {
73    pub const fn new(stack: &'a Stack, local_name: &'a str) -> Self {
74        Self { stack, local_name }
75    }
76}
77
78impl Display for StackDisplay<'_> {
79    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
80        let mut first = true;
81
82        for frame in self.stack.frames() {
83            if !first {
84                write!(formatter, "{FRAME_SEPARATOR}")?;
85            }
86
87            first = false;
88
89            write!(formatter, "{}", frame.unwrap_or(self.local_name))?;
90        }
91
92        Ok(())
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99    use pretty_assertions::assert_eq;
100
101    #[test]
102    fn parse() {
103        let stack = Stack::new(vec![Some("foo".into()), Some("bar".into())]);
104
105        assert_eq!(stack.to_string().parse::<Stack>().unwrap(), stack);
106    }
107
108    #[test]
109    fn display_local() {
110        let stack = Stack::new(vec![Some("foo".into()), None]);
111
112        assert_eq!(stack.to_string(), "foo;");
113    }
114
115    mod collapse_frames {
116        use super::*;
117        use pretty_assertions::assert_eq;
118
119        #[test]
120        fn collapse_named_frames() {
121            let mut stack = Stack::new(vec![
122                Some("foo".into()),
123                Some("foo".into()),
124                Some("foo".into()),
125            ]);
126
127            stack.collapse_frames();
128
129            assert_eq!(stack.to_string(), "foo");
130        }
131
132        #[test]
133        fn collapse_anonymous_frames() {
134            let mut stack = Stack::new(vec![None, None]);
135
136            stack.collapse_frames();
137
138            assert_eq!(stack.to_string(), "");
139        }
140
141        #[test]
142        fn collapse_frames_in_middle() {
143            let mut stack = Stack::new(vec![
144                Some("baz".into()),
145                Some("bar".into()),
146                Some("bar".into()),
147                Some("foo".into()),
148            ]);
149
150            stack.collapse_frames();
151
152            assert_eq!(stack.to_string(), "baz;bar;foo");
153        }
154    }
155}