1use crate::analysis::memory_passport_tracker::{MemoryPassportTracker, PassportTrackerConfig};
26use crate::capture::backends::async_tracker::{
27 register_global as register_async_global, AsyncTracker,
28};
29use crate::core::{MemScopeError, MemScopeResult};
30use crate::tracker::{AnalysisReport, Tracker};
31use std::path::Path;
32use std::sync::Arc;
33use std::time::Instant;
34use tracing::info;
35
36static GLOBAL_TRACKER: std::sync::RwLock<Option<Arc<GlobalTracker>>> = std::sync::RwLock::new(None);
37
38static PROCESS_START: std::sync::OnceLock<Instant> = std::sync::OnceLock::new();
43
44pub fn process_start_elapsed_secs() -> Option<f64> {
49 PROCESS_START.get().map(|t| t.elapsed().as_secs_f64())
50}
51
52#[derive(Debug, Clone)]
53pub struct TrackerConfig {
54 pub max_allocations: usize,
55 pub enable_statistics: bool,
56}
57
58impl Default for TrackerConfig {
59 fn default() -> Self {
60 Self {
61 max_allocations: 1_000_000,
62 enable_statistics: true,
63 }
64 }
65}
66
67#[derive(Debug, Clone, Default)]
68pub struct GlobalTrackerConfig {
69 pub tracker: TrackerConfig,
70 pub passport: PassportTrackerConfig,
71 }
77
78pub struct GlobalTracker {
79 tracker: Tracker,
80 passport_tracker: Arc<MemoryPassportTracker>,
81 async_tracker: Arc<AsyncTracker>,
82 start_time: Instant,
83}
84
85impl std::fmt::Debug for GlobalTracker {
86 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87 f.debug_struct("GlobalTracker")
88 .field("start_time", &self.start_time)
89 .finish()
90 }
91}
92
93impl GlobalTracker {
94 pub fn new() -> Self {
95 Self::with_config(GlobalTrackerConfig::default())
96 }
97
98 pub fn with_config(config: GlobalTrackerConfig) -> Self {
99 let _suppression = crate::core::allocator::TrackingGuard::acquire();
100 let tracker = crate::tracker::allocator_events::global_tracker().shared_clone();
110 let passport_tracker = Arc::new(MemoryPassportTracker::new(config.passport));
111 let async_tracker = Arc::new(AsyncTracker::with_event_store(
112 tracker.event_store().clone(),
113 ));
114 async_tracker.set_initialized();
115
116 let _ = register_async_global(async_tracker.clone());
119
120 Self {
121 tracker,
122 passport_tracker,
123 async_tracker,
124 start_time: Instant::now(),
125 }
126 }
127
128 pub fn tracker(&self) -> &Tracker {
129 &self.tracker
130 }
131
132 pub fn passport_tracker(&self) -> &Arc<MemoryPassportTracker> {
133 &self.passport_tracker
134 }
135
136 pub fn async_tracker(&self) -> &Arc<AsyncTracker> {
137 &self.async_tracker
138 }
139
140 pub fn elapsed(&self) -> std::time::Duration {
141 self.start_time.elapsed()
142 }
143
144 pub fn track<T: crate::Trackable>(&self, var: &T) {
145 self.track_as(var, "unknown", "", 0, "");
146 }
147
148 pub fn track_as<T: crate::Trackable>(
149 &self,
150 var: &T,
151 name: &str,
152 file: &str,
153 line: u32,
154 module_path: &str,
155 ) {
156 self.tracker.track_as(var, name, file, line, module_path);
157
158 let _guard = crate::core::allocator::TrackingGuard::acquire();
172 if let Some(task_id) = AsyncTracker::get_current_task() {
173 let kind = var.track_kind();
174 if let crate::core::types::TrackKind::HeapOwner { ptr, size } = kind {
175 let type_name = var.get_type_name().to_string();
176 self.async_tracker.track_allocation_with_location(
177 ptr,
178 size,
179 task_id,
180 Some(name.to_string()),
181 Some(type_name),
182 None,
183 );
184 }
185 }
186 drop(_guard);
187
188 }
194
195 pub fn create_passport(
196 &self,
197 ptr: usize,
198 size: usize,
199 context: String,
200 ) -> Result<String, crate::capture::types::TrackingError> {
201 self.passport_tracker
202 .create_passport_simple(ptr, size, context)
203 }
204
205 pub fn record_handover(&self, ptr: usize, context: String, function: String) {
206 let _ = self
207 .passport_tracker
208 .record_handover_to_ffi(ptr, context, function);
209 }
210
211 pub fn record_free(&self, ptr: usize, context: String, function: String) {
212 let _ = self
213 .passport_tracker
214 .record_freed_by_foreign(ptr, context, function);
215 }
216
217 pub fn detect_leaks(&self) -> crate::analysis::memory_passport_tracker::LeakDetectionResult {
218 self.passport_tracker.detect_leaks_at_shutdown()
219 }
220
221 pub fn analyze(&self) -> AnalysisReport {
222 self.tracker.analyze()
223 }
224
225 pub fn get_stats(&self) -> GlobalTrackerStats {
226 let _guard = crate::core::allocator::TrackingGuard::acquire();
231 let report = self.tracker.analyze();
232 let passport_stats = self.passport_tracker.get_stats();
233 let events = self.tracker.event_store().snapshot();
234 let task_graph = crate::task_registry::global_registry().export_graph();
235 let rebuilt = crate::capture::backends::rebuild_with_live_tracker(
236 &events,
237 &task_graph,
238 &self.async_tracker,
239 );
240
241 GlobalTrackerStats {
242 total_allocations: report.total_allocations,
243 active_allocations: report.active_allocations,
244 peak_memory_bytes: report.peak_memory_bytes,
245 current_memory_bytes: report.current_memory_bytes,
246 passport_count: passport_stats.total_passports_created,
247 active_passports: passport_stats.active_passports,
248 leaks_detected: passport_stats.leaks_detected,
249 async_task_count: rebuilt.profiles.len(),
250 active_async_tasks: rebuilt
251 .profiles
252 .iter()
253 .filter(|p| !p.is_completed())
254 .count(),
255 uptime: self.elapsed(),
256 }
257 }
258
259 pub fn get_async_profiles_rebuilt(&self) -> Vec<crate::capture::backends::TaskMemoryProfile> {
266 let _guard = crate::core::allocator::TrackingGuard::acquire();
270 let events = self.tracker.event_store().snapshot();
271 let task_graph = crate::task_registry::global_registry().export_graph();
272 let rebuilt = crate::capture::backends::rebuild_with_live_tracker(
273 &events,
274 &task_graph,
275 &self.async_tracker,
276 );
277 rebuilt.profiles
278 }
279
280 pub fn export_json<P: AsRef<Path>>(&self, path: P) -> MemScopeResult<()> {
281 use crate::render_engine::export::export_all_json;
282
283 let path = path.as_ref();
284 export_all_json(
285 path,
286 &self.tracker,
287 &self.passport_tracker,
288 &self.async_tracker,
289 )
290 .map_err(|e| MemScopeError::error("global_tracking", "export_json", e.to_string()))?;
291 Ok(())
292 }
293
294 pub fn export_html<P: AsRef<Path>>(&self, path: P) -> MemScopeResult<()> {
295 use crate::render_engine::export::export_dashboard_html_with_async;
296
297 let path = path.as_ref();
298 export_dashboard_html_with_async(
299 path,
300 &self.tracker,
301 &self.passport_tracker,
302 &self.async_tracker,
303 )
304 .map_err(|e| MemScopeError::error("global_tracking", "export_html", e.to_string()))?;
305 Ok(())
306 }
307
308 pub fn export_html_with_template<P: AsRef<Path>>(
309 &self,
310 path: P,
311 template: crate::render_engine::export::DashboardTemplate,
312 ) -> MemScopeResult<()> {
313 use crate::render_engine::export::export_dashboard_html_with_template;
314
315 let path = path.as_ref();
316 export_dashboard_html_with_template(
317 path,
318 &self.tracker,
319 &self.passport_tracker,
320 template,
321 Some(&self.async_tracker),
322 )
323 .map_err(|e| {
324 MemScopeError::error(
325 "global_tracking",
326 "export_html_with_template",
327 e.to_string(),
328 )
329 })?;
330 Ok(())
331 }
332}
333
334impl Default for GlobalTracker {
335 fn default() -> Self {
336 Self::new()
337 }
338}
339
340#[derive(Debug, Clone)]
341pub struct GlobalTrackerStats {
342 pub total_allocations: usize,
343 pub active_allocations: usize,
344 pub peak_memory_bytes: u64,
345 pub current_memory_bytes: u64,
346 pub passport_count: usize,
347 pub active_passports: usize,
348 pub leaks_detected: usize,
349 pub async_task_count: usize,
350 pub active_async_tasks: usize,
351 pub uptime: std::time::Duration,
352}
353
354pub fn init_global_tracking() -> MemScopeResult<()> {
355 PROCESS_START.get_or_init(Instant::now);
359
360 let mut guard = GLOBAL_TRACKER.write().map_err(|_| {
361 MemScopeError::error(
362 "global_tracking",
363 "init_global_tracking",
364 "Failed to acquire global tracker lock",
365 )
366 })?;
367
368 if guard.is_some() {
369 return Err(MemScopeError::error(
370 "global_tracking",
371 "init_global_tracking",
372 "Global tracking already initialized",
373 ));
374 }
375
376 *guard = Some(Arc::new(GlobalTracker::new()));
377 info!("Global tracking initialized");
378 Ok(())
379}
380
381pub fn init_global_tracking_with_config(config: GlobalTrackerConfig) -> MemScopeResult<()> {
382 PROCESS_START.get_or_init(Instant::now);
384
385 let mut guard = GLOBAL_TRACKER.write().map_err(|_| {
386 MemScopeError::error(
387 "global_tracking",
388 "init_global_tracking_with_config",
389 "Failed to acquire global tracker lock",
390 )
391 })?;
392
393 if guard.is_some() {
394 return Err(MemScopeError::error(
395 "global_tracking",
396 "init_global_tracking_with_config",
397 "Global tracking already initialized",
398 ));
399 }
400
401 *guard = Some(Arc::new(GlobalTracker::with_config(config)));
402 info!("Global tracking initialized with config");
403 Ok(())
404}
405
406pub fn reset_global_tracking() {
407 if let Ok(mut guard) = GLOBAL_TRACKER.write() {
408 *guard = None;
409 }
410}
411
412pub fn is_initialized() -> bool {
413 GLOBAL_TRACKER
414 .read()
415 .map(|guard| guard.is_some())
416 .unwrap_or(false)
417}
418
419pub fn global_tracker() -> MemScopeResult<Arc<GlobalTracker>> {
420 GLOBAL_TRACKER
421 .read()
422 .map(|guard| {
423 guard.as_ref().cloned().ok_or_else(|| {
424 MemScopeError::error(
425 "global_tracking",
426 "global_tracker",
427 "Global tracking not initialized",
428 )
429 })
430 })
431 .map_err(|_| {
432 MemScopeError::error(
433 "global_tracking",
434 "global_tracker",
435 "Failed to acquire global tracker lock",
436 )
437 })?
438}
439
440#[cfg(test)]
441mod tests {
442 use super::*;
443 use serial_test::serial;
446 use std::time::Duration;
447
448 fn reset_global_state() {
449 reset_global_tracking();
450 }
451
452 #[test]
453 fn test_unified_tracker() {
454 let tracker = GlobalTracker::new();
458 let _ = tracker.tracker().analyze().total_allocations;
459 let _ = tracker.get_stats().total_allocations;
460 }
461
462 #[test]
463 fn test_tracker_config_default() {
464 let config = TrackerConfig::default();
465 assert_eq!(config.max_allocations, 1_000_000);
466 assert!(config.enable_statistics);
467 }
468
469 #[test]
470 fn test_tracker_config_clone() {
471 let config = TrackerConfig {
472 max_allocations: 500,
473 enable_statistics: false,
474 };
475 let cloned = config.clone();
476 assert_eq!(cloned.max_allocations, 500);
477 assert!(!cloned.enable_statistics);
478 }
479
480 #[test]
481 fn test_tracker_config_debug() {
482 let config = TrackerConfig::default();
483 let debug_str = format!("{:?}", config);
484 assert!(debug_str.contains("TrackerConfig"));
485 assert!(debug_str.contains("max_allocations"));
486 assert!(debug_str.contains("enable_statistics"));
487 }
488
489 #[test]
490 fn test_global_tracker_config_default() {
491 let config = GlobalTrackerConfig::default();
492 assert_eq!(config.tracker.max_allocations, 1_000_000);
493 assert!(config.tracker.enable_statistics);
494 }
495
496 #[test]
497 fn test_global_tracker_config_clone() {
498 let config = GlobalTrackerConfig {
499 tracker: TrackerConfig {
500 max_allocations: 100,
501 enable_statistics: false,
502 },
503 passport: PassportTrackerConfig::default(),
504 };
505 let cloned = config.clone();
506 assert_eq!(cloned.tracker.max_allocations, 100);
507 assert!(!cloned.tracker.enable_statistics);
508 }
509
510 #[test]
511 fn test_global_tracker_config_debug() {
512 let config = GlobalTrackerConfig::default();
513 let debug_str = format!("{:?}", config);
514 assert!(debug_str.contains("GlobalTrackerConfig"));
515 }
516
517 #[test]
518 fn test_global_tracker_new() {
519 let _guard = crate::core::allocator::TrackingGuard::acquire();
520 let source = crate::tracker::allocator_events::global_tracker().event_store();
521 let before = source.snapshot().len();
522 let tracker = GlobalTracker::new();
523 assert!(
524 Arc::ptr_eq(source, tracker.tracker().event_store()),
525 "facade must share allocator capture"
526 );
527 assert_eq!(
528 source.snapshot().len(),
529 before,
530 "constructing a facade cannot erase process history"
531 );
532 }
533
534 #[test]
535 fn test_global_tracker_with_config() {
536 let config = GlobalTrackerConfig {
537 tracker: TrackerConfig {
538 max_allocations: 100,
539 enable_statistics: true,
540 },
541 passport: PassportTrackerConfig::default(),
542 };
543 let tracker = GlobalTracker::with_config(config);
544 let _ = tracker.tracker().analyze().total_allocations;
546 }
547
548 #[test]
549 fn test_global_tracker_default() {
550 let tracker = GlobalTracker::default();
551 let _ = tracker.tracker().analyze().total_allocations;
553 }
554
555 #[test]
556 fn test_global_tracker_debug() {
557 let tracker = GlobalTracker::new();
558 let debug_str = format!("{:?}", tracker);
559 assert!(debug_str.contains("GlobalTracker"));
560 assert!(debug_str.contains("start_time"));
561 }
562
563 #[test]
564 fn test_global_tracker_elapsed() {
565 let tracker = GlobalTracker::new();
566 std::thread::sleep(Duration::from_millis(10));
567 let elapsed = tracker.elapsed();
568 assert!(elapsed >= Duration::from_millis(10));
569 }
570
571 #[test]
572 fn test_global_tracker_tracker_accessor() {
573 let tracker = GlobalTracker::new();
574 let inner_tracker = tracker.tracker();
575 let _ = inner_tracker.analyze().total_allocations;
577 }
578
579 #[test]
580 fn test_global_tracker_passport_tracker_accessor() {
581 let tracker = GlobalTracker::new();
582 let passport = tracker.passport_tracker();
583 let stats = passport.get_stats();
584 assert_eq!(stats.total_passports_created, 0);
585 }
586
587 #[test]
588 fn test_global_tracker_async_tracker_accessor() {
589 let tracker = GlobalTracker::new();
590 let async_tracker = tracker.async_tracker();
591 let stats = async_tracker.get_stats();
592 assert!(
596 stats.active_tasks <= stats.total_tasks,
597 "the accessor must report a coherent snapshot (active {}, total {})",
598 stats.active_tasks,
599 stats.total_tasks
600 );
601 }
602
603 #[test]
604 fn test_global_tracker_analyze() {
605 let _guard = crate::core::allocator::TrackingGuard::acquire();
606 let tracker = GlobalTracker::new();
607 let data = vec![3u8; 128];
608 tracker.track(&data);
609 let report = tracker.analyze();
610 let recreated = GlobalTracker::new().analyze();
611 assert!(
612 report.total_allocations > 0,
613 "explicit allocation must appear in analysis"
614 );
615 assert_eq!(
616 recreated.total_allocations, report.total_allocations,
617 "recreation preserves totals"
618 );
619 assert_eq!(
620 recreated.active_allocations, report.active_allocations,
621 "recreation preserves live allocations"
622 );
623 }
624
625 #[test]
626 fn test_global_tracker_stats() {
627 let _guard = crate::core::allocator::TrackingGuard::acquire();
628 let tracker = GlobalTracker::new();
629 let stats = tracker.get_stats();
630 let report = tracker.analyze();
631 assert_eq!(
632 stats.total_allocations, report.total_allocations,
633 "stats use shared capture"
634 );
635 assert_eq!(
636 stats.active_allocations, report.active_allocations,
637 "stats and analysis agree on live count"
638 );
639 assert!(
640 stats.peak_memory_bytes >= stats.current_memory_bytes,
641 "peak bounds live bytes"
642 );
643 assert_eq!(stats.passport_count, 0);
644 assert_eq!(stats.active_passports, 0);
645 assert_eq!(stats.leaks_detected, 0);
646 assert!(stats.uptime < Duration::from_secs(1));
647 }
648
649 #[test]
650 fn test_global_tracker_stats_debug() {
651 let tracker = GlobalTracker::new();
652 let stats = tracker.get_stats();
653 let debug_str = format!("{:?}", stats);
654 assert!(debug_str.contains("GlobalTrackerStats"));
655 assert!(debug_str.contains("total_allocations"));
656 }
657
658 #[test]
659 fn test_global_tracker_stats_clone() {
660 let tracker = GlobalTracker::new();
661 let stats = tracker.get_stats();
662 let cloned = stats.clone();
663 assert_eq!(cloned.total_allocations, stats.total_allocations);
664 assert_eq!(cloned.active_allocations, stats.active_allocations);
665 }
666
667 #[test]
668 #[serial]
669 fn test_init_global_tracking_double_init() {
670 reset_global_state();
671 let result1 = init_global_tracking();
672 assert!(result1.is_ok(), "First init should succeed");
673 let result2 = init_global_tracking();
674 assert!(result2.is_err(), "Second init should fail");
675 reset_global_state();
676 }
677
678 #[test]
679 #[serial]
680 fn test_init_global_tracking_with_config_double_init() {
681 reset_global_state();
682 let config = GlobalTrackerConfig::default();
683 let result1 = init_global_tracking_with_config(config);
684 assert!(result1.is_ok(), "First init should succeed");
685 let config2 = GlobalTrackerConfig::default();
686 let result2 = init_global_tracking_with_config(config2);
687 assert!(result2.is_err(), "Second init should fail");
688 reset_global_state();
689 }
690
691 #[test]
692 #[serial]
693 fn test_global_tracker_accessor_not_initialized() {
694 reset_global_state();
695 let result = global_tracker();
696 assert!(result.is_err(), "Should fail when not initialized");
697 reset_global_state();
698 }
699
700 #[test]
701 #[serial]
702 fn test_reset_global_tracking() {
703 reset_global_state();
704 init_global_tracking().unwrap();
705 assert!(is_initialized());
706 reset_global_tracking();
707 assert!(!is_initialized());
708 }
709
710 #[test]
711 fn test_global_tracker_create_passport() {
712 let tracker = GlobalTracker::new();
713 let result = tracker.create_passport(0x1000, 64, "test_context".to_string());
714 assert!(result.is_ok());
715 let passport_id = result.unwrap();
716 assert!(!passport_id.is_empty());
717 }
718
719 #[test]
720 fn test_global_tracker_detect_leaks() {
721 let tracker = GlobalTracker::new();
722 let result = tracker.detect_leaks();
723 assert_eq!(result.total_leaks, 0);
724 }
725
726 #[test]
727 fn test_global_tracker_record_handover() {
728 let tracker = GlobalTracker::new();
729 tracker
730 .create_passport(0x2000, 128, "test".to_string())
731 .unwrap();
732 tracker.record_handover(0x2000, "context".to_string(), "function".to_string());
733 }
734
735 #[test]
736 fn test_global_tracker_record_free() {
737 let tracker = GlobalTracker::new();
738 tracker
739 .create_passport(0x3000, 256, "test".to_string())
740 .unwrap();
741 tracker.record_free(0x3000, "context".to_string(), "function".to_string());
742 }
743
744 #[test]
745 fn test_global_tracker_track_simple() {
746 let tracker = GlobalTracker::new();
747 let value = String::from("test");
748 tracker.track(&value);
749 }
750
751 #[test]
752 fn test_global_tracker_track_as() {
753 let tracker = GlobalTracker::new();
754 let value = String::from("test_value");
755 tracker.track_as(&value, "test_var", "test.rs", 10, "test_module");
756 }
757
758 #[test]
762 fn test_track_as_does_not_fabricate_borrow_event() {
763 let analyzer = crate::analysis::borrow_analysis::get_global_borrow_analyzer();
764 let before = analyzer.get_borrow_statistics().total_borrows;
765
766 let tracker = GlobalTracker::new();
767 let data = vec![1u8; 64];
768 tracker.track_as(&data, "data", file!(), line!(), module_path!());
769
770 let after = analyzer.get_borrow_statistics().total_borrows;
771 assert_eq!(
772 after, before,
773 "tracking a variable must not be recorded as a borrow event"
774 );
775 assert!(
776 tracker.get_stats().total_allocations >= 1,
777 "the allocation itself must still be tracked"
778 );
779 }
780}