Skip to main content

memscope_rs/capture/backends/
global_tracking.rs

1//! Global tracking module for passive memory capture.
2//!
3//! This module provides a unified entry point for automatic memory tracking
4//! across all execution modes (single-thread, multi-thread, async, unsafe/ffi).
5//!
6//! # Design Principles
7//!
8//! - **Merged**: All tracker data stored in a unified GlobalTracker
9//! - **Simplified**: Minimal public API surface
10//! - **Backward Compatible**: Legacy APIs preserved as deprecated wrappers
11//!
12//! # Core API (Recommended)
13//!
14//! ```ignore
15//! use memscope_rs::capture::backends::global_tracking::{init_global_tracking, global_tracker};
16//!
17//! init_global_tracking().unwrap();
18//! let tracker = global_tracker().unwrap();
19//!
20//! tracker.track!(my_variable);
21//! tracker.export_json("output").unwrap();
22//! tracker.export_html("output").unwrap();
23//! ```
24
25use crate::analysis::memory_passport_tracker::{MemoryPassportTracker, PassportTrackerConfig};
26use crate::capture::backends::async_tracker::{
27    register_global as register_async_global, AsyncTracker,
28};
29use crate::core::{MemScopeError, MemScopeResult};
30use crate::tracker::{AnalysisReport, Tracker};
31use std::path::Path;
32use std::sync::Arc;
33use std::time::Instant;
34use tracing::info;
35
36static GLOBAL_TRACKER: std::sync::RwLock<Option<Arc<GlobalTracker>>> = std::sync::RwLock::new(None);
37
38#[derive(Debug, Clone)]
39pub struct TrackerConfig {
40    pub max_allocations: usize,
41    pub enable_statistics: bool,
42}
43
44impl Default for TrackerConfig {
45    fn default() -> Self {
46        Self {
47            max_allocations: 1_000_000,
48            enable_statistics: true,
49        }
50    }
51}
52
53#[derive(Debug, Clone, Default)]
54pub struct GlobalTrackerConfig {
55    pub tracker: TrackerConfig,
56    pub passport: PassportTrackerConfig,
57}
58
59pub struct GlobalTracker {
60    tracker: Tracker,
61    passport_tracker: Arc<MemoryPassportTracker>,
62    async_tracker: Arc<AsyncTracker>,
63    start_time: Instant,
64}
65
66impl std::fmt::Debug for GlobalTracker {
67    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        f.debug_struct("GlobalTracker")
69            .field("start_time", &self.start_time)
70            .finish()
71    }
72}
73
74impl GlobalTracker {
75    pub fn new() -> Self {
76        Self::with_config(GlobalTrackerConfig::default())
77    }
78
79    pub fn with_config(config: GlobalTrackerConfig) -> Self {
80        let tracker = Tracker::new();
81        let passport_tracker = Arc::new(MemoryPassportTracker::new(config.passport));
82        let async_tracker = Arc::new(AsyncTracker::new());
83        async_tracker.set_initialized();
84
85        // Share this async_tracker instance with the async_tracker module
86        // so that spawn_tracked() can register task lifecycle events on it.
87        let _ = register_async_global(async_tracker.clone());
88
89        Self {
90            tracker,
91            passport_tracker,
92            async_tracker,
93            start_time: Instant::now(),
94        }
95    }
96
97    pub fn tracker(&self) -> &Tracker {
98        &self.tracker
99    }
100
101    pub fn passport_tracker(&self) -> &Arc<MemoryPassportTracker> {
102        &self.passport_tracker
103    }
104
105    pub fn async_tracker(&self) -> &Arc<AsyncTracker> {
106        &self.async_tracker
107    }
108
109    pub fn elapsed(&self) -> std::time::Duration {
110        self.start_time.elapsed()
111    }
112
113    pub fn track<T: crate::Trackable>(&self, var: &T) {
114        self.track_as(var, "unknown", "", 0, "");
115    }
116
117    pub fn track_as<T: crate::Trackable>(
118        &self,
119        var: &T,
120        name: &str,
121        file: &str,
122        line: u32,
123        module_path: &str,
124    ) {
125        self.tracker.track_as(var, name, file, line, module_path);
126
127        if let Some(task_id) = AsyncTracker::get_current_task() {
128            let kind = var.track_kind();
129            if let crate::core::types::TrackKind::HeapOwner { ptr, size } = kind {
130                let type_name = var.get_type_name().to_string();
131                self.async_tracker.track_allocation_with_location(
132                    ptr,
133                    size,
134                    task_id,
135                    Some(name.to_string()),
136                    Some(type_name),
137                    None,
138                );
139                // Also update TaskIdRegistry memory stats for the Task Relationship Graph.
140                crate::task_registry::global_registry().record_allocation(size);
141            }
142        }
143
144        let kind = var.track_kind();
145        if let crate::core::types::TrackKind::HeapOwner { ptr, size: _ } = kind {
146            let borrow_analyzer = crate::analysis::borrow_analysis::get_global_borrow_analyzer();
147            let type_name = var.get_type_name();
148            let borrow_type = if type_name.contains("Mutex") || type_name.contains("Cell") {
149                crate::analysis::borrow_analysis::BorrowType::Mutable
150            } else {
151                crate::analysis::borrow_analysis::BorrowType::Immutable
152            };
153            let _ = borrow_analyzer.track_borrow(ptr, borrow_type, name);
154        }
155    }
156
157    pub fn create_passport(
158        &self,
159        ptr: usize,
160        size: usize,
161        context: String,
162    ) -> Result<String, crate::capture::types::TrackingError> {
163        self.passport_tracker
164            .create_passport_simple(ptr, size, context)
165    }
166
167    pub fn record_handover(&self, ptr: usize, context: String, function: String) {
168        let _ = self
169            .passport_tracker
170            .record_handover_to_ffi(ptr, context, function);
171    }
172
173    pub fn record_free(&self, ptr: usize, context: String, function: String) {
174        let _ = self
175            .passport_tracker
176            .record_freed_by_foreign(ptr, context, function);
177    }
178
179    pub fn detect_leaks(&self) -> crate::analysis::memory_passport_tracker::LeakDetectionResult {
180        self.passport_tracker.detect_leaks_at_shutdown()
181    }
182
183    pub fn analyze(&self) -> AnalysisReport {
184        self.tracker.analyze()
185    }
186
187    pub fn get_stats(&self) -> GlobalTrackerStats {
188        let report = self.tracker.analyze();
189        let passport_stats = self.passport_tracker.get_stats();
190        let async_stats = self.async_tracker.get_stats();
191
192        GlobalTrackerStats {
193            total_allocations: report.total_allocations,
194            active_allocations: report.active_allocations,
195            peak_memory_bytes: report.peak_memory_bytes,
196            current_memory_bytes: report.current_memory_bytes,
197            passport_count: passport_stats.total_passports_created,
198            active_passports: passport_stats.active_passports,
199            leaks_detected: passport_stats.leaks_detected,
200            async_task_count: async_stats.total_tasks,
201            active_async_tasks: async_stats.active_tasks,
202            uptime: self.elapsed(),
203        }
204    }
205
206    pub fn export_json<P: AsRef<Path>>(&self, path: P) -> MemScopeResult<()> {
207        use crate::render_engine::export::export_all_json;
208
209        let path = path.as_ref();
210        export_all_json(
211            path,
212            &self.tracker,
213            &self.passport_tracker,
214            &self.async_tracker,
215        )
216        .map_err(|e| MemScopeError::error("global_tracking", "export_json", e.to_string()))?;
217        Ok(())
218    }
219
220    pub fn export_html<P: AsRef<Path>>(&self, path: P) -> MemScopeResult<()> {
221        use crate::render_engine::export::export_dashboard_html_with_async;
222
223        let path = path.as_ref();
224        export_dashboard_html_with_async(
225            path,
226            &self.tracker,
227            &self.passport_tracker,
228            &self.async_tracker,
229        )
230        .map_err(|e| MemScopeError::error("global_tracking", "export_html", e.to_string()))?;
231        Ok(())
232    }
233
234    pub fn export_html_with_template<P: AsRef<Path>>(
235        &self,
236        path: P,
237        template: crate::render_engine::export::DashboardTemplate,
238    ) -> MemScopeResult<()> {
239        use crate::render_engine::export::export_dashboard_html_with_template;
240
241        let path = path.as_ref();
242        export_dashboard_html_with_template(
243            path,
244            &self.tracker,
245            &self.passport_tracker,
246            template,
247            Some(&self.async_tracker),
248        )
249        .map_err(|e| {
250            MemScopeError::error(
251                "global_tracking",
252                "export_html_with_template",
253                e.to_string(),
254            )
255        })?;
256        Ok(())
257    }
258}
259
260impl Default for GlobalTracker {
261    fn default() -> Self {
262        Self::new()
263    }
264}
265
266#[derive(Debug, Clone)]
267pub struct GlobalTrackerStats {
268    pub total_allocations: usize,
269    pub active_allocations: usize,
270    pub peak_memory_bytes: u64,
271    pub current_memory_bytes: u64,
272    pub passport_count: usize,
273    pub active_passports: usize,
274    pub leaks_detected: usize,
275    pub async_task_count: usize,
276    pub active_async_tasks: usize,
277    pub uptime: std::time::Duration,
278}
279
280pub fn init_global_tracking() -> MemScopeResult<()> {
281    let mut guard = GLOBAL_TRACKER.write().map_err(|_| {
282        MemScopeError::error(
283            "global_tracking",
284            "init_global_tracking",
285            "Failed to acquire global tracker lock",
286        )
287    })?;
288
289    if guard.is_some() {
290        return Err(MemScopeError::error(
291            "global_tracking",
292            "init_global_tracking",
293            "Global tracking already initialized",
294        ));
295    }
296
297    *guard = Some(Arc::new(GlobalTracker::new()));
298    info!("Global tracking initialized");
299    Ok(())
300}
301
302pub fn init_global_tracking_with_config(config: GlobalTrackerConfig) -> MemScopeResult<()> {
303    let mut guard = GLOBAL_TRACKER.write().map_err(|_| {
304        MemScopeError::error(
305            "global_tracking",
306            "init_global_tracking_with_config",
307            "Failed to acquire global tracker lock",
308        )
309    })?;
310
311    if guard.is_some() {
312        return Err(MemScopeError::error(
313            "global_tracking",
314            "init_global_tracking_with_config",
315            "Global tracking already initialized",
316        ));
317    }
318
319    *guard = Some(Arc::new(GlobalTracker::with_config(config)));
320    info!("Global tracking initialized with config");
321    Ok(())
322}
323
324pub fn reset_global_tracking() {
325    if let Ok(mut guard) = GLOBAL_TRACKER.write() {
326        *guard = None;
327    }
328}
329
330pub fn is_initialized() -> bool {
331    GLOBAL_TRACKER
332        .read()
333        .map(|guard| guard.is_some())
334        .unwrap_or(false)
335}
336
337pub fn global_tracker() -> MemScopeResult<Arc<GlobalTracker>> {
338    GLOBAL_TRACKER
339        .read()
340        .map(|guard| {
341            guard.as_ref().cloned().ok_or_else(|| {
342                MemScopeError::error(
343                    "global_tracking",
344                    "global_tracker",
345                    "Global tracking not initialized",
346                )
347            })
348        })
349        .map_err(|_| {
350            MemScopeError::error(
351                "global_tracking",
352                "global_tracker",
353                "Failed to acquire global tracker lock",
354            )
355        })?
356}
357
358#[cfg(test)]
359mod tests {
360    use super::*;
361    use std::time::Duration;
362
363    fn reset_global_state() {
364        reset_global_tracking();
365    }
366
367    #[test]
368    fn test_unified_tracker() {
369        let tracker = GlobalTracker::new();
370        assert!(tracker.tracker().analyze().total_allocations == 0);
371
372        let stats = tracker.get_stats();
373        assert_eq!(stats.total_allocations, 0);
374    }
375
376    #[test]
377    fn test_tracker_config_default() {
378        let config = TrackerConfig::default();
379        assert_eq!(config.max_allocations, 1_000_000);
380        assert!(config.enable_statistics);
381    }
382
383    #[test]
384    fn test_tracker_config_clone() {
385        let config = TrackerConfig {
386            max_allocations: 500,
387            enable_statistics: false,
388        };
389        let cloned = config.clone();
390        assert_eq!(cloned.max_allocations, 500);
391        assert!(!cloned.enable_statistics);
392    }
393
394    #[test]
395    fn test_tracker_config_debug() {
396        let config = TrackerConfig::default();
397        let debug_str = format!("{:?}", config);
398        assert!(debug_str.contains("TrackerConfig"));
399        assert!(debug_str.contains("max_allocations"));
400        assert!(debug_str.contains("enable_statistics"));
401    }
402
403    #[test]
404    fn test_global_tracker_config_default() {
405        let config = GlobalTrackerConfig::default();
406        assert_eq!(config.tracker.max_allocations, 1_000_000);
407        assert!(config.tracker.enable_statistics);
408    }
409
410    #[test]
411    fn test_global_tracker_config_clone() {
412        let config = GlobalTrackerConfig {
413            tracker: TrackerConfig {
414                max_allocations: 100,
415                enable_statistics: false,
416            },
417            passport: PassportTrackerConfig::default(),
418        };
419        let cloned = config.clone();
420        assert_eq!(cloned.tracker.max_allocations, 100);
421        assert!(!cloned.tracker.enable_statistics);
422    }
423
424    #[test]
425    fn test_global_tracker_config_debug() {
426        let config = GlobalTrackerConfig::default();
427        let debug_str = format!("{:?}", config);
428        assert!(debug_str.contains("GlobalTrackerConfig"));
429    }
430
431    #[test]
432    fn test_global_tracker_new() {
433        let tracker = GlobalTracker::new();
434        assert!(tracker.elapsed() < Duration::from_secs(1));
435        assert!(tracker.tracker().analyze().total_allocations == 0);
436    }
437
438    #[test]
439    fn test_global_tracker_with_config() {
440        let config = GlobalTrackerConfig {
441            tracker: TrackerConfig {
442                max_allocations: 100,
443                enable_statistics: true,
444            },
445            passport: PassportTrackerConfig::default(),
446        };
447        let tracker = GlobalTracker::with_config(config);
448        assert!(tracker.tracker().analyze().total_allocations == 0);
449    }
450
451    #[test]
452    fn test_global_tracker_default() {
453        let tracker = GlobalTracker::default();
454        assert!(tracker.tracker().analyze().total_allocations == 0);
455    }
456
457    #[test]
458    fn test_global_tracker_debug() {
459        let tracker = GlobalTracker::new();
460        let debug_str = format!("{:?}", tracker);
461        assert!(debug_str.contains("GlobalTracker"));
462        assert!(debug_str.contains("start_time"));
463    }
464
465    #[test]
466    fn test_global_tracker_elapsed() {
467        let tracker = GlobalTracker::new();
468        std::thread::sleep(Duration::from_millis(10));
469        let elapsed = tracker.elapsed();
470        assert!(elapsed >= Duration::from_millis(10));
471    }
472
473    #[test]
474    fn test_global_tracker_tracker_accessor() {
475        let tracker = GlobalTracker::new();
476        let inner_tracker = tracker.tracker();
477        assert!(inner_tracker.analyze().total_allocations == 0);
478    }
479
480    #[test]
481    fn test_global_tracker_passport_tracker_accessor() {
482        let tracker = GlobalTracker::new();
483        let passport = tracker.passport_tracker();
484        let stats = passport.get_stats();
485        assert_eq!(stats.total_passports_created, 0);
486    }
487
488    #[test]
489    fn test_global_tracker_async_tracker_accessor() {
490        let tracker = GlobalTracker::new();
491        let async_tracker = tracker.async_tracker();
492        let stats = async_tracker.get_stats();
493        assert_eq!(stats.total_tasks, 0);
494    }
495
496    #[test]
497    fn test_global_tracker_analyze() {
498        let tracker = GlobalTracker::new();
499        let report = tracker.analyze();
500        assert_eq!(report.total_allocations, 0);
501        assert_eq!(report.active_allocations, 0);
502    }
503
504    #[test]
505    fn test_global_tracker_stats() {
506        let tracker = GlobalTracker::new();
507        let stats = tracker.get_stats();
508        assert_eq!(stats.total_allocations, 0);
509        assert_eq!(stats.active_allocations, 0);
510        assert_eq!(stats.peak_memory_bytes, 0);
511        assert_eq!(stats.current_memory_bytes, 0);
512        assert_eq!(stats.passport_count, 0);
513        assert_eq!(stats.active_passports, 0);
514        assert_eq!(stats.leaks_detected, 0);
515        assert_eq!(stats.async_task_count, 0);
516        assert_eq!(stats.active_async_tasks, 0);
517        assert!(stats.uptime < Duration::from_secs(1));
518    }
519
520    #[test]
521    fn test_global_tracker_stats_debug() {
522        let tracker = GlobalTracker::new();
523        let stats = tracker.get_stats();
524        let debug_str = format!("{:?}", stats);
525        assert!(debug_str.contains("GlobalTrackerStats"));
526        assert!(debug_str.contains("total_allocations"));
527    }
528
529    #[test]
530    fn test_global_tracker_stats_clone() {
531        let tracker = GlobalTracker::new();
532        let stats = tracker.get_stats();
533        let cloned = stats.clone();
534        assert_eq!(cloned.total_allocations, stats.total_allocations);
535        assert_eq!(cloned.active_allocations, stats.active_allocations);
536    }
537
538    #[test]
539    fn test_init_global_tracking_double_init() {
540        reset_global_state();
541        let result1 = init_global_tracking();
542        assert!(result1.is_ok(), "First init should succeed");
543        let result2 = init_global_tracking();
544        assert!(result2.is_err(), "Second init should fail");
545        reset_global_state();
546    }
547
548    #[test]
549    fn test_init_global_tracking_with_config_double_init() {
550        reset_global_state();
551        let config = GlobalTrackerConfig::default();
552        let result1 = init_global_tracking_with_config(config);
553        assert!(result1.is_ok(), "First init should succeed");
554        let config2 = GlobalTrackerConfig::default();
555        let result2 = init_global_tracking_with_config(config2);
556        assert!(result2.is_err(), "Second init should fail");
557        reset_global_state();
558    }
559
560    #[test]
561    fn test_global_tracker_accessor_not_initialized() {
562        reset_global_state();
563        let result = global_tracker();
564        assert!(result.is_err(), "Should fail when not initialized");
565        reset_global_state();
566    }
567
568    #[test]
569    fn test_reset_global_tracking() {
570        reset_global_state();
571        init_global_tracking().unwrap();
572        assert!(is_initialized());
573        reset_global_tracking();
574        assert!(!is_initialized());
575    }
576
577    #[test]
578    fn test_global_tracker_create_passport() {
579        let tracker = GlobalTracker::new();
580        let result = tracker.create_passport(0x1000, 64, "test_context".to_string());
581        assert!(result.is_ok());
582        let passport_id = result.unwrap();
583        assert!(!passport_id.is_empty());
584    }
585
586    #[test]
587    fn test_global_tracker_detect_leaks() {
588        let tracker = GlobalTracker::new();
589        let result = tracker.detect_leaks();
590        assert_eq!(result.total_leaks, 0);
591    }
592
593    #[test]
594    fn test_global_tracker_record_handover() {
595        let tracker = GlobalTracker::new();
596        tracker
597            .create_passport(0x2000, 128, "test".to_string())
598            .unwrap();
599        tracker.record_handover(0x2000, "context".to_string(), "function".to_string());
600    }
601
602    #[test]
603    fn test_global_tracker_record_free() {
604        let tracker = GlobalTracker::new();
605        tracker
606            .create_passport(0x3000, 256, "test".to_string())
607            .unwrap();
608        tracker.record_free(0x3000, "context".to_string(), "function".to_string());
609    }
610
611    #[test]
612    fn test_global_tracker_track_simple() {
613        let tracker = GlobalTracker::new();
614        let value = String::from("test");
615        tracker.track(&value);
616    }
617
618    #[test]
619    fn test_global_tracker_track_as() {
620        let tracker = GlobalTracker::new();
621        let value = String::from("test_value");
622        tracker.track_as(&value, "test_var", "test.rs", 10, "test_module");
623    }
624}