grafos_profile/
span_tree.rs1use alloc::vec::Vec;
7
8use grafos_observe::span::ResourceSpan;
9
10#[derive(Debug, Clone)]
12pub struct SpanTreeNode {
13 pub span: ResourceSpan,
15 pub children: Vec<usize>,
17}
18
19#[derive(Debug, Clone)]
21pub struct SpanTree {
22 pub nodes: Vec<SpanTreeNode>,
24 pub roots: Vec<usize>,
26}
27
28impl SpanTree {
29 pub fn build(spans: &[ResourceSpan]) -> Self {
34 let mut nodes: Vec<SpanTreeNode> = spans
35 .iter()
36 .map(|s| SpanTreeNode {
37 span: s.clone(),
38 children: Vec::new(),
39 })
40 .collect();
41
42 let mut roots = Vec::new();
44
45 for i in 0..nodes.len() {
47 let parent_id = nodes[i].span.parent_span_id;
48 if let Some(pid) = parent_id {
49 if pid.is_valid() {
50 let parent_idx = nodes
52 .iter()
53 .position(|n| n.span.trace_context.span_id == pid);
54 if parent_idx.is_some() {
55 continue;
57 }
58 }
59 }
60 roots.push(i);
62 }
63
64 let mut child_links: Vec<(usize, usize)> = Vec::new();
67 for i in 0..nodes.len() {
68 if roots.contains(&i) {
69 continue;
70 }
71 if let Some(pid) = nodes[i].span.parent_span_id {
72 if pid.is_valid() {
73 if let Some(pi) = nodes
74 .iter()
75 .position(|n| n.span.trace_context.span_id == pid)
76 {
77 child_links.push((pi, i));
78 } else {
79 roots.push(i);
80 }
81 }
82 }
83 }
84
85 for (parent_idx, child_idx) in child_links {
86 nodes[parent_idx].children.push(child_idx);
87 }
88
89 SpanTree { nodes, roots }
90 }
91
92 pub fn total_cost(&self) -> u64 {
94 self.nodes.iter().map(|n| n.span.lease_cost_byte_secs).sum()
95 }
96
97 pub fn dfs(&self) -> Vec<(usize, usize)> {
99 let mut result = Vec::new();
100 for &root_idx in &self.roots {
101 self.dfs_visit(root_idx, 0, &mut result);
102 }
103 result
104 }
105
106 fn dfs_visit(&self, idx: usize, depth: usize, result: &mut Vec<(usize, usize)>) {
107 result.push((depth, idx));
108 for &child_idx in &self.nodes[idx].children {
109 self.dfs_visit(child_idx, depth + 1, result);
110 }
111 }
112
113 pub fn subtree_cost(&self, idx: usize) -> u64 {
115 let mut cost = self.nodes[idx].span.lease_cost_byte_secs;
116 for &child_idx in &self.nodes[idx].children {
117 cost += self.subtree_cost(child_idx);
118 }
119 cost
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use grafos_observe::trace::{SpanId, TraceContext};
127
128 fn test_ctx() -> TraceContext {
129 let mut bytes = [0u8; 24];
130 for (i, b) in bytes.iter_mut().enumerate() {
131 *b = (i as u8).wrapping_add(0x42);
132 }
133 TraceContext::new_root(&bytes)
134 }
135
136 #[test]
137 fn single_root() {
138 let ctx = test_ctx();
139 let span = ResourceSpan::new("root", ctx);
140 let tree = SpanTree::build(&[span]);
141
142 assert_eq!(tree.nodes.len(), 1);
143 assert_eq!(tree.roots.len(), 1);
144 assert_eq!(tree.roots[0], 0);
145 assert!(tree.nodes[0].children.is_empty());
146 }
147
148 #[test]
149 fn parent_child_hierarchy() {
150 let root_ctx = test_ctx();
151 let child_ctx = root_ctx.child(&[0xAA; 8]);
152 let grandchild_ctx = child_ctx.child(&[0xBB; 8]);
153
154 let mut root_span = ResourceSpan::new("root", root_ctx);
155 root_span.lease_cost_byte_secs = 100;
156
157 let mut child_span = ResourceSpan::new("child", child_ctx);
158 child_span.parent_span_id = Some(root_ctx.span_id);
159 child_span.lease_cost_byte_secs = 50;
160
161 let mut grandchild_span = ResourceSpan::new("grandchild", grandchild_ctx);
162 grandchild_span.parent_span_id = Some(child_ctx.span_id);
163 grandchild_span.lease_cost_byte_secs = 25;
164
165 let tree = SpanTree::build(&[root_span, child_span, grandchild_span]);
166
167 assert_eq!(tree.roots.len(), 1);
168 assert_eq!(tree.roots[0], 0);
169 assert_eq!(tree.nodes[0].children, vec![1]);
170 assert_eq!(tree.nodes[1].children, vec![2]);
171 assert!(tree.nodes[2].children.is_empty());
172
173 assert_eq!(tree.total_cost(), 175);
174 assert_eq!(tree.subtree_cost(0), 175);
175 assert_eq!(tree.subtree_cost(1), 75);
176 assert_eq!(tree.subtree_cost(2), 25);
177 }
178
179 #[test]
180 fn orphan_becomes_root() {
181 let ctx1 = test_ctx();
182 let ctx2 = ctx1.child(&[0xCC; 8]);
183
184 let root_span = ResourceSpan::new("root", ctx1);
185
186 let mut orphan = ResourceSpan::new("orphan", ctx2);
188 orphan.parent_span_id = Some(SpanId(0xDEAD));
189
190 let tree = SpanTree::build(&[root_span, orphan]);
191
192 assert_eq!(tree.roots.len(), 2);
193 assert!(tree.roots.contains(&0));
194 assert!(tree.roots.contains(&1));
195 }
196
197 #[test]
198 fn dfs_order() {
199 let root_ctx = test_ctx();
200 let child_a_ctx = root_ctx.child(&[0xAA; 8]);
201 let child_b_ctx = root_ctx.child(&[0xBB; 8]);
202
203 let root = ResourceSpan::new("root", root_ctx);
204
205 let mut child_a = ResourceSpan::new("child_a", child_a_ctx);
206 child_a.parent_span_id = Some(root_ctx.span_id);
207
208 let mut child_b = ResourceSpan::new("child_b", child_b_ctx);
209 child_b.parent_span_id = Some(root_ctx.span_id);
210
211 let tree = SpanTree::build(&[root, child_a, child_b]);
212 let dfs = tree.dfs();
213
214 assert_eq!(dfs.len(), 3);
215 assert_eq!(dfs[0], (0, 0)); assert_eq!(dfs[1], (1, 1)); assert_eq!(dfs[2], (1, 2)); }
219}