1use core::fmt;
2
3use crate::{CallPathPool, CallPathTiming, FinishedCallTreeProcessor};
4
5pub struct LoggingCallTreeCollector {
6    max_call_depth: usize,
7    left_margin: usize,
8}
9
10pub struct LoggingCallTreeCollectorBuilder {
11    max_call_depth: usize,
12    left_margin: usize,
13}
14
15impl LoggingCallTreeCollectorBuilder {
16    pub fn max_call_depth(mut self, max_call_depth: usize) -> Self {
17        self.max_call_depth = max_call_depth;
18        self
19    }
20
21    pub fn left_margin(mut self, left_margin: usize) -> Self {
22        self.left_margin = left_margin;
23        self
24    }
25
26    pub fn build(self) -> LoggingCallTreeCollector {
27        LoggingCallTreeCollector {
28            max_call_depth: self.max_call_depth,
29            left_margin: self.left_margin,
30        }
31    }
32}
33
34impl Default for LoggingCallTreeCollectorBuilder {
35    fn default() -> Self {
36        LoggingCallTreeCollectorBuilder {
37            max_call_depth: 10,
38            left_margin: 20,
39        }
40    }
41}
42
43impl FinishedCallTreeProcessor for LoggingCallTreeCollector {
44    fn process_finished_call(&self, pool: CallPathPool) {
45        let root = pool.root();
46        tracing::info!(
47            "Call summary of {}@{}:{}\n\n{}",
48            root.static_span_meta().name(),
49            root.static_span_meta().file().unwrap_or("unknown"),
50            root.static_span_meta().line().unwrap_or(0),
51            DisplayableCallPathTiming {
52                max_call_depth: self.max_call_depth,
53                left_margin: self.left_margin,
54                pool: &pool,
55                root
56            }
57        )
58    }
59}
60
61#[derive(Debug)]
62struct DisplayableCallPathTiming<'a> {
63    max_call_depth: usize,
64    left_margin: usize,
65    pool: &'a CallPathPool,
66    root: &'a CallPathTiming,
67}
68
69impl<'a> fmt::Display for DisplayableCallPathTiming<'a> {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        writeln!(
72            f,
73            "{:indent$}    # calls │   ∑ alive ms │    ∑ busy ms │ ∑ own busy ms │ span tree",
74            "",
75            indent = self.left_margin
76        )?;
77        writeln!(
78            f,
79            "{:indent$}────────────┼──────────────┼──────────────┼────────────-──┼───────────────────────",
80            "",
81            indent = self.left_margin
82        )?;
83        let mut last = Vec::with_capacity(self.max_call_depth);
84        last.push(true);
85        self.fmt(&mut last, self.root, f)
86    }
87}
88
89impl DisplayableCallPathTiming<'_> {
90    fn fmt(
91        &self,
92        last: &mut Vec<bool>,
94        node: &CallPathTiming,
95        f: &mut fmt::Formatter<'_>,
96    ) -> fmt::Result {
97        write!(
98            f,
99            "{:indent$}{: >7} {:0>3} ┊ {: >8}.{:0>3} ┊ {: >8}.{:0>3} ┊  {: >8}.{:0>3} ┊ ",
100            "",
101            node.call_count() / 1000,
102            node.call_count() % 1000,
103            node.span_alive().as_micros() / 1000,
104            node.span_alive().as_micros() % 1000,
105            node.sum_with_children().as_micros() / 1000,
106            node.sum_with_children().as_micros() % 1000,
107            node.sum_without_children().as_micros() / 1000,
108            node.sum_without_children().as_micros() % 1000,
109            indent = self.left_margin
110        )?;
111
112        let child_connector = if node.children().next().is_none() {
113            "─"
114        } else {
115            "┬"
116        };
117        match last.len() {
118            1 => writeln!(f, "{} {}", child_connector, node.static_span_meta().name())?,
119            _ => {
120                if last.len() > 2 {
121                    for is_last in last.iter().skip(1).take(last.len() - 2) {
122                        f.write_str(if *is_last { " " } else { "┊" })?;
123                    }
124                }
125
126                let connect_me = if *last.iter().last().unwrap() {
127                    "╰"
128                } else {
129                    "├"
130                };
131                f.write_str(connect_me)?;
132                f.write_str(child_connector)?;
133
134                writeln!(f, " {}", node.static_span_meta().name())?;
135            }
136        };
137
138        let mut children = node.children().copied().collect::<Vec<_>>();
139        if !children.is_empty() {
140            children.sort();
141            let last_dx = children.len() - 1;
142            for (idx, child_idx) in children.iter().enumerate() {
143                let child = &self.pool[*child_idx];
144                last.push(idx == last_dx);
145                self.fmt(last, child, f)?;
146                last.pop();
147            }
148        }
149        Ok(())
150    }
151}
152
153#[cfg(test)]
154mod test {
155    use std::sync::Arc;
156
157    use quanta::Mock;
158
159    use crate::internal::test::{collect_call_trees, compound_call, cooking_party, one_ns};
160
161    #[test]
162    fn display_one_ns() {
163        let str = display_call_trees(|mock| one_ns(&mock));
164        assert_eq!(
165            &str,
166            indoc::indoc! {r#"
167                    # calls │   ∑ alive ms │    ∑ busy ms │ ∑ own busy ms │ span tree
168                ────────────┼──────────────┼──────────────┼────────────-──┼───────────────────────
169                      0 001 ┊        0.000 ┊        0.000 ┊         0.000 ┊ ─ one_ns
170
171            "#},
172            "got:\n{}",
173            str
174        );
175    }
176
177    #[test]
178    fn display_compound_call() {
179        let str = display_call_trees(|mock| compound_call(&mock));
180        assert_eq!(
181            &str,
182            indoc::indoc! {r#"
183                # calls │   ∑ alive ms │    ∑ busy ms │ ∑ own busy ms │ span tree
184            ────────────┼──────────────┼──────────────┼────────────-──┼───────────────────────
185                  0 001 ┊        0.001 ┊        0.001 ┊         0.001 ┊ ┬ compound_call
186                  0 003 ┊        0.000 ┊        0.000 ┊         0.000 ┊ ╰─ one_ns
187      
188            "#},
189            "got:\n{}",
190            str
191        );
192    }
193
194    #[tracing::instrument]
195    fn nest_deeply(mock: &Mock, nest: usize) {
196        if nest == 0 {
197            return;
198        }
199
200        mock.increment(1_000);
201        nest_deeply(mock, nest - 1);
202        mock.increment(1_000_000);
203    }
204
205    #[test]
206    fn display_nest_deeply() {
207        let str = display_call_trees(|mock| nest_deeply(&mock, 11));
208        assert_eq!(
209            &str,
210            indoc::indoc! {r#"
211                # calls │   ∑ alive ms │    ∑ busy ms │ ∑ own busy ms │ span tree
212            ────────────┼──────────────┼──────────────┼────────────-──┼───────────────────────
213                  0 001 ┊       11.011 ┊       11.011 ┊         1.001 ┊ ┬ nest_deeply
214                  0 001 ┊       10.010 ┊       10.010 ┊         1.001 ┊ ╰┬ nest_deeply
215                  0 001 ┊        9.009 ┊        9.009 ┊         1.001 ┊  ╰┬ nest_deeply
216                  0 001 ┊        8.008 ┊        8.008 ┊         1.001 ┊   ╰┬ nest_deeply
217                  0 001 ┊        7.007 ┊        7.007 ┊         1.001 ┊    ╰┬ nest_deeply
218                  0 001 ┊        6.006 ┊        6.006 ┊         1.001 ┊     ╰┬ nest_deeply
219                  0 001 ┊        5.005 ┊        5.005 ┊         1.001 ┊      ╰┬ nest_deeply
220                  0 001 ┊        4.004 ┊        4.004 ┊         1.001 ┊       ╰┬ nest_deeply
221                  0 001 ┊        3.003 ┊        3.003 ┊         1.001 ┊        ╰┬ nest_deeply
222                  0 001 ┊        2.002 ┊        2.002 ┊         2.002 ┊         ╰─ nest_deeply
223            
224            "#},
225            "got:\n{}",
226            str
227        );
228    }
229
230    #[test]
231    fn display_with_futures() {
232        let str = display_call_trees(|mock| {
233            async_std::task::block_on(async {
236                cooking_party(mock).await;
237            });
238        });
239
240        let pattern = indoc::indoc! {r#"
243                # calls │   ∑ alive ms │    ∑ busy ms │ ∑ own busy ms │ span tree
244            ────────────┼──────────────┼──────────────┼────────────-──┼───────────────────────
245                  0 001 ┊      101.XXX ┊      101.XXX ┊       101.XXX ┊ ┬ cooking_party
246                  0 001 ┊        0.03X ┊        0.03X ┊         0.03X ┊ ├─ cook_three
247                  0 001 ┊        0.0X3 ┊        0.0X3 ┊         0.0X3 ┊ ╰─ eat_three
248
249        "#};
250
251        pattern_matches(pattern, &str);
252    }
253
254    fn pattern_matches(pattern: &str, actual: &str) {
255        assert_eq!(
256            pattern.len(),
257            actual.len(),
258            "unexpected length:\n{}",
259            actual
260        );
261
262        let mut all_matches = true;
263        let mut matches = String::new();
264        for (p, a) in pattern.chars().zip(actual.chars()) {
265            if p != 'X' && p != a {
266                all_matches = false;
267                matches += "!";
268            } else {
269                matches.push(p);
270            }
271        }
272
273        assert!(
274            all_matches,
275            "positions at ! didn't match:\n{}\n{}\n",
276            matches, actual
277        );
278    }
279
280    fn display_call_trees(call: impl Fn(Arc<Mock>)) -> String {
281        use std::fmt::Write;
282
283        let call_trees = collect_call_trees(call);
284
285        let mut out = String::new();
286        for call_tree in call_trees {
287            writeln!(
288                &mut out,
289                "{}",
290                super::DisplayableCallPathTiming {
291                    max_call_depth: 10,
292                    left_margin: 0,
293                    pool: &call_tree,
294                    root: call_tree.root()
295                }
296            )
297            .unwrap();
298        }
299        out
300    }
301}