1use 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}