memscope_rs/capture/backends/
global_tracking.rs1use 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}