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