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            if let Some(ptr) = var.get_heap_ptr() {
116                let size = var.get_size_estimate();
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
208impl Default for GlobalTracker {
209    fn default() -> Self {
210        Self::new()
211    }
212}
213
214#[derive(Debug, Clone)]
215pub struct GlobalTrackerStats {
216    pub total_allocations: usize,
217    pub active_allocations: usize,
218    pub peak_memory_bytes: u64,
219    pub current_memory_bytes: u64,
220    pub passport_count: usize,
221    pub active_passports: usize,
222    pub leaks_detected: usize,
223    pub async_task_count: usize,
224    pub active_async_tasks: usize,
225    pub uptime: std::time::Duration,
226}
227
228pub fn init_global_tracking() -> MemScopeResult<()> {
229    let mut guard = GLOBAL_TRACKER.write().map_err(|_| {
230        MemScopeError::error(
231            "global_tracking",
232            "init_global_tracking",
233            "Failed to acquire global tracker lock",
234        )
235    })?;
236
237    if guard.is_some() {
238        return Err(MemScopeError::error(
239            "global_tracking",
240            "init_global_tracking",
241            "Global tracking already initialized",
242        ));
243    }
244
245    *guard = Some(Arc::new(GlobalTracker::new()));
246    info!("Global tracking initialized");
247    Ok(())
248}
249
250pub fn init_global_tracking_with_config(config: GlobalTrackerConfig) -> MemScopeResult<()> {
251    let mut guard = GLOBAL_TRACKER.write().map_err(|_| {
252        MemScopeError::error(
253            "global_tracking",
254            "init_global_tracking_with_config",
255            "Failed to acquire global tracker lock",
256        )
257    })?;
258
259    if guard.is_some() {
260        return Err(MemScopeError::error(
261            "global_tracking",
262            "init_global_tracking_with_config",
263            "Global tracking already initialized",
264        ));
265    }
266
267    *guard = Some(Arc::new(GlobalTracker::with_config(config)));
268    info!("Global tracking initialized with config");
269    Ok(())
270}
271
272pub fn reset_global_tracking() {
273    if let Ok(mut guard) = GLOBAL_TRACKER.write() {
274        *guard = None;
275    }
276}
277
278pub fn is_initialized() -> bool {
279    GLOBAL_TRACKER
280        .read()
281        .map(|guard| guard.is_some())
282        .unwrap_or(false)
283}
284
285pub fn global_tracker() -> MemScopeResult<Arc<GlobalTracker>> {
286    GLOBAL_TRACKER
287        .read()
288        .map(|guard| {
289            guard.as_ref().cloned().ok_or_else(|| {
290                MemScopeError::error(
291                    "global_tracking",
292                    "global_tracker",
293                    "Global tracking not initialized",
294                )
295            })
296        })
297        .map_err(|_| {
298            MemScopeError::error(
299                "global_tracking",
300                "global_tracker",
301                "Failed to acquire global tracker lock",
302            )
303        })?
304}
305
306#[cfg(test)]
307mod tests {
308    use super::*;
309
310    #[test]
311    fn test_unified_tracker() {
312        let tracker = GlobalTracker::new();
313        assert!(tracker.tracker().analyze().total_allocations == 0);
314
315        let stats = tracker.get_stats();
316        assert_eq!(stats.total_allocations, 0);
317    }
318}