1use crate::capture::system_monitor;
41use crate::core::tracker::MemoryTracker;
42use crate::event_store::{EventStore, MemoryEvent};
43use crate::render_engine::dashboard::renderer::rebuild_allocations_from_events;
44use crate::render_engine::export::{export_snapshot_to_json, ExportJsonOptions};
45use crate::snapshot::MemorySnapshot;
46
47use std::collections::HashMap;
48use std::sync::{Arc, Mutex};
49use std::time::{Duration, Instant};
50
51#[derive(Debug, Clone)]
52pub struct SamplingConfig {
53 pub sample_rate: f64,
54 pub capture_call_stack: bool,
55 pub max_stack_depth: usize,
56}
57
58impl Default for SamplingConfig {
59 fn default() -> Self {
60 Self {
61 sample_rate: 1.0,
62 capture_call_stack: false,
63 max_stack_depth: 10,
64 }
65 }
66}
67
68impl SamplingConfig {
69 pub fn demo() -> Self {
70 Self {
71 sample_rate: 0.1,
72 capture_call_stack: false,
73 max_stack_depth: 5,
74 }
75 }
76
77 pub fn full() -> Self {
78 Self {
79 sample_rate: 1.0,
80 capture_call_stack: true,
81 max_stack_depth: 20,
82 }
83 }
84
85 pub fn high_performance() -> Self {
86 Self {
87 sample_rate: 0.01,
88 capture_call_stack: false,
89 max_stack_depth: 0,
90 }
91 }
92}
93
94#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
95pub struct SystemSnapshot {
96 pub timestamp: u64,
97 pub cpu_usage_percent: f64,
98 pub memory_usage_bytes: u64,
99 pub memory_usage_percent: f64,
100 pub thread_count: usize,
101 pub disk_read_bps: u64,
102 pub disk_write_bps: u64,
103 pub network_rx_bps: u64,
104 pub network_tx_bps: u64,
105 pub gpu_usage_percent: f64,
106 pub gpu_memory_used: u64,
107 pub gpu_memory_total: u64,
108}
109
110#[derive(Debug, Clone)]
111pub struct AnalysisReport {
112 pub total_allocations: usize,
113 pub total_deallocations: usize,
114 pub active_allocations: usize,
115 pub peak_memory_bytes: u64,
116 pub current_memory_bytes: u64,
117 pub allocation_rate_per_sec: f64,
118 pub deallocation_rate_per_sec: f64,
119 pub hotspots: Vec<AllocationHotspot>,
120 pub system_snapshots: Vec<SystemSnapshot>,
121}
122
123#[derive(Debug, Clone)]
124pub struct AllocationHotspot {
125 pub var_name: String,
126 pub type_name: String,
127 pub total_size: usize,
128 pub allocation_count: usize,
129 pub location: Option<String>,
130}
131
132pub struct Tracker {
133 inner: Arc<MemoryTracker>,
134 event_store: Arc<EventStore>,
135 config: Arc<Mutex<TrackerConfig>>,
136 start_time: Instant,
137 system_snapshots: Arc<Mutex<Vec<SystemSnapshot>>>,
138}
139
140impl Clone for Tracker {
141 fn clone(&self) -> Self {
142 Tracker {
143 inner: self.inner.clone(),
144 event_store: self.event_store.clone(),
145 config: self.config.clone(),
146 start_time: Instant::now(), system_snapshots: self.system_snapshots.clone(),
148 }
149 }
150}
151
152#[derive(Debug, Clone)]
153pub struct TrackerConfig {
154 sampling: SamplingConfig,
155 auto_export_on_drop: bool,
156 export_path: Option<String>,
157}
158
159impl Tracker {
160 pub fn new() -> Self {
161 Self {
162 inner: Arc::new(MemoryTracker::new()),
163 event_store: Arc::new(EventStore::new()),
164 config: Arc::new(Mutex::new(TrackerConfig {
165 sampling: SamplingConfig::default(),
166 auto_export_on_drop: false,
167 export_path: None,
168 })),
169 start_time: Instant::now(),
170 system_snapshots: Arc::new(Mutex::new(Vec::new())),
171 }
172 }
173
174 pub fn with_config(config: crate::capture::backends::global_tracking::TrackerConfig) -> Self {
188 tracing::debug!(
189 target: "memscope::tracker",
190 max_allocations = config.max_allocations,
191 enable_statistics = config.enable_statistics,
192 "constructing Tracker from GlobalTrackerConfig.tracker \
193 (currently informational; fields are reserved for future enforcement)",
194 );
195 Self::new()
196 }
197
198 pub fn global() -> Self {
199 use crate::core::tracker::get_tracker;
200 static GLOBAL_EVENT_STORE: std::sync::OnceLock<Arc<EventStore>> =
201 std::sync::OnceLock::new();
202 static GLOBAL_CONFIG: std::sync::OnceLock<Arc<Mutex<TrackerConfig>>> =
203 std::sync::OnceLock::new();
204 static GLOBAL_SYSTEM_SNAPSHOTS: std::sync::OnceLock<Arc<Mutex<Vec<SystemSnapshot>>>> =
205 std::sync::OnceLock::new();
206
207 Self {
208 inner: get_tracker(),
209 event_store: GLOBAL_EVENT_STORE
210 .get_or_init(|| Arc::new(EventStore::new()))
211 .clone(),
212 config: GLOBAL_CONFIG
213 .get_or_init(|| {
214 Arc::new(Mutex::new(TrackerConfig {
215 sampling: SamplingConfig::default(),
216 auto_export_on_drop: false,
217 export_path: None,
218 }))
219 })
220 .clone(),
221 start_time: Instant::now(),
222 system_snapshots: GLOBAL_SYSTEM_SNAPSHOTS
223 .get_or_init(|| Arc::new(Mutex::new(Vec::new())))
224 .clone(),
225 }
226 }
227
228 pub fn with_system_monitoring(self) -> Self {
229 self.capture_system_snapshot();
230 self
231 }
232
233 pub fn with_sampling(self, config: SamplingConfig) -> Self {
234 if let Ok(mut cfg) = self.config.lock() {
235 cfg.sampling = config;
236 }
237 self
238 }
239
240 pub fn with_auto_export(self, path: &str) -> Self {
241 if let Ok(mut cfg) = self.config.lock() {
242 cfg.auto_export_on_drop = true;
243 cfg.export_path = Some(path.to_string());
244 }
245 self
246 }
247
248 pub fn track_as<T: crate::Trackable>(
249 &self,
250 var: &T,
251 name: &str,
252 file: &str,
253 line: u32,
254 module_path: &str,
255 ) {
256 if let Ok(cfg) = self.config.lock() {
257 if cfg.sampling.sample_rate < 1.0 {
258 use std::collections::hash_map::DefaultHasher;
259 use std::hash::{Hash, Hasher};
260 let mut hasher = DefaultHasher::new();
261 let timestamp = std::time::SystemTime::now()
264 .duration_since(std::time::UNIX_EPOCH)
265 .unwrap_or_default()
266 .as_nanos();
267 timestamp.hash(&mut hasher);
268 std::thread::current().id().hash(&mut hasher);
269 name.hash(&mut hasher);
270 file.hash(&mut hasher);
271 line.hash(&mut hasher);
272 let hash = hasher.finish();
273 let threshold = (cfg.sampling.sample_rate * 1000.0) as u64;
274 if (hash % 1000) > threshold {
275 return;
276 }
277 }
278 }
279
280 self.track_inner(var, name, file, line, module_path);
281 }
282
283 #[allow(clippy::too_many_arguments)]
284 pub fn track_clone(
286 &self,
287 source_ptr: usize,
288 target_ptr: usize,
289 size: usize,
290 var_name: Option<String>,
291 type_name: Option<String>,
292 file: &str,
293 line: u32,
294 module_path: &str,
295 ) {
296 let thread_id_u64 = crate::utils::current_thread_id_u64();
297
298 let mut event = crate::event_store::MemoryEvent::clone_event(
299 source_ptr,
300 target_ptr,
301 size,
302 thread_id_u64,
303 var_name,
304 type_name,
305 );
306 event.source_file = Some(file.to_string());
307 event.source_line = Some(line);
308 event.module_path = Some(module_path.to_string());
309
310 self.event_store.record(event);
311 }
312
313 fn track_inner<T: crate::Trackable>(
314 &self,
315 var: &T,
316 name: &str,
317 file: &str,
318 line: u32,
319 module_path: &str,
320 ) {
321 let type_name = var.get_type_name().to_string();
322 let kind = var.track_kind();
323
324 let thread_id_u64 = crate::utils::current_thread_id_u64();
325
326 match kind {
327 crate::core::types::TrackKind::HeapOwner { ptr, size } => {
328 if let Err(e) = self.inner.track_allocation(ptr, size) {
330 tracing::error!("Failed to track allocation at ptr {:x}: {}", ptr, e);
331 return;
332 }
333
334 let mut event = MemoryEvent::allocate(ptr, size, thread_id_u64);
335 event.var_name = Some(name.to_string());
336 event.type_name = Some(type_name.clone());
337 event.source_file = Some(file.to_string());
338 event.source_line = Some(line);
339 event.module_path = Some(module_path.to_string());
340 self.event_store.record(event);
341
342 if let Err(e) = self.inner.associate_var(
343 ptr,
344 name.to_string(),
345 type_name,
346 Some(file),
347 Some(line),
348 ) {
349 tracing::error!("Failed to associate var '{}' at ptr {:x}: {}", name, ptr, e);
350 }
351 }
352 crate::core::types::TrackKind::StackOwner {
353 ptr: stack_ptr,
354 heap_ptr,
355 size,
356 } => {
357 if let Err(e) = self.inner.track_allocation(stack_ptr, size) {
362 tracing::error!("Failed to track allocation at ptr {:x}: {}", stack_ptr, e);
363 return;
364 }
365
366 let mut event = MemoryEvent::allocate(heap_ptr, size, thread_id_u64);
367 event.var_name = Some(name.to_string());
368 event.type_name = Some(type_name.clone());
369 event.source_file = Some(file.to_string());
370 event.source_line = Some(line);
371 event.module_path = Some(module_path.to_string());
372 event.stack_ptr = Some(stack_ptr);
374 self.event_store.record(event);
375
376 if let Err(e) = self.inner.associate_var(
377 heap_ptr,
378 name.to_string(),
379 type_name,
380 Some(file),
381 Some(line),
382 ) {
383 tracing::error!(
384 "Failed to associate var '{}' at ptr {:x}: {}",
385 name,
386 heap_ptr,
387 e
388 );
389 }
390 }
391 crate::core::types::TrackKind::Container | crate::core::types::TrackKind::Value => {
392 let mut event = MemoryEvent::metadata(
395 name.to_string(),
396 type_name,
397 thread_id_u64,
398 var.get_size_estimate(),
399 );
400 event.source_file = Some(file.to_string());
401 event.source_line = Some(line);
402 event.module_path = Some(module_path.to_string());
403 self.event_store.record(event);
404 }
405 }
406 }
407
408 pub fn track_deallocation(&self, ptr: usize) -> crate::TrackingResult<bool> {
409 let size = self.inner.get_allocation_size(ptr).unwrap_or(0);
410
411 let result = self.inner.track_deallocation(ptr)?;
412
413 if result {
415 let thread_id_u64 = crate::utils::current_thread_id_u64();
416
417 let event = MemoryEvent::deallocate(ptr, size, thread_id_u64);
418 self.event_store.record(event);
419 }
420
421 Ok(result)
422 }
423
424 pub fn events(&self) -> Vec<MemoryEvent> {
425 self.event_store.snapshot()
426 }
427
428 pub fn event_store(&self) -> &Arc<EventStore> {
429 &self.event_store
430 }
431
432 fn capture_system_snapshot(&self) {
433 let snapshot = SystemSnapshot {
434 timestamp: std::time::SystemTime::now()
435 .duration_since(std::time::UNIX_EPOCH)
436 .unwrap_or_default()
437 .as_millis() as u64,
438 cpu_usage_percent: system_monitor::cpu_usage(),
439 memory_usage_bytes: system_monitor::memory_used(),
440 memory_usage_percent: system_monitor::memory_usage_percent(),
441 thread_count: system_monitor::thread_count(),
442 disk_read_bps: system_monitor::disk_read_bps(),
443 disk_write_bps: system_monitor::disk_write_bps(),
444 network_rx_bps: system_monitor::network_rx_bps(),
445 network_tx_bps: system_monitor::network_tx_bps(),
446 gpu_usage_percent: system_monitor::gpu_memory_usage_percent(),
447 gpu_memory_used: system_monitor::gpu_memory_used(),
448 gpu_memory_total: system_monitor::gpu_memory_total(),
449 };
450
451 if let Ok(mut snapshots) = self.system_snapshots.lock() {
452 snapshots.push(snapshot);
453 }
454 }
455
456 pub fn stats(&self) -> crate::core::types::MemoryStats {
457 let stats = self.inner.get_stats().unwrap_or_default();
458 crate::core::types::MemoryStats {
459 total_allocations: stats.total_allocations as usize,
460 total_allocated: stats.total_allocated as usize,
461 active_allocations: stats.active_allocations,
462 active_memory: stats.active_memory as usize,
463 peak_allocations: stats.peak_allocations,
464 peak_memory: stats.peak_memory as usize,
465 total_deallocations: stats.total_deallocations as usize,
466 total_deallocated: stats.total_deallocated as usize,
467 leaked_allocations: stats.leaked_allocations,
468 leaked_memory: stats.leaked_memory as usize,
469 ..Default::default()
470 }
471 }
472
473 pub fn analyze(&self) -> AnalysisReport {
474 let stats = self.stats();
475 let events = self.event_store().snapshot();
476 let allocations = rebuild_allocations_from_events(&events);
477 let elapsed = self.start_time.elapsed().as_secs_f64();
478
479 let current_memory: usize = allocations.iter().map(|a| a.size).sum();
480 let peak_memory = stats.peak_memory.max(current_memory);
481
482 let mut hotspot_map: HashMap<String, (String, usize, usize)> = HashMap::new();
483 for alloc in &allocations {
484 if let Some(ref var_name) = alloc.var_name {
485 let key = var_name.clone();
486 let entry = hotspot_map.entry(key).or_insert((
487 alloc.type_name.clone().unwrap_or_default(),
488 0,
489 0,
490 ));
491 entry.1 += alloc.size;
492 entry.2 += 1;
493 }
494 }
495
496 let hotspots: Vec<AllocationHotspot> = hotspot_map
497 .into_iter()
498 .map(
499 |(var_name, (type_name, total_size, count))| AllocationHotspot {
500 var_name,
501 type_name,
502 total_size,
503 allocation_count: count,
504 location: None,
505 },
506 )
507 .collect();
508
509 let system_snapshots = self
510 .system_snapshots
511 .lock()
512 .unwrap_or_else(|e| e.into_inner())
513 .clone();
514
515 AnalysisReport {
516 total_allocations: stats.total_allocations,
517 total_deallocations: stats.total_deallocations,
518 active_allocations: allocations.len(),
519 peak_memory_bytes: peak_memory as u64,
520 current_memory_bytes: current_memory as u64,
521 allocation_rate_per_sec: if elapsed > 0.0 {
522 stats.total_allocations as f64 / elapsed
523 } else {
524 0.0
525 },
526 deallocation_rate_per_sec: if elapsed > 0.0 {
527 stats.total_deallocations as f64 / elapsed
528 } else {
529 0.0
530 },
531 hotspots,
532 system_snapshots,
533 }
534 }
535
536 pub fn inner(&self) -> &Arc<MemoryTracker> {
537 &self.inner
538 }
539
540 pub fn elapsed(&self) -> Duration {
541 self.start_time.elapsed()
542 }
543
544 pub fn system_snapshots(&self) -> Vec<SystemSnapshot> {
545 self.system_snapshots
546 .lock()
547 .unwrap_or_else(|e| e.into_inner())
548 .clone()
549 }
550
551 pub fn current_system_snapshot(&self) -> SystemSnapshot {
552 SystemSnapshot {
553 timestamp: std::time::SystemTime::now()
554 .duration_since(std::time::UNIX_EPOCH)
555 .unwrap_or_default()
556 .as_millis() as u64,
557 cpu_usage_percent: system_monitor::cpu_usage(),
558 memory_usage_bytes: system_monitor::memory_used(),
559 memory_usage_percent: system_monitor::memory_usage_percent(),
560 thread_count: system_monitor::thread_count(),
561 disk_read_bps: system_monitor::disk_read_bps(),
562 disk_write_bps: system_monitor::disk_write_bps(),
563 network_rx_bps: system_monitor::network_rx_bps(),
564 network_tx_bps: system_monitor::network_tx_bps(),
565 gpu_usage_percent: system_monitor::gpu_memory_usage_percent(),
566 gpu_memory_used: system_monitor::gpu_memory_used(),
567 gpu_memory_total: system_monitor::gpu_memory_total(),
568 }
569 }
570}
571
572impl Default for Tracker {
573 fn default() -> Self {
574 Self::new()
575 }
576}
577
578impl Drop for Tracker {
579 fn drop(&mut self) {
580 let events = self.event_store().snapshot();
582 let allocations = rebuild_allocations_from_events(&events);
583
584 let thread_id_u64 = crate::utils::current_thread_id_u64();
586
587 for alloc in &allocations {
588 let event = MemoryEvent::deallocate(alloc.ptr, alloc.size, thread_id_u64);
589 self.event_store().record(event);
590 }
591
592 if let Ok(cfg) = self.config.lock() {
593 if cfg.auto_export_on_drop {
594 if let Some(ref path) = cfg.export_path {
595 let events = self.event_store().snapshot();
597 let allocations = rebuild_allocations_from_events(&events);
598 let snapshot = MemorySnapshot::from_allocation_infos(allocations);
599 let options = ExportJsonOptions::default();
600 if let Err(e) =
601 export_snapshot_to_json(&snapshot, std::path::Path::new(path), &options)
602 {
603 tracing::error!("Failed to auto-export on drop: {}", e);
604 }
605 }
606 }
607 }
608 }
609}
610
611impl serde::Serialize for AnalysisReport {
612 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
613 where
614 S: serde::Serializer,
615 {
616 use serde::ser::SerializeStruct;
617 let mut state = serializer.serialize_struct("AnalysisReport", 9)?;
618 state.serialize_field("total_allocations", &self.total_allocations)?;
619 state.serialize_field("total_deallocations", &self.total_deallocations)?;
620 state.serialize_field("active_allocations", &self.active_allocations)?;
621 state.serialize_field("peak_memory_bytes", &self.peak_memory_bytes)?;
622 state.serialize_field("current_memory_bytes", &self.current_memory_bytes)?;
623 state.serialize_field("allocation_rate_per_sec", &self.allocation_rate_per_sec)?;
624 state.serialize_field("deallocation_rate_per_sec", &self.deallocation_rate_per_sec)?;
625 state.serialize_field("hotspots", &self.hotspots)?;
626 state.serialize_field("system_snapshots", &self.system_snapshots)?;
627 state.end()
628 }
629}
630
631impl serde::Serialize for AllocationHotspot {
632 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
633 where
634 S: serde::Serializer,
635 {
636 use serde::ser::SerializeStruct;
637 let mut state = serializer.serialize_struct("AllocationHotspot", 5)?;
638 state.serialize_field("var_name", &self.var_name)?;
639 state.serialize_field("type_name", &self.type_name)?;
640 state.serialize_field("total_size", &self.total_size)?;
641 state.serialize_field("allocation_count", &self.allocation_count)?;
642 state.serialize_field("location", &self.location)?;
643 state.end()
644 }
645}
646
647#[macro_export]
648macro_rules! tracker {
649 () => {
650 $crate::tracker::Tracker::new()
651 };
652}
653
654#[macro_export]
655macro_rules! track {
656 ($tracker:expr, $var:expr) => {{
657 let var_name = stringify!($var);
658 $tracker.track_as(&$var, var_name, file!(), line!(), module_path!());
659 }};
660}
661
662#[macro_export]
663macro_rules! track_clone {
664 ($tracker:expr, $source:expr, $target:expr) => {{
665 let source_name = stringify!($source);
666 let target_name = stringify!($target);
667 let source_ptr = &$source as *const _ as usize;
668 let target_ptr = &$target as *const _ as usize;
669 let type_name = $crate::utils::type_of(&$target);
670 $tracker.track_clone(
671 source_ptr,
672 target_ptr,
673 std::mem::size_of_val(&$target),
674 Some(target_name.to_string()),
675 Some(type_name.to_string()),
676 file!(),
677 line!(),
678 module_path!(),
679 );
680 }};
681}
682
683#[cfg(test)]
684mod tests {
685 use super::*;
686
687 #[test]
688 fn test_tracker_creation() {
689 let tracker = Tracker::new();
690 let _ = tracker;
691 }
692
693 #[test]
694 fn test_tracker_with_config() {
695 let tracker = Tracker::new()
696 .with_sampling(SamplingConfig::demo())
697 .with_system_monitoring();
698 let _ = tracker;
699 }
700
701 #[test]
702 fn test_track_macro() {
703 let tracker = tracker!();
704 let my_vec = vec![1, 2, 3];
705 track!(tracker, my_vec);
706 }
707
708 #[test]
709 fn test_analyze() {
710 let tracker = tracker!();
711 let data = vec![1, 2, 3];
712 track!(tracker, data);
713 let report = tracker.analyze();
714 assert!(report.total_allocations > 0);
715 }
716
717 #[cfg(target_os = "macos")]
718 #[test]
719 fn test_system_monitoring() {
720 system_monitor::SystemMonitor::global();
721 std::thread::sleep(std::time::Duration::from_millis(350));
722
723 let cpu = system_monitor::cpu_usage();
724 let mem = system_monitor::memory_used();
725 let total = system_monitor::memory_total();
726
727 println!("CPU: {:.2}%", cpu);
728 println!("Memory: {} / {} bytes", mem, total);
729
730 assert!((0.0..=100.0).contains(&cpu));
731 assert!(total > 0, "total memory should be initialized by now");
732 }
733
734 #[test]
735 fn test_current_system_snapshot() {
736 std::thread::sleep(std::time::Duration::from_millis(150));
737
738 let tracker = tracker!();
739 let snapshot = tracker.current_system_snapshot();
740
741 println!(
742 "Snapshot: CPU={:.2}%, Mem={:.2}%",
743 snapshot.cpu_usage_percent, snapshot.memory_usage_percent
744 );
745
746 assert!(snapshot.cpu_usage_percent >= 0.0 && snapshot.cpu_usage_percent <= 100.0);
747 }
748
749 #[test]
750 fn test_sampling_config_default() {
751 let config = SamplingConfig::default();
752 assert_eq!(config.sample_rate, 1.0);
753 assert!(!config.capture_call_stack);
754 assert_eq!(config.max_stack_depth, 10);
755 }
756
757 #[test]
758 fn test_sampling_config_demo() {
759 let config = SamplingConfig::demo();
760 assert_eq!(config.sample_rate, 0.1);
761 assert!(!config.capture_call_stack);
762 assert_eq!(config.max_stack_depth, 5);
763 }
764
765 #[test]
766 fn test_sampling_config_full() {
767 let config = SamplingConfig::full();
768 assert_eq!(config.sample_rate, 1.0);
769 assert!(config.capture_call_stack);
770 assert_eq!(config.max_stack_depth, 20);
771 }
772
773 #[test]
774 fn test_sampling_config_high_performance() {
775 let config = SamplingConfig::high_performance();
776 assert_eq!(config.sample_rate, 0.01);
777 assert!(!config.capture_call_stack);
778 assert_eq!(config.max_stack_depth, 0);
779 }
780
781 #[test]
782 fn test_sampling_config_clone() {
783 let config = SamplingConfig::full();
784 let cloned = config.clone();
785 assert_eq!(cloned.sample_rate, config.sample_rate);
786 assert_eq!(cloned.capture_call_stack, config.capture_call_stack);
787 }
788
789 #[test]
790 fn test_sampling_config_debug() {
791 let config = SamplingConfig::default();
792 let debug_str = format!("{:?}", config);
793 assert!(debug_str.contains("SamplingConfig"));
794 assert!(debug_str.contains("sample_rate"));
795 }
796
797 #[test]
798 fn test_system_snapshot_default() {
799 let snapshot = SystemSnapshot::default();
800 assert_eq!(snapshot.timestamp, 0);
801 assert_eq!(snapshot.cpu_usage_percent, 0.0);
802 assert_eq!(snapshot.memory_usage_bytes, 0);
803 assert_eq!(snapshot.thread_count, 0);
804 }
805
806 #[test]
807 fn test_system_snapshot_clone() {
808 let snapshot = SystemSnapshot {
809 timestamp: 1000,
810 cpu_usage_percent: 50.0,
811 memory_usage_bytes: 1024 * 1024,
812 memory_usage_percent: 25.0,
813 thread_count: 4,
814 disk_read_bps: 1000,
815 disk_write_bps: 500,
816 network_rx_bps: 2000,
817 network_tx_bps: 1000,
818 gpu_usage_percent: 30.0,
819 gpu_memory_used: 512 * 1024 * 1024,
820 gpu_memory_total: 2 * 1024 * 1024 * 1024,
821 };
822
823 let cloned = snapshot.clone();
824 assert_eq!(cloned.timestamp, 1000);
825 assert_eq!(cloned.cpu_usage_percent, 50.0);
826 }
827
828 #[test]
829 fn test_system_snapshot_debug() {
830 let snapshot = SystemSnapshot::default();
831 let debug_str = format!("{:?}", snapshot);
832 assert!(debug_str.contains("SystemSnapshot"));
833 }
834
835 #[test]
836 fn test_analysis_report_creation() {
837 let report = AnalysisReport {
838 total_allocations: 100,
839 total_deallocations: 50,
840 active_allocations: 50,
841 peak_memory_bytes: 1024 * 1024,
842 current_memory_bytes: 512 * 1024,
843 allocation_rate_per_sec: 10.0,
844 deallocation_rate_per_sec: 5.0,
845 hotspots: vec![],
846 system_snapshots: vec![],
847 };
848
849 assert_eq!(report.total_allocations, 100);
850 assert_eq!(report.active_allocations, 50);
851 }
852
853 #[test]
854 fn test_analysis_report_clone() {
855 let report = AnalysisReport {
856 total_allocations: 10,
857 total_deallocations: 5,
858 active_allocations: 5,
859 peak_memory_bytes: 1024,
860 current_memory_bytes: 512,
861 allocation_rate_per_sec: 1.0,
862 deallocation_rate_per_sec: 0.5,
863 hotspots: vec![],
864 system_snapshots: vec![],
865 };
866
867 let cloned = report.clone();
868 assert_eq!(cloned.total_allocations, 10);
869 }
870
871 #[test]
872 fn test_analysis_report_debug() {
873 let report = AnalysisReport {
874 total_allocations: 0,
875 total_deallocations: 0,
876 active_allocations: 0,
877 peak_memory_bytes: 0,
878 current_memory_bytes: 0,
879 allocation_rate_per_sec: 0.0,
880 deallocation_rate_per_sec: 0.0,
881 hotspots: vec![],
882 system_snapshots: vec![],
883 };
884
885 let debug_str = format!("{:?}", report);
886 assert!(debug_str.contains("AnalysisReport"));
887 }
888
889 #[test]
890 fn test_allocation_hotspot_creation() {
891 let hotspot = AllocationHotspot {
892 var_name: "my_vec".to_string(),
893 type_name: "Vec<u8>".to_string(),
894 total_size: 1024,
895 allocation_count: 10,
896 location: Some("main.rs:42".to_string()),
897 };
898
899 assert_eq!(hotspot.var_name, "my_vec");
900 assert_eq!(hotspot.total_size, 1024);
901 }
902
903 #[test]
904 fn test_allocation_hotspot_clone() {
905 let hotspot = AllocationHotspot {
906 var_name: "data".to_string(),
907 type_name: "String".to_string(),
908 total_size: 100,
909 allocation_count: 5,
910 location: None,
911 };
912
913 let cloned = hotspot.clone();
914 assert_eq!(cloned.var_name, "data");
915 }
916
917 #[test]
918 fn test_allocation_hotspot_debug() {
919 let hotspot = AllocationHotspot {
920 var_name: "test".to_string(),
921 type_name: "i32".to_string(),
922 total_size: 4,
923 allocation_count: 1,
924 location: None,
925 };
926
927 let debug_str = format!("{:?}", hotspot);
928 assert!(debug_str.contains("AllocationHotspot"));
929 }
930
931 #[test]
932 fn test_tracker_clone() {
933 let tracker = Tracker::new();
934 let cloned = tracker.clone();
935
936 let report1 = tracker.analyze();
937 let report2 = cloned.analyze();
938
939 assert_eq!(report1.total_allocations, report2.total_allocations);
941 }
942
943 #[test]
944 fn test_tracker_with_sampling() {
945 let tracker = Tracker::new().with_sampling(SamplingConfig::high_performance());
946 let data = vec![1, 2, 3];
947 tracker.track_as(&data, "data", "test.rs", 1, "test_module");
948 }
949
950 #[test]
951 fn test_tracker_elapsed() {
952 let tracker = Tracker::new();
953 std::thread::sleep(std::time::Duration::from_millis(10));
954 let elapsed = tracker.elapsed();
955 assert!(elapsed >= std::time::Duration::from_millis(10));
956 }
957
958 #[test]
959 fn test_tracker_with_system_monitoring() {
960 let tracker = Tracker::new().with_system_monitoring();
961 let _ = tracker.current_system_snapshot();
962 }
963
964 #[test]
965 fn test_tracker_track_as_multiple() {
966 let tracker = Tracker::new();
967 let data = vec![1, 2, 3, 4, 5];
968
969 tracker.track_as(&data, "my_vec", "test.rs", 10, "test_module");
970 tracker.track_as(&data, "my_vec", "test.rs", 20, "test_module");
971
972 let report = tracker.analyze();
973 let _ = report.total_allocations;
974 }
975
976 #[test]
977 fn test_sampling_config_custom() {
978 let config = SamplingConfig {
979 sample_rate: 0.5,
980 capture_call_stack: true,
981 max_stack_depth: 15,
982 };
983
984 assert!((config.sample_rate - 0.5).abs() < 0.001);
985 assert!(config.capture_call_stack);
986 assert_eq!(config.max_stack_depth, 15);
987 }
988
989 #[test]
990 fn test_analysis_report_with_hotspots() {
991 let report = AnalysisReport {
992 total_allocations: 100,
993 total_deallocations: 50,
994 active_allocations: 50,
995 peak_memory_bytes: 1024 * 1024,
996 current_memory_bytes: 512 * 1024,
997 allocation_rate_per_sec: 10.0,
998 deallocation_rate_per_sec: 5.0,
999 hotspots: vec![AllocationHotspot {
1000 var_name: "test".to_string(),
1001 type_name: "Vec<u8>".to_string(),
1002 total_size: 1024,
1003 allocation_count: 10,
1004 location: Some("test.rs:1".to_string()),
1005 }],
1006 system_snapshots: vec![],
1007 };
1008
1009 assert_eq!(report.hotspots.len(), 1);
1010 }
1011
1012 #[test]
1013 fn test_tracker_with_sampling_and_monitoring() {
1014 let tracker = Tracker::new()
1015 .with_sampling(SamplingConfig::demo())
1016 .with_system_monitoring();
1017
1018 let data = vec![1, 2, 3];
1019 tracker.track_as(&data, "data", "test.rs", 1, "test_module");
1020
1021 let snapshot = tracker.current_system_snapshot();
1022 assert!(snapshot.cpu_usage_percent >= 0.0);
1023 }
1024
1025 #[test]
1026 fn test_tracker_events() {
1027 let tracker = Tracker::new();
1028 let data = vec![1, 2, 3];
1029 tracker.track_as(&data, "test_data", "test.rs", 1, "test_module");
1030
1031 let events = tracker.events();
1032 assert!(!events.is_empty());
1033 }
1034
1035 #[test]
1036 fn test_tracker_event_store() {
1037 let tracker = Tracker::new();
1038 let _store = tracker.event_store();
1039 }
1040
1041 #[test]
1042 fn test_tracker_stats() {
1043 let tracker = Tracker::new();
1044 let stats = tracker.stats();
1045
1046 assert_eq!(stats.total_allocations, 0);
1047 assert_eq!(stats.active_allocations, 0);
1048 }
1049
1050 #[test]
1051 fn test_tracker_stats_with_data() {
1052 let tracker = Tracker::new();
1053 let data = vec![1u8; 1024];
1054 tracker.track_as(&data, "buffer", "test.rs", 1, "test_module");
1055
1056 let stats = tracker.stats();
1057 assert!(
1058 stats.total_allocations >= 1,
1059 "Should have at least one allocation after tracking data"
1060 );
1061 }
1062
1063 #[test]
1064 fn test_tracker_system_snapshots() {
1065 let tracker = Tracker::new().with_system_monitoring();
1066 let snapshots = tracker.system_snapshots();
1067 assert!(!snapshots.is_empty());
1068 }
1069
1070 #[test]
1071 fn test_tracker_inner() {
1072 let tracker = Tracker::new();
1073 let _inner = tracker.inner();
1074 }
1075
1076 #[test]
1077 fn test_tracker_with_auto_export() {
1078 let tracker = Tracker::new().with_auto_export("/tmp/test_export");
1079 let data = vec![1, 2, 3];
1080 tracker.track_as(&data, "test", "test.rs", 1, "test_module");
1081 }
1082
1083 #[test]
1084 fn test_sampling_config_zero_rate() {
1085 let config = SamplingConfig {
1086 sample_rate: 0.0,
1087 capture_call_stack: false,
1088 max_stack_depth: 0,
1089 };
1090
1091 let tracker = Tracker::new().with_sampling(config);
1092 let data = vec![1, 2, 3];
1093 tracker.track_as(&data, "test", "test.rs", 1, "test_module");
1094 }
1095
1096 #[test]
1097 fn test_analysis_report_serialization() {
1098 let report = AnalysisReport {
1099 total_allocations: 100,
1100 total_deallocations: 50,
1101 active_allocations: 50,
1102 peak_memory_bytes: 1024,
1103 current_memory_bytes: 512,
1104 allocation_rate_per_sec: 10.0,
1105 deallocation_rate_per_sec: 5.0,
1106 hotspots: vec![],
1107 system_snapshots: vec![],
1108 };
1109
1110 let json = serde_json::to_string(&report);
1111 assert!(json.is_ok());
1112 }
1113
1114 #[test]
1115 fn test_allocation_hotspot_serialization() {
1116 let hotspot = AllocationHotspot {
1117 var_name: "test".to_string(),
1118 type_name: "Vec<u8>".to_string(),
1119 total_size: 1024,
1120 allocation_count: 10,
1121 location: Some("test.rs:1".to_string()),
1122 };
1123
1124 let json = serde_json::to_string(&hotspot);
1125 assert!(json.is_ok());
1126 }
1127
1128 #[test]
1129 fn test_tracker_multiple_system_snapshots() {
1130 let tracker = Tracker::new().with_system_monitoring();
1131 std::thread::sleep(std::time::Duration::from_millis(10));
1132 tracker.current_system_snapshot();
1133
1134 let snapshots = tracker.system_snapshots();
1135 assert!(
1136 !snapshots.is_empty(),
1137 "Should have at least one system snapshot"
1138 );
1139 }
1140
1141 #[test]
1142 fn test_tracker_analyze_with_hotspots() {
1143 let tracker = Tracker::new();
1144 let data1 = vec![1u8; 100];
1145 let data2 = vec![2u8; 200];
1146 let data3 = vec![3u8; 300];
1147
1148 tracker.track_as(&data1, "buffer1", "test.rs", 1, "test_module");
1149 tracker.track_as(&data2, "buffer2", "test.rs", 2, "test_module");
1150 tracker.track_as(&data3, "buffer3", "test.rs", 3, "test_module");
1151
1152 let report = tracker.analyze();
1153 assert!(
1154 report.total_allocations >= 3,
1155 "Should have at least 3 allocations after tracking"
1156 );
1157 }
1158
1159 #[test]
1160 fn test_tracker_default() {
1161 let tracker = Tracker::default();
1162 let report = tracker.analyze();
1163 assert_eq!(report.total_allocations, 0);
1164 }
1165}