Skip to main content

memscope_rs/
tracker.rs

1//! Unified Tracker API - Enhanced Version
2//!
3//! This module provides a simple, unified interface for memory tracking
4//! with all the power of the old API and more.
5//!
6//! # Features
7//!
8//! - **Simple API**: `tracker!()` and `track!()` macros
9//! - **Auto-capture**: Automatic variable name and type capture
10//! - **System Monitoring**: CPU, memory monitoring (background thread, zero overhead)
11//! - **Per-thread Tracking**: Independent tracking per thread
12//! - **Sampling**: Configurable sampling rates
13//! - **Hotspot Analysis**: Automatic allocation hotspot detection
14//! - **HTML Dashboard**: Interactive visualization
15//! - **JSON/Binary Export**: Multiple export formats
16//!
17//! # Architecture
18//!
19//! System monitoring runs in a background thread that collects metrics every 100ms.
20//! The `track!` macro only reads atomic values (nanosecond overhead), ensuring
21//! no blocking on data collection.
22//!
23//! # Usage
24//!
25//! ```rust
26//! use memscope_rs::{tracker, track};
27//!
28//! // Simple usage - system monitoring is automatic
29//! let tracker = tracker!();
30//! let my_vec = vec![1, 2, 3];
31//! track!(tracker, my_vec);
32//! // Analyze the tracked allocations
33//! let report = tracker.analyze();
34//!
35//! // Advanced usage with custom sampling
36//! use memscope_rs::tracker::SamplingConfig;
37//! let tracker = tracker!().with_sampling(SamplingConfig::high_performance());
38//! ```
39
40use 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(), // Use current time for cloned tracker
147            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>(&self, var: &T, name: &str, file: &str, line: u32) {
225        if let Ok(cfg) = self.config.lock() {
226            if cfg.sampling.sample_rate < 1.0 {
227                use std::collections::hash_map::DefaultHasher;
228                use std::hash::{Hash, Hasher};
229                let mut hasher = DefaultHasher::new();
230                // Use current timestamp for randomness to ensure sampling works
231                // correctly even with identical variable names in a loop
232                let timestamp = std::time::SystemTime::now()
233                    .duration_since(std::time::UNIX_EPOCH)
234                    .unwrap_or_default()
235                    .as_nanos();
236                timestamp.hash(&mut hasher);
237                std::thread::current().id().hash(&mut hasher);
238                name.hash(&mut hasher);
239                file.hash(&mut hasher);
240                line.hash(&mut hasher);
241                let hash = hasher.finish();
242                let threshold = (cfg.sampling.sample_rate * 1000.0) as u64;
243                if (hash % 1000) > threshold {
244                    return;
245                }
246            }
247        }
248
249        self.track_inner(var, name, file, line);
250    }
251
252    fn track_inner<T: crate::Trackable>(&self, var: &T, name: &str, file: &str, line: u32) {
253        let type_name = var.get_type_name().to_string();
254        let kind = var.track_kind();
255
256        let thread_id_u64 = crate::utils::current_thread_id_u64();
257
258        match kind {
259            crate::core::types::TrackKind::HeapOwner { ptr, size } => {
260                // Only HeapOwner gets tracked in inner tracker
261                if let Err(e) = self.inner.track_allocation(ptr, size) {
262                    tracing::error!("Failed to track allocation at ptr {:x}: {}", ptr, e);
263                    return;
264                }
265
266                let mut event = MemoryEvent::allocate(ptr, size, thread_id_u64);
267                event.var_name = Some(name.to_string());
268                event.type_name = Some(type_name.clone());
269                event.source_file = Some(file.to_string());
270                event.source_line = Some(line);
271                self.event_store.record(event);
272
273                if let Err(e) = self.inner.associate_var(
274                    ptr,
275                    name.to_string(),
276                    type_name,
277                    Some(file),
278                    Some(line),
279                ) {
280                    tracing::error!("Failed to associate var '{}' at ptr {:x}: {}", name, ptr, e);
281                }
282            }
283            crate::core::types::TrackKind::Container | crate::core::types::TrackKind::Value => {
284                // Container and Value record metadata events without heap allocation
285                // They will be tracked as graph nodes but not scanned by HeapScanner
286                let mut event = MemoryEvent::metadata(
287                    name.to_string(),
288                    type_name,
289                    thread_id_u64,
290                    var.get_size_estimate(),
291                );
292                event.source_file = Some(file.to_string());
293                event.source_line = Some(line);
294                self.event_store.record(event);
295            }
296        }
297    }
298
299    pub fn track_deallocation(&self, ptr: usize) -> crate::TrackingResult<bool> {
300        let size = self.inner.get_allocation_size(ptr).unwrap_or(0);
301
302        let result = self.inner.track_deallocation(ptr)?;
303
304        // Only record event if deallocation was successful (ptr was tracked)
305        if result {
306            let thread_id_u64 = crate::utils::current_thread_id_u64();
307
308            let event = MemoryEvent::deallocate(ptr, size, thread_id_u64);
309            self.event_store.record(event);
310        }
311
312        Ok(result)
313    }
314
315    pub fn events(&self) -> Vec<MemoryEvent> {
316        self.event_store.snapshot()
317    }
318
319    pub fn event_store(&self) -> &Arc<EventStore> {
320        &self.event_store
321    }
322
323    fn capture_system_snapshot(&self) {
324        let snapshot = SystemSnapshot {
325            timestamp: std::time::SystemTime::now()
326                .duration_since(std::time::UNIX_EPOCH)
327                .unwrap_or_default()
328                .as_millis() as u64,
329            cpu_usage_percent: system_monitor::cpu_usage(),
330            memory_usage_bytes: system_monitor::memory_used(),
331            memory_usage_percent: system_monitor::memory_usage_percent(),
332            thread_count: system_monitor::thread_count(),
333            disk_read_bps: system_monitor::disk_read_bps(),
334            disk_write_bps: system_monitor::disk_write_bps(),
335            network_rx_bps: system_monitor::network_rx_bps(),
336            network_tx_bps: system_monitor::network_tx_bps(),
337            gpu_usage_percent: system_monitor::gpu_memory_usage_percent(),
338            gpu_memory_used: system_monitor::gpu_memory_used(),
339            gpu_memory_total: system_monitor::gpu_memory_total(),
340        };
341
342        if let Ok(mut snapshots) = self.system_snapshots.lock() {
343            snapshots.push(snapshot);
344        }
345    }
346
347    pub fn stats(&self) -> crate::core::types::MemoryStats {
348        let stats = self.inner.get_stats().unwrap_or_default();
349        crate::core::types::MemoryStats {
350            total_allocations: stats.total_allocations as usize,
351            total_allocated: stats.total_allocated as usize,
352            active_allocations: stats.active_allocations,
353            active_memory: stats.active_memory as usize,
354            peak_allocations: stats.peak_allocations,
355            peak_memory: stats.peak_memory as usize,
356            total_deallocations: stats.total_deallocations as usize,
357            total_deallocated: stats.total_deallocated as usize,
358            leaked_allocations: stats.leaked_allocations,
359            leaked_memory: stats.leaked_memory as usize,
360            ..Default::default()
361        }
362    }
363
364    pub fn analyze(&self) -> AnalysisReport {
365        let stats = self.stats();
366        let events = self.event_store().snapshot();
367        let allocations = rebuild_allocations_from_events(&events);
368        let elapsed = self.start_time.elapsed().as_secs_f64();
369
370        let current_memory: usize = allocations.iter().map(|a| a.size).sum();
371        let peak_memory = stats.peak_memory.max(current_memory);
372
373        let mut hotspot_map: HashMap<String, (String, usize, usize)> = HashMap::new();
374        for alloc in &allocations {
375            if let Some(ref var_name) = alloc.var_name {
376                let key = var_name.clone();
377                let entry = hotspot_map.entry(key).or_insert((
378                    alloc.type_name.clone().unwrap_or_default(),
379                    0,
380                    0,
381                ));
382                entry.1 += alloc.size;
383                entry.2 += 1;
384            }
385        }
386
387        let hotspots: Vec<AllocationHotspot> = hotspot_map
388            .into_iter()
389            .map(
390                |(var_name, (type_name, total_size, count))| AllocationHotspot {
391                    var_name,
392                    type_name,
393                    total_size,
394                    allocation_count: count,
395                    location: None,
396                },
397            )
398            .collect();
399
400        let system_snapshots = self
401            .system_snapshots
402            .lock()
403            .unwrap_or_else(|e| e.into_inner())
404            .clone();
405
406        AnalysisReport {
407            total_allocations: stats.total_allocations,
408            total_deallocations: stats.total_deallocations,
409            active_allocations: allocations.len(),
410            peak_memory_bytes: peak_memory as u64,
411            current_memory_bytes: current_memory as u64,
412            allocation_rate_per_sec: if elapsed > 0.0 {
413                stats.total_allocations as f64 / elapsed
414            } else {
415                0.0
416            },
417            deallocation_rate_per_sec: if elapsed > 0.0 {
418                stats.total_deallocations as f64 / elapsed
419            } else {
420                0.0
421            },
422            hotspots,
423            system_snapshots,
424        }
425    }
426
427    pub fn inner(&self) -> &Arc<MemoryTracker> {
428        &self.inner
429    }
430
431    pub fn elapsed(&self) -> Duration {
432        self.start_time.elapsed()
433    }
434
435    pub fn system_snapshots(&self) -> Vec<SystemSnapshot> {
436        self.system_snapshots
437            .lock()
438            .unwrap_or_else(|e| e.into_inner())
439            .clone()
440    }
441
442    pub fn current_system_snapshot(&self) -> SystemSnapshot {
443        SystemSnapshot {
444            timestamp: std::time::SystemTime::now()
445                .duration_since(std::time::UNIX_EPOCH)
446                .unwrap_or_default()
447                .as_millis() as u64,
448            cpu_usage_percent: system_monitor::cpu_usage(),
449            memory_usage_bytes: system_monitor::memory_used(),
450            memory_usage_percent: system_monitor::memory_usage_percent(),
451            thread_count: system_monitor::thread_count(),
452            disk_read_bps: system_monitor::disk_read_bps(),
453            disk_write_bps: system_monitor::disk_write_bps(),
454            network_rx_bps: system_monitor::network_rx_bps(),
455            network_tx_bps: system_monitor::network_tx_bps(),
456            gpu_usage_percent: system_monitor::gpu_memory_usage_percent(),
457            gpu_memory_used: system_monitor::gpu_memory_used(),
458            gpu_memory_total: system_monitor::gpu_memory_total(),
459        }
460    }
461}
462
463impl Default for Tracker {
464    fn default() -> Self {
465        Self::new()
466    }
467}
468
469impl Drop for Tracker {
470    fn drop(&mut self) {
471        if let Ok(cfg) = self.config.lock() {
472            if cfg.auto_export_on_drop {
473                if let Some(ref path) = cfg.export_path {
474                    // Use event_store as unified data source (includes both HeapOwner and Container allocations)
475                    let events = self.event_store().snapshot();
476                    let allocations = rebuild_allocations_from_events(&events);
477                    let snapshot = MemorySnapshot::from_allocation_infos(allocations);
478                    let options = ExportJsonOptions::default();
479                    if let Err(e) =
480                        export_snapshot_to_json(&snapshot, std::path::Path::new(path), &options)
481                    {
482                        tracing::error!("Failed to auto-export on drop: {}", e);
483                    }
484                }
485            }
486        }
487    }
488}
489
490impl serde::Serialize for AnalysisReport {
491    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
492    where
493        S: serde::Serializer,
494    {
495        use serde::ser::SerializeStruct;
496        let mut state = serializer.serialize_struct("AnalysisReport", 9)?;
497        state.serialize_field("total_allocations", &self.total_allocations)?;
498        state.serialize_field("total_deallocations", &self.total_deallocations)?;
499        state.serialize_field("active_allocations", &self.active_allocations)?;
500        state.serialize_field("peak_memory_bytes", &self.peak_memory_bytes)?;
501        state.serialize_field("current_memory_bytes", &self.current_memory_bytes)?;
502        state.serialize_field("allocation_rate_per_sec", &self.allocation_rate_per_sec)?;
503        state.serialize_field("deallocation_rate_per_sec", &self.deallocation_rate_per_sec)?;
504        state.serialize_field("hotspots", &self.hotspots)?;
505        state.serialize_field("system_snapshots", &self.system_snapshots)?;
506        state.end()
507    }
508}
509
510impl serde::Serialize for AllocationHotspot {
511    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
512    where
513        S: serde::Serializer,
514    {
515        use serde::ser::SerializeStruct;
516        let mut state = serializer.serialize_struct("AllocationHotspot", 5)?;
517        state.serialize_field("var_name", &self.var_name)?;
518        state.serialize_field("type_name", &self.type_name)?;
519        state.serialize_field("total_size", &self.total_size)?;
520        state.serialize_field("allocation_count", &self.allocation_count)?;
521        state.serialize_field("location", &self.location)?;
522        state.end()
523    }
524}
525
526#[macro_export]
527macro_rules! tracker {
528    () => {
529        $crate::tracker::Tracker::new()
530    };
531}
532
533#[macro_export]
534macro_rules! track {
535    ($tracker:expr, $var:expr) => {{
536        let var_name = stringify!($var);
537        $tracker.track_as(&$var, var_name, file!(), line!());
538    }};
539}
540
541#[cfg(test)]
542mod tests {
543    use super::*;
544
545    #[test]
546    fn test_tracker_creation() {
547        let tracker = Tracker::new();
548        let _ = tracker;
549    }
550
551    #[test]
552    fn test_tracker_with_config() {
553        let tracker = Tracker::new()
554            .with_sampling(SamplingConfig::demo())
555            .with_system_monitoring();
556        let _ = tracker;
557    }
558
559    #[test]
560    fn test_track_macro() {
561        let tracker = tracker!();
562        let my_vec = vec![1, 2, 3];
563        track!(tracker, my_vec);
564    }
565
566    #[test]
567    fn test_analyze() {
568        let tracker = tracker!();
569        let data = vec![1, 2, 3];
570        track!(tracker, data);
571        let report = tracker.analyze();
572        assert!(report.total_allocations > 0);
573    }
574
575    #[test]
576    #[cfg(target_os = "macos")]
577    fn test_system_monitoring() {
578        std::thread::sleep(std::time::Duration::from_millis(200));
579
580        let cpu = system_monitor::cpu_usage();
581        let mem = system_monitor::memory_used();
582        let total = system_monitor::memory_total();
583
584        println!("CPU: {:.2}%", cpu);
585        println!("Memory: {} / {} bytes", mem, total);
586
587        assert!((0.0..=100.0).contains(&cpu));
588        assert!(total > 0);
589    }
590
591    #[test]
592    fn test_current_system_snapshot() {
593        std::thread::sleep(std::time::Duration::from_millis(150));
594
595        let tracker = tracker!();
596        let snapshot = tracker.current_system_snapshot();
597
598        println!(
599            "Snapshot: CPU={:.2}%, Mem={:.2}%",
600            snapshot.cpu_usage_percent, snapshot.memory_usage_percent
601        );
602
603        assert!(snapshot.cpu_usage_percent >= 0.0 && snapshot.cpu_usage_percent <= 100.0);
604    }
605
606    #[test]
607    fn test_sampling_config_default() {
608        let config = SamplingConfig::default();
609        assert_eq!(config.sample_rate, 1.0);
610        assert!(!config.capture_call_stack);
611        assert_eq!(config.max_stack_depth, 10);
612    }
613
614    #[test]
615    fn test_sampling_config_demo() {
616        let config = SamplingConfig::demo();
617        assert_eq!(config.sample_rate, 0.1);
618        assert!(!config.capture_call_stack);
619        assert_eq!(config.max_stack_depth, 5);
620    }
621
622    #[test]
623    fn test_sampling_config_full() {
624        let config = SamplingConfig::full();
625        assert_eq!(config.sample_rate, 1.0);
626        assert!(config.capture_call_stack);
627        assert_eq!(config.max_stack_depth, 20);
628    }
629
630    #[test]
631    fn test_sampling_config_high_performance() {
632        let config = SamplingConfig::high_performance();
633        assert_eq!(config.sample_rate, 0.01);
634        assert!(!config.capture_call_stack);
635        assert_eq!(config.max_stack_depth, 0);
636    }
637
638    #[test]
639    fn test_sampling_config_clone() {
640        let config = SamplingConfig::full();
641        let cloned = config.clone();
642        assert_eq!(cloned.sample_rate, config.sample_rate);
643        assert_eq!(cloned.capture_call_stack, config.capture_call_stack);
644    }
645
646    #[test]
647    fn test_sampling_config_debug() {
648        let config = SamplingConfig::default();
649        let debug_str = format!("{:?}", config);
650        assert!(debug_str.contains("SamplingConfig"));
651        assert!(debug_str.contains("sample_rate"));
652    }
653
654    #[test]
655    fn test_system_snapshot_default() {
656        let snapshot = SystemSnapshot::default();
657        assert_eq!(snapshot.timestamp, 0);
658        assert_eq!(snapshot.cpu_usage_percent, 0.0);
659        assert_eq!(snapshot.memory_usage_bytes, 0);
660        assert_eq!(snapshot.thread_count, 0);
661    }
662
663    #[test]
664    fn test_system_snapshot_clone() {
665        let snapshot = SystemSnapshot {
666            timestamp: 1000,
667            cpu_usage_percent: 50.0,
668            memory_usage_bytes: 1024 * 1024,
669            memory_usage_percent: 25.0,
670            thread_count: 4,
671            disk_read_bps: 1000,
672            disk_write_bps: 500,
673            network_rx_bps: 2000,
674            network_tx_bps: 1000,
675            gpu_usage_percent: 30.0,
676            gpu_memory_used: 512 * 1024 * 1024,
677            gpu_memory_total: 2 * 1024 * 1024 * 1024,
678        };
679
680        let cloned = snapshot.clone();
681        assert_eq!(cloned.timestamp, 1000);
682        assert_eq!(cloned.cpu_usage_percent, 50.0);
683    }
684
685    #[test]
686    fn test_system_snapshot_debug() {
687        let snapshot = SystemSnapshot::default();
688        let debug_str = format!("{:?}", snapshot);
689        assert!(debug_str.contains("SystemSnapshot"));
690    }
691
692    #[test]
693    fn test_analysis_report_creation() {
694        let report = AnalysisReport {
695            total_allocations: 100,
696            total_deallocations: 50,
697            active_allocations: 50,
698            peak_memory_bytes: 1024 * 1024,
699            current_memory_bytes: 512 * 1024,
700            allocation_rate_per_sec: 10.0,
701            deallocation_rate_per_sec: 5.0,
702            hotspots: vec![],
703            system_snapshots: vec![],
704        };
705
706        assert_eq!(report.total_allocations, 100);
707        assert_eq!(report.active_allocations, 50);
708    }
709
710    #[test]
711    fn test_analysis_report_clone() {
712        let report = AnalysisReport {
713            total_allocations: 10,
714            total_deallocations: 5,
715            active_allocations: 5,
716            peak_memory_bytes: 1024,
717            current_memory_bytes: 512,
718            allocation_rate_per_sec: 1.0,
719            deallocation_rate_per_sec: 0.5,
720            hotspots: vec![],
721            system_snapshots: vec![],
722        };
723
724        let cloned = report.clone();
725        assert_eq!(cloned.total_allocations, 10);
726    }
727
728    #[test]
729    fn test_analysis_report_debug() {
730        let report = AnalysisReport {
731            total_allocations: 0,
732            total_deallocations: 0,
733            active_allocations: 0,
734            peak_memory_bytes: 0,
735            current_memory_bytes: 0,
736            allocation_rate_per_sec: 0.0,
737            deallocation_rate_per_sec: 0.0,
738            hotspots: vec![],
739            system_snapshots: vec![],
740        };
741
742        let debug_str = format!("{:?}", report);
743        assert!(debug_str.contains("AnalysisReport"));
744    }
745
746    #[test]
747    fn test_allocation_hotspot_creation() {
748        let hotspot = AllocationHotspot {
749            var_name: "my_vec".to_string(),
750            type_name: "Vec<u8>".to_string(),
751            total_size: 1024,
752            allocation_count: 10,
753            location: Some("main.rs:42".to_string()),
754        };
755
756        assert_eq!(hotspot.var_name, "my_vec");
757        assert_eq!(hotspot.total_size, 1024);
758    }
759
760    #[test]
761    fn test_allocation_hotspot_clone() {
762        let hotspot = AllocationHotspot {
763            var_name: "data".to_string(),
764            type_name: "String".to_string(),
765            total_size: 100,
766            allocation_count: 5,
767            location: None,
768        };
769
770        let cloned = hotspot.clone();
771        assert_eq!(cloned.var_name, "data");
772    }
773
774    #[test]
775    fn test_allocation_hotspot_debug() {
776        let hotspot = AllocationHotspot {
777            var_name: "test".to_string(),
778            type_name: "i32".to_string(),
779            total_size: 4,
780            allocation_count: 1,
781            location: None,
782        };
783
784        let debug_str = format!("{:?}", hotspot);
785        assert!(debug_str.contains("AllocationHotspot"));
786    }
787
788    #[test]
789    fn test_tracker_clone() {
790        let tracker = Tracker::new();
791        let cloned = tracker.clone();
792
793        let report1 = tracker.analyze();
794        let report2 = cloned.analyze();
795
796        // Both should have the same underlying data
797        assert_eq!(report1.total_allocations, report2.total_allocations);
798    }
799
800    #[test]
801    fn test_tracker_with_sampling() {
802        let tracker = Tracker::new().with_sampling(SamplingConfig::high_performance());
803        let data = vec![1, 2, 3];
804        tracker.track_as(&data, "data", "test.rs", 1);
805    }
806
807    #[test]
808    fn test_tracker_elapsed() {
809        let tracker = Tracker::new();
810        std::thread::sleep(std::time::Duration::from_millis(10));
811        let elapsed = tracker.elapsed();
812        assert!(elapsed >= std::time::Duration::from_millis(10));
813    }
814
815    #[test]
816    fn test_tracker_with_system_monitoring() {
817        let tracker = Tracker::new().with_system_monitoring();
818        let _ = tracker.current_system_snapshot();
819    }
820
821    #[test]
822    fn test_tracker_track_as_multiple() {
823        let tracker = Tracker::new();
824        let data = vec![1, 2, 3, 4, 5];
825
826        tracker.track_as(&data, "my_vec", "test.rs", 10);
827        tracker.track_as(&data, "my_vec", "test.rs", 20);
828
829        let report = tracker.analyze();
830        let _ = report.total_allocations;
831    }
832
833    #[test]
834    fn test_sampling_config_custom() {
835        let config = SamplingConfig {
836            sample_rate: 0.5,
837            capture_call_stack: true,
838            max_stack_depth: 15,
839        };
840
841        assert!((config.sample_rate - 0.5).abs() < 0.001);
842        assert!(config.capture_call_stack);
843        assert_eq!(config.max_stack_depth, 15);
844    }
845
846    #[test]
847    fn test_analysis_report_with_hotspots() {
848        let report = AnalysisReport {
849            total_allocations: 100,
850            total_deallocations: 50,
851            active_allocations: 50,
852            peak_memory_bytes: 1024 * 1024,
853            current_memory_bytes: 512 * 1024,
854            allocation_rate_per_sec: 10.0,
855            deallocation_rate_per_sec: 5.0,
856            hotspots: vec![AllocationHotspot {
857                var_name: "test".to_string(),
858                type_name: "Vec<u8>".to_string(),
859                total_size: 1024,
860                allocation_count: 10,
861                location: Some("test.rs:1".to_string()),
862            }],
863            system_snapshots: vec![],
864        };
865
866        assert_eq!(report.hotspots.len(), 1);
867    }
868
869    #[test]
870    fn test_tracker_with_sampling_and_monitoring() {
871        let tracker = Tracker::new()
872            .with_sampling(SamplingConfig::demo())
873            .with_system_monitoring();
874
875        let data = vec![1, 2, 3];
876        tracker.track_as(&data, "data", "test.rs", 1);
877
878        let snapshot = tracker.current_system_snapshot();
879        assert!(snapshot.cpu_usage_percent >= 0.0);
880    }
881
882    #[test]
883    fn test_tracker_events() {
884        let tracker = Tracker::new();
885        let data = vec![1, 2, 3];
886        tracker.track_as(&data, "test_data", "test.rs", 1);
887
888        let events = tracker.events();
889        assert!(!events.is_empty());
890    }
891
892    #[test]
893    fn test_tracker_event_store() {
894        let tracker = Tracker::new();
895        let _store = tracker.event_store();
896    }
897
898    #[test]
899    fn test_tracker_stats() {
900        let tracker = Tracker::new();
901        let stats = tracker.stats();
902
903        assert_eq!(stats.total_allocations, 0);
904        assert_eq!(stats.active_allocations, 0);
905    }
906
907    #[test]
908    fn test_tracker_stats_with_data() {
909        let tracker = Tracker::new();
910        let data = vec![1u8; 1024];
911        tracker.track_as(&data, "buffer", "test.rs", 1);
912
913        let stats = tracker.stats();
914        assert!(
915            stats.total_allocations >= 1,
916            "Should have at least one allocation after tracking data"
917        );
918    }
919
920    #[test]
921    fn test_tracker_system_snapshots() {
922        let tracker = Tracker::new().with_system_monitoring();
923        let snapshots = tracker.system_snapshots();
924        assert!(!snapshots.is_empty());
925    }
926
927    #[test]
928    fn test_tracker_inner() {
929        let tracker = Tracker::new();
930        let _inner = tracker.inner();
931    }
932
933    #[test]
934    fn test_tracker_with_auto_export() {
935        let tracker = Tracker::new().with_auto_export("/tmp/test_export");
936        let data = vec![1, 2, 3];
937        tracker.track_as(&data, "test", "test.rs", 1);
938    }
939
940    #[test]
941    fn test_sampling_config_zero_rate() {
942        let config = SamplingConfig {
943            sample_rate: 0.0,
944            capture_call_stack: false,
945            max_stack_depth: 0,
946        };
947
948        let tracker = Tracker::new().with_sampling(config);
949        let data = vec![1, 2, 3];
950        tracker.track_as(&data, "test", "test.rs", 1);
951    }
952
953    #[test]
954    fn test_analysis_report_serialization() {
955        let report = AnalysisReport {
956            total_allocations: 100,
957            total_deallocations: 50,
958            active_allocations: 50,
959            peak_memory_bytes: 1024,
960            current_memory_bytes: 512,
961            allocation_rate_per_sec: 10.0,
962            deallocation_rate_per_sec: 5.0,
963            hotspots: vec![],
964            system_snapshots: vec![],
965        };
966
967        let json = serde_json::to_string(&report);
968        assert!(json.is_ok());
969    }
970
971    #[test]
972    fn test_allocation_hotspot_serialization() {
973        let hotspot = AllocationHotspot {
974            var_name: "test".to_string(),
975            type_name: "Vec<u8>".to_string(),
976            total_size: 1024,
977            allocation_count: 10,
978            location: Some("test.rs:1".to_string()),
979        };
980
981        let json = serde_json::to_string(&hotspot);
982        assert!(json.is_ok());
983    }
984
985    #[test]
986    fn test_tracker_multiple_system_snapshots() {
987        let tracker = Tracker::new().with_system_monitoring();
988        std::thread::sleep(std::time::Duration::from_millis(10));
989        tracker.current_system_snapshot();
990
991        let snapshots = tracker.system_snapshots();
992        assert!(
993            !snapshots.is_empty(),
994            "Should have at least one system snapshot"
995        );
996    }
997
998    #[test]
999    fn test_tracker_analyze_with_hotspots() {
1000        let tracker = Tracker::new();
1001        let data1 = vec![1u8; 100];
1002        let data2 = vec![2u8; 200];
1003        let data3 = vec![3u8; 300];
1004
1005        tracker.track_as(&data1, "buffer1", "test.rs", 1);
1006        tracker.track_as(&data2, "buffer2", "test.rs", 2);
1007        tracker.track_as(&data3, "buffer3", "test.rs", 3);
1008
1009        let report = tracker.analyze();
1010        assert!(
1011            report.total_allocations >= 3,
1012            "Should have at least 3 allocations after tracking"
1013        );
1014    }
1015
1016    #[test]
1017    fn test_tracker_default() {
1018        let tracker = Tracker::default();
1019        let report = tracker.analyze();
1020        assert_eq!(report.total_allocations, 0);
1021    }
1022}