memscope_rs/core/
scope_tracker.rs

1//! Scope tracking functionality for memory analysis
2
3use crate::core::optimized_locks::OptimizedMutex;
4use crate::core::sharded_locks::ShardedRwLock;
5use crate::core::types::*;
6use std::collections::HashMap;
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::sync::{Arc, OnceLock};
9use std::time::{Duration, SystemTime, UNIX_EPOCH};
10
11/// Global scope tracker instance
12static GLOBAL_SCOPE_TRACKER: OnceLock<Arc<ScopeTracker>> = OnceLock::new();
13
14/// Get the global scope tracker instance
15pub fn get_global_scope_tracker() -> Arc<ScopeTracker> {
16    GLOBAL_SCOPE_TRACKER
17        .get_or_init(|| Arc::new(ScopeTracker::new()))
18        .clone()
19}
20
21/// Unique identifier for scopes
22pub type ScopeId = u64;
23
24/// Core scope tracking functionality
25pub struct ScopeTracker {
26    /// Active scopes using sharded storage for better concurrency
27    pub active_scopes: ShardedRwLock<ScopeId, ScopeInfo>,
28    /// Completed scopes for analysis
29    pub completed_scopes: OptimizedMutex<Vec<ScopeInfo>>,
30    /// Scope hierarchy relationships
31    pub scope_hierarchy: OptimizedMutex<ScopeHierarchy>,
32    /// Next available scope ID using atomic counter
33    next_scope_id: AtomicU64,
34    /// Current scope stack per thread using sharded storage
35    pub scope_stack: ShardedRwLock<String, Vec<ScopeId>>,
36}
37
38impl ScopeTracker {
39    /// Create a new scope tracker
40    pub fn new() -> Self {
41        Self {
42            active_scopes: ShardedRwLock::new(),
43            completed_scopes: OptimizedMutex::new(Vec::new()),
44            scope_hierarchy: OptimizedMutex::new(ScopeHierarchy {
45                root_scopes: Vec::new(),
46                scope_tree: HashMap::new(),
47                max_depth: 0,
48                total_scopes: 0,
49                relationships: HashMap::new(),
50                depth_map: HashMap::new(),
51            }),
52            next_scope_id: AtomicU64::new(1),
53            scope_stack: ShardedRwLock::new(),
54        }
55    }
56
57    /// Enter a new scope
58    pub fn enter_scope(&self, name: String) -> TrackingResult<ScopeId> {
59        let scope_id = self.allocate_scope_id();
60        let thread_id = format!("{:?}", std::thread::current().id());
61        let timestamp = current_timestamp();
62
63        // Determine parent scope and depth
64        let (parent_scope, depth) = {
65            if let Some(thread_stack) = self.scope_stack.get(&thread_id) {
66                if let Some(&parent_id) = thread_stack.last() {
67                    if let Some(parent) = self.active_scopes.get(&parent_id) {
68                        (Some(parent.name.clone()), parent.depth + 1)
69                    } else {
70                        (None, 0)
71                    }
72                } else {
73                    (None, 0)
74                }
75            } else {
76                (None, 0)
77            }
78        };
79
80        // Create scope info
81        let scope_info = ScopeInfo {
82            name: name.clone(),
83            parent: parent_scope.clone(),
84            children: Vec::new(),
85            depth,
86            variables: Vec::new(),
87            total_memory: 0,
88            peak_memory: 0,
89            allocation_count: 0,
90            lifetime_start: Some(timestamp as u64),
91            lifetime_end: None,
92            is_active: true,
93            start_time: timestamp as u64,
94            end_time: None,
95            memory_usage: 0,
96            child_scopes: Vec::new(),
97            parent_scope: parent_scope.clone(),
98        };
99
100        // Add to active scopes
101        self.active_scopes.insert(scope_id, scope_info);
102
103        // Update scope stack
104        self.scope_stack.with_shard_write(&thread_id, |shard| {
105            shard.entry(thread_id.clone()).or_default().push(scope_id);
106        });
107
108        // Update hierarchy
109        if let Some(mut hierarchy) = self.scope_hierarchy.try_lock() {
110            hierarchy.depth_map.insert(name.clone(), depth);
111
112            if let Some(parent) = parent_scope.clone() {
113                hierarchy
114                    .relationships
115                    .entry(parent)
116                    .or_default()
117                    .push(name.clone());
118            } else {
119                hierarchy.root_scopes.push(name);
120            }
121        }
122
123        Ok(scope_id)
124    }
125
126    /// Exit a scope
127    pub fn exit_scope(&self, scope_id: ScopeId) -> TrackingResult<()> {
128        let thread_id = format!("{:?}", std::thread::current().id());
129        let timestamp = current_timestamp();
130
131        // Remove from active scopes and get scope info
132        let mut scope_info = self.active_scopes.remove(&scope_id).ok_or_else(|| {
133            TrackingError::InvalidPointer(format!("Invalid scope ID: {scope_id}"))
134        })?;
135
136        // Update end time
137        scope_info.end_time = Some(timestamp as u64);
138        scope_info.lifetime_end = Some(timestamp as u64);
139
140        // Update scope stack
141        self.scope_stack.with_shard_write(&thread_id, |shard| {
142            if let Some(thread_stack) = shard.get_mut(&thread_id) {
143                if let Some(pos) = thread_stack.iter().position(|&id| id == scope_id) {
144                    thread_stack.remove(pos);
145                }
146            }
147        });
148
149        // Add to completed scopes
150        let mut retries = 0;
151        const MAX_RETRIES: u32 = 10;
152        const RETRY_DELAY: Duration = Duration::from_millis(10);
153
154        while retries < MAX_RETRIES {
155            if let Some(mut completed_scopes) = self.completed_scopes.try_lock() {
156                completed_scopes.push(scope_info);
157                break;
158            }
159
160            // If we couldn't get the lock, wait a bit and try again
161            retries += 1;
162            if retries < MAX_RETRIES {
163                std::thread::sleep(RETRY_DELAY);
164            } else {
165                // If we've exhausted our retries, log an error and drop the scope
166                // This is better than silently losing the scope
167                eprintln!(
168                    "Failed to add scope {scope_id} to completed_scopes after {MAX_RETRIES} retries",
169                );
170            }
171        }
172
173        Ok(())
174    }
175
176    /// Associate a variable with the current scope
177    pub fn associate_variable(
178        &self,
179        variable_name: String,
180        memory_size: usize,
181    ) -> TrackingResult<()> {
182        let thread_id = format!("{:?}", std::thread::current().id());
183
184        // Find current scope for this thread
185        let current_scope_id = self
186            .scope_stack
187            .get(&thread_id)
188            .and_then(|stack| stack.last().copied());
189
190        if let Some(scope_id) = current_scope_id {
191            self.active_scopes.with_shard_write(&scope_id, |shard| {
192                if let Some(scope) = shard.get_mut(&scope_id) {
193                    scope.variables.push(variable_name);
194                    scope.memory_usage += memory_size;
195                    scope.peak_memory = scope.peak_memory.max(scope.memory_usage);
196                    scope.allocation_count += 1;
197                }
198            });
199        }
200
201        Ok(())
202    }
203
204    /// Get current scope analysis
205    pub fn get_scope_analysis(&self) -> TrackingResult<ScopeAnalysis> {
206        let mut all_scopes = Vec::new();
207
208        // Collect active scopes from all shards
209        for i in 0..16 {
210            // Default shard count
211            self.active_scopes.with_shard_read(&i, |shard| {
212                all_scopes.extend(shard.values().cloned());
213            });
214        }
215
216        // Add completed scopes
217        if let Some(completed_scopes) = self.completed_scopes.try_lock() {
218            all_scopes.extend(completed_scopes.iter().cloned());
219        }
220
221        let hierarchy = if let Some(hierarchy) = self.scope_hierarchy.try_lock() {
222            hierarchy.clone()
223        } else {
224            // Return default hierarchy if lock fails
225            ScopeHierarchy {
226                root_scopes: Vec::new(),
227                scope_tree: HashMap::new(),
228                max_depth: 0,
229                total_scopes: 0,
230                relationships: HashMap::new(),
231                depth_map: HashMap::new(),
232            }
233        };
234
235        Ok(ScopeAnalysis {
236            total_scopes: all_scopes.len(),
237            active_scopes: all_scopes.iter().filter(|s| s.is_active).count(),
238            max_depth: hierarchy.max_depth,
239            average_lifetime: 1000.0, // Placeholder
240            memory_efficiency: 0.8,   // Placeholder
241            scopes: all_scopes,
242            scope_hierarchy: hierarchy,
243            cross_scope_references: Vec::new(), // Cross-scope reference tracking implementation
244        })
245    }
246
247    /// Get scope lifecycle metrics
248    pub fn get_scope_lifecycle_metrics(&self) -> TrackingResult<Vec<ScopeLifecycleMetrics>> {
249        let metrics = if let Some(completed_scopes) = self.completed_scopes.try_lock() {
250            completed_scopes
251                .iter()
252                .map(|scope| {
253                    let lifetime =
254                        scope.end_time.unwrap_or(current_timestamp() as u64) - scope.start_time;
255                    let efficiency_score = if scope.peak_memory > 0 {
256                        scope.memory_usage as f64 / scope.peak_memory as f64
257                    } else {
258                        1.0
259                    };
260
261                    ScopeLifecycleMetrics {
262                        scope_name: scope.name.clone(),
263                        variable_count: scope.variables.len(),
264                        average_lifetime_ms: lifetime as f64,
265                        total_memory_usage: scope.memory_usage,
266                        peak_memory_usage: scope.peak_memory,
267                        allocation_frequency: 1.0, // Simplified
268                        deallocation_efficiency: efficiency_score,
269                        completed_allocations: scope.allocation_count,
270                        memory_growth_events: 0,
271                        peak_concurrent_variables: scope.variables.len(),
272                        memory_efficiency_ratio: if scope.peak_memory > 0 {
273                            scope.memory_usage as f64 / scope.peak_memory as f64
274                        } else {
275                            1.0
276                        },
277                        ownership_transfer_events: 0,
278                        fragmentation_score: 0.0,
279                        instant_allocations: 0,
280                        short_term_allocations: 0,
281                        medium_term_allocations: 0,
282                        long_term_allocations: 0,
283                        suspected_leaks: 0,
284                        risk_distribution: crate::core::types::RiskDistribution::default(),
285                        scope_metrics: Vec::new(),
286                        type_lifecycle_patterns: Vec::new(),
287                    }
288                })
289                .collect()
290        } else {
291            Vec::new()
292        };
293
294        Ok(metrics)
295    }
296
297    /// Get all scopes (active and completed) for data localization
298    pub fn get_all_scopes(&self) -> Vec<ScopeInfo> {
299        let mut all_scopes = Vec::new();
300
301        // Add active scopes from all shards
302        for i in 0..16 {
303            // Default shard count
304            self.active_scopes.with_shard_read(&i, |shard| {
305                all_scopes.extend(shard.values().cloned());
306            });
307        }
308
309        // Add completed scopes
310        if let Some(completed_scopes) = self.completed_scopes.try_lock() {
311            all_scopes.extend(completed_scopes.iter().cloned());
312        }
313
314        all_scopes
315    }
316
317    /// Allocate a new unique scope ID using atomic operations
318    fn allocate_scope_id(&self) -> ScopeId {
319        self.next_scope_id.fetch_add(1, Ordering::Relaxed)
320    }
321}
322
323impl Default for ScopeTracker {
324    fn default() -> Self {
325        Self::new()
326    }
327}
328
329/// RAII scope guard for automatic scope management
330pub struct ScopeGuard {
331    scope_id: ScopeId,
332    tracker: Arc<ScopeTracker>,
333}
334
335impl ScopeGuard {
336    /// Enter a new scope with automatic cleanup
337    pub fn enter(name: &str) -> TrackingResult<Self> {
338        let tracker = get_global_scope_tracker();
339        let scope_id = tracker.enter_scope(name.to_string())?;
340
341        Ok(Self { scope_id, tracker })
342    }
343
344    /// Get the scope ID
345    pub fn scope_id(&self) -> ScopeId {
346        self.scope_id
347    }
348}
349
350impl Drop for ScopeGuard {
351    fn drop(&mut self) {
352        let _ = self.tracker.exit_scope(self.scope_id);
353    }
354}
355fn current_timestamp() -> u128 {
356    SystemTime::now()
357        .duration_since(UNIX_EPOCH)
358        .map(|d| d.as_millis())
359        .unwrap_or(0)
360}
361
362/// Macro for tracking scopes with automatic cleanup
363#[macro_export]
364macro_rules! track_scope {
365    ($scope_name:expr) => {
366        let _scope_guard = $crate::scope_tracker::ScopeGuard::enter($scope_name)?;
367    };
368    ($scope_name:expr, $block:block) => {{
369        let _scope_guard = $crate::scope_tracker::ScopeGuard::enter($scope_name)?;
370        $block
371    }};
372}
373
374/// Enhanced track_var macro that also associates with current scope
375#[macro_export]
376macro_rules! track_var_with_scope {
377    ($var:ident) => {{
378        // Track the variable normally
379        let result = $crate::_track_var_impl(&$var, stringify!($var));
380
381        // Also associate with current scope
382        if result.is_ok() {
383            if let Some(size) = $crate::Trackable::get_heap_ptr(&$var) {
384                let scope_tracker = $crate::scope_tracker::get_global_scope_tracker();
385                let _ = scope_tracker
386                    .associate_variable(stringify!($var).to_string(), std::mem::size_of_val(&$var));
387            }
388        }
389
390        result
391    }};
392}
393
394#[cfg(test)]
395mod tests {
396    use super::*;
397    use std::{thread, time::Duration};
398
399    #[test]
400    fn test_scope_tracker_creation() {
401        let tracker = ScopeTracker::new();
402        assert_eq!(tracker.next_scope_id.load(Ordering::SeqCst), 1);
403    }
404
405    #[test]
406    fn test_enter_and_exit_scope() {
407        let tracker = ScopeTracker::new();
408        let scope_id = tracker.enter_scope("test_scope".to_string()).unwrap();
409
410        // Check scope is active
411        assert!(tracker.active_scopes.get(&scope_id).is_some());
412
413        // Exit scope
414        tracker.exit_scope(scope_id).unwrap();
415
416        // Check scope is no longer active
417        assert!(tracker.active_scopes.get(&scope_id).is_none());
418    }
419
420    #[test]
421    fn test_scope_hierarchy() {
422        let tracker = ScopeTracker::new();
423
424        // Enter parent scope
425        let parent_id = tracker.enter_scope("parent".to_string()).unwrap();
426
427        // Get the thread ID for checking the scope stack
428        let thread_id = format!("{:?}", std::thread::current().id());
429
430        // Check that parent scope is in the active scopes
431        assert!(tracker.active_scopes.get(&parent_id).is_some());
432
433        // Check that parent scope is in the thread's scope stack
434        let scope_stack = tracker.scope_stack.get(&thread_id).unwrap();
435        assert_eq!(scope_stack.last(), Some(&parent_id));
436
437        // Enter child scope
438        let child_id = tracker.enter_scope("child".to_string()).unwrap();
439
440        // Check that child scope is in the active scopes
441        assert!(tracker.active_scopes.get(&child_id).is_some());
442
443        // Check that child scope is now at the top of the thread's scope stack
444        let scope_stack = tracker.scope_stack.get(&thread_id).unwrap();
445        assert_eq!(scope_stack.last(), Some(&child_id));
446
447        // Check that parent scope is still in the stack
448        assert!(scope_stack.contains(&parent_id));
449
450        // Clean up - exit child scope first
451        tracker.exit_scope(child_id).unwrap();
452
453        // Check that parent scope is now at the top of the stack again
454        let scope_stack = tracker.scope_stack.get(&thread_id).unwrap();
455        assert_eq!(scope_stack.last(), Some(&parent_id));
456
457        // Exit parent scope
458        tracker.exit_scope(parent_id).unwrap();
459
460        // Check that the stack is now empty
461        let scope_stack = tracker.scope_stack.get(&thread_id);
462        assert!(scope_stack.is_none() || scope_stack.unwrap().is_empty());
463    }
464
465    #[test]
466    fn test_variable_association() {
467        let tracker = ScopeTracker::new();
468        let scope_id = tracker.enter_scope("test_scope".to_string()).unwrap();
469
470        // Associate variable
471        tracker
472            .associate_variable("test_var".to_string(), 1024)
473            .unwrap();
474
475        // Check variable was associated
476        let scope = tracker.active_scopes.get(&scope_id).unwrap();
477        assert_eq!(scope.variables.len(), 1);
478        assert_eq!(scope.variables[0], "test_var");
479        assert_eq!(scope.memory_usage, 1024);
480
481        tracker.exit_scope(scope_id).unwrap();
482    }
483
484    #[test]
485    fn test_scope_guard() {
486        // Get a fresh tracker for this test
487        let tracker = Arc::new(ScopeTracker::new());
488
489        // Set this tracker as the global instance for this test
490        let _ = GLOBAL_SCOPE_TRACKER.set(Arc::clone(&tracker));
491
492        let scope_id;
493        let start_time;
494
495        // Use a block to ensure the guard is dropped
496        {
497            let _guard = ScopeGuard::enter("guarded_scope").unwrap();
498            scope_id = _guard.scope_id();
499
500            // Scope should be active
501            assert!(tracker.active_scopes.get(&scope_id).is_some());
502
503            // Check the scope is marked as active in the ScopeInfo
504            let scope = tracker.active_scopes.get(&scope_id).unwrap();
505            assert!(scope.is_active);
506            assert!(scope.end_time.is_none());
507
508            // Save the start time for later comparison
509            start_time = scope.start_time;
510
511            // Associate variable
512            tracker
513                .associate_variable("guard_var".to_string(), 512)
514                .unwrap();
515
516            // Verify the variable was associated
517            let scope = tracker.active_scopes.get(&scope_id).unwrap();
518            assert_eq!(scope.variables.len(), 1);
519            assert_eq!(scope.variables[0], "guard_var");
520            assert!(scope.memory_usage >= 512);
521        } // Guard drops here, scope should exit automatically
522
523        // Scope should no longer be active
524        assert!(tracker.active_scopes.get(&scope_id).is_none());
525
526        // Check that the scope was moved to completed_scopes
527        let completed_scopes = tracker.completed_scopes.lock();
528        assert!(!completed_scopes.is_empty());
529
530        // Find our completed scope
531        let completed_scope = completed_scopes.last().expect("No completed scopes found");
532
533        // Verify the completed scope has the correct properties
534        assert_eq!(completed_scope.name, "guarded_scope");
535        assert!(completed_scope.end_time.is_some(), "End time should be set");
536
537        // Compare the end_time with the saved start_time
538        assert!(
539            completed_scope.end_time.unwrap() >= start_time,
540            "End time should be after start time"
541        );
542
543        assert!(
544            completed_scope.memory_usage >= 512,
545            "Memory usage should be at least 512 bytes"
546        );
547
548        // Note: The is_active flag is not currently updated in exit_scope,
549        // so we won't check it. The scope is considered completed based on end_time being set.
550    }
551
552    #[test]
553    fn test_concurrent_scope_operations() {
554        // Use a single tracker for all threads
555        let tracker = Arc::new(ScopeTracker::new());
556        let num_threads = 5;
557
558        // We'll collect the scope names that were actually created and completed
559        let created_scopes = Arc::new(std::sync::Mutex::new(Vec::new()));
560        let completed_scopes_copy = Arc::new(std::sync::Mutex::new(Vec::new()));
561
562        // Create a barrier to ensure all threads start at the same time
563        let start_barrier = Arc::new(std::sync::Barrier::new(num_threads));
564
565        // Spawn worker threads
566        let handles = (0..num_threads)
567            .map(|i| {
568                let tracker = Arc::clone(&tracker);
569                let start_barrier = start_barrier.clone();
570                let created_scopes = Arc::clone(&created_scopes);
571                let completed_scopes_copy = Arc::clone(&completed_scopes_copy);
572
573                thread::spawn(move || {
574                    // Wait for all threads to be ready
575                    start_barrier.wait();
576
577                    // Each thread creates its own scope
578                    let scope_name = format!("thread_{i}");
579                    let scope_id = tracker.enter_scope(scope_name.clone()).unwrap();
580
581                    // Verify the scope was created
582                    assert!(tracker.active_scopes.get(&scope_id).is_some());
583
584                    // Store the created scope name
585                    created_scopes.lock().unwrap().push(scope_name.clone());
586
587                    // Simulate some work
588                    thread::sleep(Duration::from_millis(10));
589
590                    // Associate a variable
591                    let var_name = format!("var_{i}");
592                    tracker.associate_variable(var_name, 256).unwrap();
593
594                    // Verify the variable was associated
595                    let scope = tracker.active_scopes.get(&scope_id).unwrap();
596                    assert_eq!(scope.variables.len(), 1);
597
598                    // Exit the scope
599                    tracker.exit_scope(scope_id).unwrap();
600
601                    // Store the completed scope name
602                    completed_scopes_copy.lock().unwrap().push(scope_name);
603
604                    // Return the scope ID for verification
605                    scope_id
606                })
607            })
608            .collect::<Vec<_>>();
609
610        // Wait for all threads to complete and collect their results
611        let _: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
612
613        // Get the created and completed scopes
614        let created = created_scopes.lock().unwrap();
615        let completed = completed_scopes_copy.lock().unwrap();
616
617        // Verify all scopes were created and completed
618        assert_eq!(created.len(), num_threads);
619        assert_eq!(completed.len(), num_threads);
620
621        // Get the completed scopes from the tracker
622        let tracker_scopes = tracker.completed_scopes.lock();
623
624        // Verify we have a reasonable number of completed scopes in the tracker
625        // We use >= to account for any potential race conditions where some scopes might be lost
626        // due to lock contention, but we still want to ensure most scopes were processed
627        assert!(
628            !tracker_scopes.is_empty(),
629            "No scopes were completed in the tracker"
630        );
631
632        // Log the number of completed scopes for debugging
633        println!(
634            "Completed {} out of {} scopes in the tracker",
635            tracker_scopes.len(),
636            num_threads
637        );
638
639        // Get the scope names from the tracker
640        let mut tracker_scope_names: Vec<String> =
641            tracker_scopes.iter().map(|s| s.name.clone()).collect();
642
643        // Sort for consistent comparison
644        let mut expected_names = created.clone();
645        expected_names.sort();
646        tracker_scope_names.sort();
647
648        // Verify all expected scopes are in the tracker
649        assert_eq!(
650            expected_names, tracker_scope_names,
651            "Expected scopes in tracker: {expected_names:?}, but found: {tracker_scope_names:?}",
652        );
653
654        // Verify all scopes in the tracker have proper end times and are not active
655        for scope in tracker_scopes.iter() {
656            assert!(
657                scope.end_time.is_some(),
658                "Scope {} has no end time",
659                scope.name
660            );
661            assert!(
662                scope.end_time.unwrap() >= scope.start_time,
663                "Scope {} has end time before start time",
664                scope.name
665            );
666
667            // Verify the scope is in our completed list
668            assert!(
669                completed.contains(&scope.name),
670                "Scope {} not found in completed list",
671                scope.name
672            );
673        }
674    }
675}