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