memscope_rs/metadata/
scope.rs1use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use std::sync::{Arc, Mutex};
14use tracing::debug;
15
16pub type ScopeId = u64;
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct ScopeInfo {
22 pub name: String,
24 pub parent: Option<String>,
26 pub depth: usize,
28 pub variables: Vec<String>,
30 pub total_memory: usize,
32 pub peak_memory: usize,
34 pub allocation_count: usize,
36 pub lifetime_start: Option<u64>,
38 pub lifetime_end: Option<u64>,
40 pub is_active: bool,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct ScopeHierarchy {
47 pub root_scopes: Vec<String>,
49 pub scope_tree: HashMap<String, Vec<String>>,
51 pub max_depth: usize,
53 pub total_scopes: usize,
55}
56
57#[derive(Debug)]
60struct ScopeData {
61 active_scopes: HashMap<ScopeId, ScopeInfo>,
63 scope_stack: HashMap<String, Vec<ScopeId>>,
65 next_scope_id: u64,
67 hierarchy: ScopeHierarchy,
69}
70
71impl ScopeData {
72 fn new() -> Self {
73 Self {
74 active_scopes: HashMap::new(),
75 scope_stack: HashMap::new(),
76 next_scope_id: 1,
77 hierarchy: ScopeHierarchy {
78 root_scopes: Vec::new(),
79 scope_tree: HashMap::new(),
80 max_depth: 0,
81 total_scopes: 0,
82 },
83 }
84 }
85}
86
87#[derive(Debug)]
89pub struct ScopeTracker {
90 data: Arc<Mutex<ScopeData>>,
92}
93
94impl ScopeTracker {
95 pub fn new() -> Self {
97 debug!("Creating new ScopeTracker");
98 Self {
99 data: Arc::new(Mutex::new(ScopeData::new())),
100 }
101 }
102
103 pub fn enter_scope(&self, name: String) -> ScopeId {
105 let thread_id = format!("{:?}", std::thread::current().id());
106 let timestamp = std::time::SystemTime::now()
107 .duration_since(std::time::UNIX_EPOCH)
108 .unwrap_or_default()
109 .as_nanos() as u64;
110
111 let mut data = self.data.lock().expect("Failed to acquire scope_data lock");
113
114 let scope_id = data.next_scope_id;
116 data.next_scope_id += 1;
117
118 let (parent, depth) = data
120 .scope_stack
121 .get(&thread_id)
122 .and_then(|thread_stack| thread_stack.last())
123 .and_then(|&parent_id| data.active_scopes.get(&parent_id))
124 .map(|active| (Some(active.name.clone()), active.depth + 1))
125 .unwrap_or((None, 0));
126
127 let scope_info = ScopeInfo {
128 name: name.clone(),
129 parent: parent.clone(),
130 depth,
131 variables: Vec::new(),
132 total_memory: 0,
133 peak_memory: 0,
134 allocation_count: 0,
135 lifetime_start: Some(timestamp),
136 lifetime_end: None,
137 is_active: true,
138 };
139
140 data.active_scopes.insert(scope_id, scope_info.clone());
142
143 data.scope_stack
145 .entry(thread_id)
146 .or_default()
147 .push(scope_id);
148
149 if let Some(parent_name) = &parent {
151 data.hierarchy
152 .scope_tree
153 .entry(parent_name.clone())
154 .or_default()
155 .push(name.clone());
156 } else {
157 if !data.hierarchy.root_scopes.contains(&name) {
158 data.hierarchy.root_scopes.push(name.clone());
159 }
160 }
161 data.hierarchy.total_scopes += 1;
162 if depth > data.hierarchy.max_depth {
163 data.hierarchy.max_depth = depth;
164 }
165
166 debug!("Entered scope '{}' with id {}", name, scope_id);
167 scope_id
168 }
169
170 pub fn exit_scope(&self) -> Option<ScopeId> {
172 let thread_id = format!("{:?}", std::thread::current().id());
173 let timestamp = std::time::SystemTime::now()
174 .duration_since(std::time::UNIX_EPOCH)
175 .unwrap_or_default()
176 .as_nanos() as u64;
177
178 let mut data = self.data.lock().expect("Failed to acquire scope_data lock");
179
180 let scope_id = data
181 .scope_stack
182 .get_mut(&thread_id)
183 .and_then(|thread_stack| thread_stack.pop());
184
185 if let Some(scope_id) = scope_id {
186 if let Some(scope) = data.active_scopes.get_mut(&scope_id) {
187 scope.lifetime_end = Some(timestamp);
188 scope.is_active = false;
189 }
190 debug!("Exited scope with id {}", scope_id);
191 Some(scope_id)
192 } else {
193 debug!("No scope to exit for thread {}", thread_id);
194 None
195 }
196 }
197
198 pub fn get_scope(&self, scope_id: ScopeId) -> Option<ScopeInfo> {
200 let data = self.data.lock().expect("Failed to acquire scope_data lock");
201 data.active_scopes.get(&scope_id).cloned()
202 }
203
204 pub fn get_all_scopes(&self) -> Vec<(ScopeId, ScopeInfo)> {
206 let data = self.data.lock().expect("Failed to acquire scope_data lock");
207 data.active_scopes
208 .iter()
209 .map(|(k, v)| (*k, v.clone()))
210 .collect()
211 }
212
213 pub fn get_hierarchy(&self) -> ScopeHierarchy {
215 let data = self.data.lock().expect("Failed to acquire scope_data lock");
216 data.hierarchy.clone()
217 }
218
219 pub fn clear(&self) {
221 let mut data = self.data.lock().expect("Failed to acquire scope_data lock");
222 data.active_scopes.clear();
223 data.scope_stack.clear();
224 data.next_scope_id = 1;
225 data.hierarchy = ScopeHierarchy {
226 root_scopes: Vec::new(),
227 scope_tree: HashMap::new(),
228 max_depth: 0,
229 total_scopes: 0,
230 };
231 debug!("Cleared all scopes");
232 }
233}
234
235impl Default for ScopeTracker {
236 fn default() -> Self {
237 Self::new()
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 #[test]
246 fn test_scope_tracker_creation() {
247 let tracker = ScopeTracker::new();
248 assert_eq!(tracker.get_all_scopes().len(), 0);
249 }
250
251 #[test]
252 fn test_enter_exit_scope() {
253 let tracker = ScopeTracker::new();
254 let scope_id = tracker.enter_scope("test_scope".to_string());
255 assert!(scope_id > 0);
256
257 let scope = tracker.get_scope(scope_id);
258 assert!(scope.is_some());
259 assert!(scope.unwrap().is_active);
260
261 let exited = tracker.exit_scope();
262 assert!(exited.is_some());
263
264 let scope = tracker.get_scope(scope_id);
265 assert!(scope.is_some());
266 assert!(!scope.unwrap().is_active);
267 }
268
269 #[test]
270 fn test_nested_scopes() {
271 let tracker = ScopeTracker::new();
272 let outer_id = tracker.enter_scope("outer".to_string());
273 let inner_id = tracker.enter_scope("inner".to_string());
274
275 let outer = tracker.get_scope(outer_id).unwrap();
276 let inner = tracker.get_scope(inner_id).unwrap();
277
278 assert_eq!(inner.depth, outer.depth + 1);
279 assert_eq!(inner.parent, Some("outer".to_string()));
280
281 tracker.exit_scope();
282 tracker.exit_scope();
283 }
284
285 #[test]
286 fn test_hierarchy() {
287 let tracker = ScopeTracker::new();
288 tracker.enter_scope("root".to_string());
289 tracker.enter_scope("child1".to_string());
290 tracker.exit_scope();
291 tracker.enter_scope("child2".to_string());
292 tracker.exit_scope();
293 tracker.exit_scope();
294
295 let hierarchy = tracker.get_hierarchy();
296 assert_eq!(hierarchy.root_scopes.len(), 1);
297 assert_eq!(hierarchy.total_scopes, 3);
298 }
299}