1use std::collections::BTreeMap;
10
11use crate::stats::StatsTable;
12
13#[derive(Debug, Clone, Default)]
16pub struct TreeNode {
17 pub children: BTreeMap<String, TreeNode>,
18 pub count: u64,
20 pub bytes: u64,
21 pub rate_hz: f64,
22 pub subtree_count: u64,
24}
25
26#[derive(Debug, Clone, Default)]
28pub struct KeyTreeSnapshot {
29 pub root: TreeNode,
30 pub keys: usize,
31}
32
33impl KeyTreeSnapshot {
34 pub fn build(stats: &StatsTable) -> KeyTreeSnapshot {
37 let mut root = TreeNode::default();
38 for (key, s) in stats.iter() {
39 let mut node = &mut root;
40 node.subtree_count += s.count;
41 for chunk in key.split('/') {
42 node = node.children.entry(chunk.to_string()).or_default();
43 node.subtree_count += s.count;
44 }
45 node.count = s.count;
46 node.bytes = s.bytes;
47 node.rate_hz = s.rate_hz;
48 }
49 KeyTreeSnapshot {
50 root,
51 keys: stats.len(),
52 }
53 }
54
55 pub fn node(&self, path: &[&str]) -> Option<&TreeNode> {
57 let mut node = &self.root;
58 for chunk in path {
59 node = node.children.get(*chunk)?;
60 }
61 Some(node)
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68 use std::time::Instant;
69
70 #[test]
71 fn builds_grouped_counts() {
72 let mut stats = StatsTable::new();
73 let now = Instant::now();
74 stats.record("zs/v1/h-a/telemetry/x/m1", 4, None, now);
75 stats.record("zs/v1/h-a/telemetry/x/m1", 4, None, now);
76 stats.record("zs/v1/h-a/telemetry/x/m2", 4, None, now);
77 stats.record("zs/v1/h-b/state/x/health", 4, None, now);
78
79 let snap = KeyTreeSnapshot::build(&stats);
80 assert_eq!(snap.keys, 3);
81 assert_eq!(snap.root.subtree_count, 4);
82 let telemetry = snap.node(&["zs", "v1", "h-a", "telemetry", "x"]).unwrap();
83 assert_eq!(telemetry.subtree_count, 3);
84 let m1 = snap
85 .node(&["zs", "v1", "h-a", "telemetry", "x", "m1"])
86 .unwrap();
87 assert_eq!(m1.count, 2);
88 assert_eq!(m1.bytes, 8);
89 assert!(snap.node(&["zs", "v1", "h-c"]).is_none());
90 }
91}