tracing_calltree/
display.rs1use crate::snapshot::{CallTreeSnapshot, NodeSnapshot};
2use std::fmt;
3use std::time::Duration;
4
5pub struct SnapshotDisplay<'a>(pub(crate) &'a CallTreeSnapshot);
6
7impl fmt::Display for SnapshotDisplay<'_> {
8 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
9 for (index, root) in self.0.roots.iter().enumerate() {
10 let is_last = index + 1 == self.0.roots.len();
11 write_node(f, root, "", is_last)?;
12 if !is_last {
13 writeln!(f)?;
14 }
15 }
16
17 Ok(())
18 }
19}
20
21impl fmt::Display for CallTreeSnapshot {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 self.display().fmt(f)
24 }
25}
26
27fn write_node(
28 f: &mut fmt::Formatter<'_>,
29 node: &NodeSnapshot,
30 prefix: &str,
31 is_last: bool,
32) -> fmt::Result {
33 let branch = if prefix.is_empty() {
34 ""
35 } else if is_last {
36 "└── "
37 } else {
38 "├── "
39 };
40
41 write!(
42 f,
43 "{prefix}{branch}{:<28} {:>8} avg {:>8} p95 n={}",
44 node.name,
45 format_duration(node.wall.mean),
46 format_duration(node.wall.p95),
47 node.wall.samples,
48 )?;
49
50 if !node.children.is_empty() {
51 let child_prefix = if prefix.is_empty() {
52 String::new()
53 } else if is_last {
54 format!("{prefix} ")
55 } else {
56 format!("{prefix}│ ")
57 };
58
59 for (index, child) in node.children.iter().enumerate() {
60 writeln!(f)?;
61 write_node(f, child, &child_prefix, index + 1 == node.children.len())?;
62 }
63 }
64
65 Ok(())
66}
67
68fn format_duration(duration: Duration) -> String {
69 let nanos = duration.as_nanos();
70
71 if nanos >= 1_000_000_000 {
72 format!("{:.1}s", nanos as f64 / 1_000_000_000.0)
73 } else if nanos >= 1_000_000 {
74 format!("{:.1}ms", nanos as f64 / 1_000_000.0)
75 } else if nanos >= 1_000 {
76 format!("{:.1}us", nanos as f64 / 1_000.0)
77 } else {
78 format!("{nanos}ns")
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use crate::snapshot::{CallTreeSnapshot, NodeSnapshot};
85 use crate::stats::TimingStats;
86 use std::time::Duration;
87
88 #[test]
89 fn renders_tree_display() {
90 let snapshot = CallTreeSnapshot {
91 roots: vec![NodeSnapshot {
92 name: "request".to_string(),
93 target: "app".to_string(),
94 module_path: None,
95 line: None,
96 total_calls: 2,
97 wall: TimingStats {
98 samples: 2,
99 min: Duration::from_millis(8),
100 max: Duration::from_millis(12),
101 mean: Duration::from_millis(10),
102 p95: Duration::from_millis(12),
103 },
104 active: TimingStats {
105 samples: 2,
106 min: Duration::from_millis(4),
107 max: Duration::from_millis(8),
108 mean: Duration::from_millis(6),
109 p95: Duration::from_millis(8),
110 },
111 suspended: TimingStats {
112 samples: 2,
113 min: Duration::from_millis(2),
114 max: Duration::from_millis(4),
115 mean: Duration::from_millis(3),
116 p95: Duration::from_millis(4),
117 },
118 children: vec![],
119 }],
120 };
121
122 let rendered = snapshot.to_string();
123 assert!(rendered.contains("request"));
124 assert!(rendered.contains("10.0ms avg"));
125 assert!(rendered.contains("12.0ms p95 n=2"));
126 }
127}