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>(
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                // Use current timestamp for randomness to ensure sampling works
238                // correctly even with identical variable names in a loop
239                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    /// Track a clone operation
261    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                // Only HeapOwner gets tracked in inner tracker
305                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                // StackOwner records stack pointer metadata for clone detection
334                // Use stack_ptr as key for track_allocation to avoid overwriting Arc clones
335                // This allows inner tracker to count allocations while preserving clone detection
336
337                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                // Store stack pointer in custom metadata for clone detection
349                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                // Container and Value record metadata events without heap allocation
369                // They will be tracked as graph nodes but not scanned by HeapScanner
370                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        // Only record event if deallocation was successful (ptr was tracked)
390        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        // Auto-record deallocation for all active allocations
557        let events = self.event_store().snapshot();
558        let allocations = rebuild_allocations_from_events(&events);
559
560        // Record deallocation events for all active allocations
561        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                    // Use event_store as unified data source (includes both HeapOwner and Container allocations)
572                    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    #[test]
694    #[cfg(target_os = "macos")]
695    fn test_system_monitoring() {
696        std::thread::sleep(std::time::Duration::from_millis(200));
697
698        let cpu = system_monitor::cpu_usage();
699        let mem = system_monitor::memory_used();
700        let total = system_monitor::memory_total();
701
702        println!("CPU: {:.2}%", cpu);
703        println!("Memory: {} / {} bytes", mem, total);
704
705        assert!((0.0..=100.0).contains(&cpu));
706        assert!(total > 0);
707    }
708
709    #[test]
710    fn test_current_system_snapshot() {
711        std::thread::sleep(std::time::Duration::from_millis(150));
712
713        let tracker = tracker!();
714        let snapshot = tracker.current_system_snapshot();
715
716        println!(
717            "Snapshot: CPU={:.2}%, Mem={:.2}%",
718            snapshot.cpu_usage_percent, snapshot.memory_usage_percent
719        );
720
721        assert!(snapshot.cpu_usage_percent >= 0.0 && snapshot.cpu_usage_percent <= 100.0);
722    }
723
724    #[test]
725    fn test_sampling_config_default() {
726        let config = SamplingConfig::default();
727        assert_eq!(config.sample_rate, 1.0);
728        assert!(!config.capture_call_stack);
729        assert_eq!(config.max_stack_depth, 10);
730    }
731
732    #[test]
733    fn test_sampling_config_demo() {
734        let config = SamplingConfig::demo();
735        assert_eq!(config.sample_rate, 0.1);
736        assert!(!config.capture_call_stack);
737        assert_eq!(config.max_stack_depth, 5);
738    }
739
740    #[test]
741    fn test_sampling_config_full() {
742        let config = SamplingConfig::full();
743        assert_eq!(config.sample_rate, 1.0);
744        assert!(config.capture_call_stack);
745        assert_eq!(config.max_stack_depth, 20);
746    }
747
748    #[test]
749    fn test_sampling_config_high_performance() {
750        let config = SamplingConfig::high_performance();
751        assert_eq!(config.sample_rate, 0.01);
752        assert!(!config.capture_call_stack);
753        assert_eq!(config.max_stack_depth, 0);
754    }
755
756    #[test]
757    fn test_sampling_config_clone() {
758        let config = SamplingConfig::full();
759        let cloned = config.clone();
760        assert_eq!(cloned.sample_rate, config.sample_rate);
761        assert_eq!(cloned.capture_call_stack, config.capture_call_stack);
762    }
763
764    #[test]
765    fn test_sampling_config_debug() {
766        let config = SamplingConfig::default();
767        let debug_str = format!("{:?}", config);
768        assert!(debug_str.contains("SamplingConfig"));
769        assert!(debug_str.contains("sample_rate"));
770    }
771
772    #[test]
773    fn test_system_snapshot_default() {
774        let snapshot = SystemSnapshot::default();
775        assert_eq!(snapshot.timestamp, 0);
776        assert_eq!(snapshot.cpu_usage_percent, 0.0);
777        assert_eq!(snapshot.memory_usage_bytes, 0);
778        assert_eq!(snapshot.thread_count, 0);
779    }
780
781    #[test]
782    fn test_system_snapshot_clone() {
783        let snapshot = SystemSnapshot {
784            timestamp: 1000,
785            cpu_usage_percent: 50.0,
786            memory_usage_bytes: 1024 * 1024,
787            memory_usage_percent: 25.0,
788            thread_count: 4,
789            disk_read_bps: 1000,
790            disk_write_bps: 500,
791            network_rx_bps: 2000,
792            network_tx_bps: 1000,
793            gpu_usage_percent: 30.0,
794            gpu_memory_used: 512 * 1024 * 1024,
795            gpu_memory_total: 2 * 1024 * 1024 * 1024,
796        };
797
798        let cloned = snapshot.clone();
799        assert_eq!(cloned.timestamp, 1000);
800        assert_eq!(cloned.cpu_usage_percent, 50.0);
801    }
802
803    #[test]
804    fn test_system_snapshot_debug() {
805        let snapshot = SystemSnapshot::default();
806        let debug_str = format!("{:?}", snapshot);
807        assert!(debug_str.contains("SystemSnapshot"));
808    }
809
810    #[test]
811    fn test_analysis_report_creation() {
812        let report = AnalysisReport {
813            total_allocations: 100,
814            total_deallocations: 50,
815            active_allocations: 50,
816            peak_memory_bytes: 1024 * 1024,
817            current_memory_bytes: 512 * 1024,
818            allocation_rate_per_sec: 10.0,
819            deallocation_rate_per_sec: 5.0,
820            hotspots: vec![],
821            system_snapshots: vec![],
822        };
823
824        assert_eq!(report.total_allocations, 100);
825        assert_eq!(report.active_allocations, 50);
826    }
827
828    #[test]
829    fn test_analysis_report_clone() {
830        let report = AnalysisReport {
831            total_allocations: 10,
832            total_deallocations: 5,
833            active_allocations: 5,
834            peak_memory_bytes: 1024,
835            current_memory_bytes: 512,
836            allocation_rate_per_sec: 1.0,
837            deallocation_rate_per_sec: 0.5,
838            hotspots: vec![],
839            system_snapshots: vec![],
840        };
841
842        let cloned = report.clone();
843        assert_eq!(cloned.total_allocations, 10);
844    }
845
846    #[test]
847    fn test_analysis_report_debug() {
848        let report = AnalysisReport {
849            total_allocations: 0,
850            total_deallocations: 0,
851            active_allocations: 0,
852            peak_memory_bytes: 0,
853            current_memory_bytes: 0,
854            allocation_rate_per_sec: 0.0,
855            deallocation_rate_per_sec: 0.0,
856            hotspots: vec![],
857            system_snapshots: vec![],
858        };
859
860        let debug_str = format!("{:?}", report);
861        assert!(debug_str.contains("AnalysisReport"));
862    }
863
864    #[test]
865    fn test_allocation_hotspot_creation() {
866        let hotspot = AllocationHotspot {
867            var_name: "my_vec".to_string(),
868            type_name: "Vec<u8>".to_string(),
869            total_size: 1024,
870            allocation_count: 10,
871            location: Some("main.rs:42".to_string()),
872        };
873
874        assert_eq!(hotspot.var_name, "my_vec");
875        assert_eq!(hotspot.total_size, 1024);
876    }
877
878    #[test]
879    fn test_allocation_hotspot_clone() {
880        let hotspot = AllocationHotspot {
881            var_name: "data".to_string(),
882            type_name: "String".to_string(),
883            total_size: 100,
884            allocation_count: 5,
885            location: None,
886        };
887
888        let cloned = hotspot.clone();
889        assert_eq!(cloned.var_name, "data");
890    }
891
892    #[test]
893    fn test_allocation_hotspot_debug() {
894        let hotspot = AllocationHotspot {
895            var_name: "test".to_string(),
896            type_name: "i32".to_string(),
897            total_size: 4,
898            allocation_count: 1,
899            location: None,
900        };
901
902        let debug_str = format!("{:?}", hotspot);
903        assert!(debug_str.contains("AllocationHotspot"));
904    }
905
906    #[test]
907    fn test_tracker_clone() {
908        let tracker = Tracker::new();
909        let cloned = tracker.clone();
910
911        let report1 = tracker.analyze();
912        let report2 = cloned.analyze();
913
914        // Both should have the same underlying data
915        assert_eq!(report1.total_allocations, report2.total_allocations);
916    }
917
918    #[test]
919    fn test_tracker_with_sampling() {
920        let tracker = Tracker::new().with_sampling(SamplingConfig::high_performance());
921        let data = vec![1, 2, 3];
922        tracker.track_as(&data, "data", "test.rs", 1, "test_module");
923    }
924
925    #[test]
926    fn test_tracker_elapsed() {
927        let tracker = Tracker::new();
928        std::thread::sleep(std::time::Duration::from_millis(10));
929        let elapsed = tracker.elapsed();
930        assert!(elapsed >= std::time::Duration::from_millis(10));
931    }
932
933    #[test]
934    fn test_tracker_with_system_monitoring() {
935        let tracker = Tracker::new().with_system_monitoring();
936        let _ = tracker.current_system_snapshot();
937    }
938
939    #[test]
940    fn test_tracker_track_as_multiple() {
941        let tracker = Tracker::new();
942        let data = vec![1, 2, 3, 4, 5];
943
944        tracker.track_as(&data, "my_vec", "test.rs", 10, "test_module");
945        tracker.track_as(&data, "my_vec", "test.rs", 20, "test_module");
946
947        let report = tracker.analyze();
948        let _ = report.total_allocations;
949    }
950
951    #[test]
952    fn test_sampling_config_custom() {
953        let config = SamplingConfig {
954            sample_rate: 0.5,
955            capture_call_stack: true,
956            max_stack_depth: 15,
957        };
958
959        assert!((config.sample_rate - 0.5).abs() < 0.001);
960        assert!(config.capture_call_stack);
961        assert_eq!(config.max_stack_depth, 15);
962    }
963
964    #[test]
965    fn test_analysis_report_with_hotspots() {
966        let report = AnalysisReport {
967            total_allocations: 100,
968            total_deallocations: 50,
969            active_allocations: 50,
970            peak_memory_bytes: 1024 * 1024,
971            current_memory_bytes: 512 * 1024,
972            allocation_rate_per_sec: 10.0,
973            deallocation_rate_per_sec: 5.0,
974            hotspots: vec![AllocationHotspot {
975                var_name: "test".to_string(),
976                type_name: "Vec<u8>".to_string(),
977                total_size: 1024,
978                allocation_count: 10,
979                location: Some("test.rs:1".to_string()),
980            }],
981            system_snapshots: vec![],
982        };
983
984        assert_eq!(report.hotspots.len(), 1);
985    }
986
987    #[test]
988    fn test_tracker_with_sampling_and_monitoring() {
989        let tracker = Tracker::new()
990            .with_sampling(SamplingConfig::demo())
991            .with_system_monitoring();
992
993        let data = vec![1, 2, 3];
994        tracker.track_as(&data, "data", "test.rs", 1, "test_module");
995
996        let snapshot = tracker.current_system_snapshot();
997        assert!(snapshot.cpu_usage_percent >= 0.0);
998    }
999
1000    #[test]
1001    fn test_tracker_events() {
1002        let tracker = Tracker::new();
1003        let data = vec![1, 2, 3];
1004        tracker.track_as(&data, "test_data", "test.rs", 1, "test_module");
1005
1006        let events = tracker.events();
1007        assert!(!events.is_empty());
1008    }
1009
1010    #[test]
1011    fn test_tracker_event_store() {
1012        let tracker = Tracker::new();
1013        let _store = tracker.event_store();
1014    }
1015
1016    #[test]
1017    fn test_tracker_stats() {
1018        let tracker = Tracker::new();
1019        let stats = tracker.stats();
1020
1021        assert_eq!(stats.total_allocations, 0);
1022        assert_eq!(stats.active_allocations, 0);
1023    }
1024
1025    #[test]
1026    fn test_tracker_stats_with_data() {
1027        let tracker = Tracker::new();
1028        let data = vec![1u8; 1024];
1029        tracker.track_as(&data, "buffer", "test.rs", 1, "test_module");
1030
1031        let stats = tracker.stats();
1032        assert!(
1033            stats.total_allocations >= 1,
1034            "Should have at least one allocation after tracking data"
1035        );
1036    }
1037
1038    #[test]
1039    fn test_tracker_system_snapshots() {
1040        let tracker = Tracker::new().with_system_monitoring();
1041        let snapshots = tracker.system_snapshots();
1042        assert!(!snapshots.is_empty());
1043    }
1044
1045    #[test]
1046    fn test_tracker_inner() {
1047        let tracker = Tracker::new();
1048        let _inner = tracker.inner();
1049    }
1050
1051    #[test]
1052    fn test_tracker_with_auto_export() {
1053        let tracker = Tracker::new().with_auto_export("/tmp/test_export");
1054        let data = vec![1, 2, 3];
1055        tracker.track_as(&data, "test", "test.rs", 1, "test_module");
1056    }
1057
1058    #[test]
1059    fn test_sampling_config_zero_rate() {
1060        let config = SamplingConfig {
1061            sample_rate: 0.0,
1062            capture_call_stack: false,
1063            max_stack_depth: 0,
1064        };
1065
1066        let tracker = Tracker::new().with_sampling(config);
1067        let data = vec![1, 2, 3];
1068        tracker.track_as(&data, "test", "test.rs", 1, "test_module");
1069    }
1070
1071    #[test]
1072    fn test_analysis_report_serialization() {
1073        let report = AnalysisReport {
1074            total_allocations: 100,
1075            total_deallocations: 50,
1076            active_allocations: 50,
1077            peak_memory_bytes: 1024,
1078            current_memory_bytes: 512,
1079            allocation_rate_per_sec: 10.0,
1080            deallocation_rate_per_sec: 5.0,
1081            hotspots: vec![],
1082            system_snapshots: vec![],
1083        };
1084
1085        let json = serde_json::to_string(&report);
1086        assert!(json.is_ok());
1087    }
1088
1089    #[test]
1090    fn test_allocation_hotspot_serialization() {
1091        let hotspot = AllocationHotspot {
1092            var_name: "test".to_string(),
1093            type_name: "Vec<u8>".to_string(),
1094            total_size: 1024,
1095            allocation_count: 10,
1096            location: Some("test.rs:1".to_string()),
1097        };
1098
1099        let json = serde_json::to_string(&hotspot);
1100        assert!(json.is_ok());
1101    }
1102
1103    #[test]
1104    fn test_tracker_multiple_system_snapshots() {
1105        let tracker = Tracker::new().with_system_monitoring();
1106        std::thread::sleep(std::time::Duration::from_millis(10));
1107        tracker.current_system_snapshot();
1108
1109        let snapshots = tracker.system_snapshots();
1110        assert!(
1111            !snapshots.is_empty(),
1112            "Should have at least one system snapshot"
1113        );
1114    }
1115
1116    #[test]
1117    fn test_tracker_analyze_with_hotspots() {
1118        let tracker = Tracker::new();
1119        let data1 = vec![1u8; 100];
1120        let data2 = vec![2u8; 200];
1121        let data3 = vec![3u8; 300];
1122
1123        tracker.track_as(&data1, "buffer1", "test.rs", 1, "test_module");
1124        tracker.track_as(&data2, "buffer2", "test.rs", 2, "test_module");
1125        tracker.track_as(&data3, "buffer3", "test.rs", 3, "test_module");
1126
1127        let report = tracker.analyze();
1128        assert!(
1129            report.total_allocations >= 3,
1130            "Should have at least 3 allocations after tracking"
1131        );
1132    }
1133
1134    #[test]
1135    fn test_tracker_default() {
1136        let tracker = Tracker::default();
1137        let report = tracker.analyze();
1138        assert_eq!(report.total_allocations, 0);
1139    }
1140}