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