micromegas_analytics/
call_tree.rs1use crate::metadata::{StreamMetadata, get_thread_name_from_stream_metadata};
2use crate::scope::BorrowedScopeDesc;
3use crate::scope::ScopeDesc;
4use crate::scope::ScopeHashMap;
5use crate::thread_block_processor::ThreadBlockProcessor;
6use crate::thread_block_processor::parse_thread_block;
7use crate::time::ConvertTicks;
8use anyhow::Result;
9use micromegas_telemetry::blob_storage::BlobStorage;
10use micromegas_telemetry::types::block::BlockMetadata;
11use micromegas_tracing::prelude::*;
12use std::sync::Arc;
13
14#[derive(Debug)]
16pub struct CallTreeNode {
17 pub id: Option<i64>,
19 pub hash: u32,
21 pub begin: i64, pub end: i64,
25 pub children: Vec<CallTreeNode>,
27}
28
29#[derive(Debug)]
31pub struct CallTree {
32 pub scopes: ScopeHashMap,
34 pub call_tree_root: Option<CallTreeNode>,
37}
38
39pub struct CallTreeBuilder {
41 begin_range_ns: i64,
42 end_range_ns: i64,
43 limit: Option<i64>,
44 nb_spans: i64,
45 stack: Vec<CallTreeNode>,
46 scopes: ScopeHashMap,
47 convert_ticks: ConvertTicks,
48 root_hash: u32,
49}
50
51impl CallTreeBuilder {
52 pub fn new(
53 begin_range_ns: i64,
54 end_range_ns: i64,
55 limit: Option<i64>,
56 convert_ticks: ConvertTicks,
57 thread_name: String,
58 ) -> Self {
59 let thread_scope_desc = ScopeDesc::new(
60 Arc::new(thread_name),
61 Arc::new("".to_owned()),
62 Arc::new("".to_owned()),
63 0,
64 );
65 let mut scopes = ScopeHashMap::new();
66 let root_hash = thread_scope_desc.hash;
67 scopes.insert(root_hash, thread_scope_desc);
68 Self {
69 begin_range_ns,
70 end_range_ns,
71 limit,
72 nb_spans: 0,
73 stack: Vec::new(),
74 scopes,
75 convert_ticks,
76 root_hash,
77 }
78 }
79
80 #[span_fn]
81 pub fn finish(mut self) -> CallTree {
82 if self.stack.is_empty() {
83 return CallTree {
84 scopes: self.scopes,
85 call_tree_root: None,
86 };
87 }
88 while self.stack.len() > 1 {
89 let top = self.stack.pop().unwrap();
90 let last_index = self.stack.len() - 1;
91 let parent = &mut self.stack[last_index];
92 parent.children.push(top);
93 }
94 assert_eq!(1, self.stack.len());
95 CallTree {
96 scopes: self.scopes,
97 call_tree_root: self.stack.pop(),
98 }
99 }
100
101 fn add_child_to_top(&mut self, node: CallTreeNode) {
102 if let Some(mut top) = self.stack.pop() {
103 top.children.push(node);
104 self.stack.push(top);
105 } else {
106 let new_root = CallTreeNode {
107 id: None,
108 hash: self.root_hash,
109 begin: self.begin_range_ns,
110 end: self.end_range_ns,
111 children: vec![node],
112 };
113 self.stack.push(new_root);
114 self.nb_spans += 1;
115 }
116 }
117
118 fn record_scope_desc(&mut self, scope_desc: BorrowedScopeDesc<'_>) {
119 self.scopes
122 .entry(scope_desc.hash)
123 .or_insert_with(|| scope_desc.to_owned());
124 }
125}
126
127impl ThreadBlockProcessor for CallTreeBuilder {
128 fn on_begin_thread_scope(
129 &mut self,
130 _block_id: &str,
131 event_id: i64,
132 scope: BorrowedScopeDesc<'_>,
133 ts: i64,
134 ) -> Result<bool> {
135 if self.limit.is_some() && self.nb_spans >= self.limit.unwrap() {
136 return Ok(false);
137 }
138 let time = self.convert_ticks.ticks_to_nanoseconds(ts);
139 if time < self.begin_range_ns {
140 return Ok(true);
141 }
142 if time > self.end_range_ns {
143 return Ok(false);
144 }
145 let hash = scope.hash;
146 self.record_scope_desc(scope);
147 let node = CallTreeNode {
148 id: Some(event_id),
149 hash,
150 begin: time,
151 end: self.end_range_ns,
152 children: Vec::new(),
153 };
154 self.stack.push(node);
155 self.nb_spans += 1;
156 Ok(true) }
158
159 fn on_end_thread_scope(
160 &mut self,
161 _block_id: &str,
162 event_id: i64,
163 scope: BorrowedScopeDesc<'_>,
164 ts: i64,
165 ) -> Result<bool> {
166 let time = self.convert_ticks.ticks_to_nanoseconds(ts);
167 if time < self.begin_range_ns {
168 return Ok(true);
169 }
170 if time > self.end_range_ns {
171 return Ok(false);
172 }
173 let hash = scope.hash;
174 self.record_scope_desc(scope);
175 if let Some(mut old_top) = self.stack.pop() {
176 if old_top.hash == hash {
177 old_top.end = time;
178 self.add_child_to_top(old_top);
179 } else if old_top.hash == self.root_hash {
180 old_top.id = Some(event_id);
181 old_top.hash = hash;
182 old_top.end = time;
183 self.add_child_to_top(old_top);
184 } else {
185 let ending_name = self.scopes.get(&hash).map_or("?", |s| s.name.as_str());
186 let open_name = self
187 .scopes
188 .get(&old_top.hash)
189 .map_or("?", |s| s.name.as_str());
190 anyhow::bail!(
191 "top scope mismatch in block {_block_id}: closing '{ending_name}' but '{open_name}' is open"
192 );
193 }
194 } else {
195 if self.limit.is_some() && self.nb_spans >= self.limit.unwrap() {
196 return Ok(false);
197 }
198 let node = CallTreeNode {
199 id: Some(event_id),
200 hash,
201 begin: self.begin_range_ns,
202 end: time,
203 children: Vec::new(),
204 };
205 self.add_child_to_top(node);
206 }
207 Ok(true)
208 }
209}
210
211#[allow(clippy::cast_precision_loss)]
213#[span_fn]
214pub async fn make_call_tree(
215 blocks: &[BlockMetadata],
216 begin_range_ns: i64,
217 end_range_ns: i64,
218 limit: Option<i64>,
219 blob_storage: Arc<BlobStorage>,
220 convert_ticks: ConvertTicks,
221 stream: &StreamMetadata,
222) -> Result<CallTree> {
223 let mut builder = CallTreeBuilder::new(
224 begin_range_ns,
225 end_range_ns,
226 limit,
227 convert_ticks,
228 get_thread_name_from_stream_metadata(stream)?,
229 );
230 for block in blocks {
231 parse_thread_block(
232 blob_storage.clone(),
233 stream,
234 block.block_id,
235 block.object_offset,
236 &mut builder,
237 )
238 .await?;
239 }
240 Ok(builder.finish())
241}