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