Skip to main content

memscope_rs/metadata/
scope.rs

1//! Scope Tracker - Scope metadata management
2//!
3//! This module provides scope tracking and metadata management
4//! for the MetadataEngine.
5//!
6//! # Concurrency Safety
7//!
8//! All scope-related data is protected by a single `Mutex<ScopeData>` to prevent
9//! deadlock. The lock order is always: `scope_data` (single lock).
10
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use std::sync::{Arc, Mutex};
14use tracing::debug;
15
16/// Unique identifier for scopes
17pub type ScopeId = u64;
18
19/// Scope information
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct ScopeInfo {
22    /// Name of the scope
23    pub name: String,
24    /// Parent scope name (if any)
25    pub parent: Option<String>,
26    /// Depth of this scope in the hierarchy
27    pub depth: usize,
28    /// Variables in this scope
29    pub variables: Vec<String>,
30    /// Total memory usage in this scope
31    pub total_memory: usize,
32    /// Peak memory usage in this scope
33    pub peak_memory: usize,
34    /// Number of allocations in this scope
35    pub allocation_count: usize,
36    /// When this scope was created
37    pub lifetime_start: Option<u64>,
38    /// When this scope was destroyed
39    pub lifetime_end: Option<u64>,
40    /// Whether this scope is currently active
41    pub is_active: bool,
42}
43
44/// Scope hierarchy information
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct ScopeHierarchy {
47    /// Root scopes
48    pub root_scopes: Vec<String>,
49    /// Scope tree relationships
50    pub scope_tree: HashMap<String, Vec<String>>,
51    /// Maximum depth observed
52    pub max_depth: usize,
53    /// Total number of scopes
54    pub total_scopes: usize,
55}
56
57/// Internal data structure protected by a single lock.
58/// This prevents deadlock by ensuring all related data is accessed atomically.
59#[derive(Debug)]
60struct ScopeData {
61    /// Active scopes
62    active_scopes: HashMap<ScopeId, ScopeInfo>,
63    /// Scope stack per thread
64    scope_stack: HashMap<String, Vec<ScopeId>>,
65    /// Next available scope ID
66    next_scope_id: u64,
67    /// Scope hierarchy
68    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/// Scope Tracker - manages scope hierarchy and metadata
88#[derive(Debug)]
89pub struct ScopeTracker {
90    /// All scope data protected by a single lock to prevent deadlock
91    data: Arc<Mutex<ScopeData>>,
92}
93
94impl ScopeTracker {
95    /// Create a new ScopeTracker
96    pub fn new() -> Self {
97        debug!("Creating new ScopeTracker");
98        Self {
99            data: Arc::new(Mutex::new(ScopeData::new())),
100        }
101    }
102
103    /// Enter a new scope
104    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        // Acquire lock once and do all operations atomically
112        let mut data = self.data.lock().expect("Failed to acquire scope_data lock");
113
114        // Generate scope ID
115        let scope_id = data.next_scope_id;
116        data.next_scope_id += 1;
117
118        // Determine parent scope and depth
119        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        // Add to active scopes
141        data.active_scopes.insert(scope_id, scope_info.clone());
142
143        // Push to scope stack
144        data.scope_stack
145            .entry(thread_id)
146            .or_default()
147            .push(scope_id);
148
149        // Update hierarchy
150        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    /// Exit the current scope
171    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    /// Get scope information by ID
199    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    /// Get all scopes
205    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    /// Get scope hierarchy
214    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    /// Clear all scopes
220    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}