scirs2_core/memory/metrics/
mod.rs

1//! Memory metrics system for tracking and analyzing memory usage
2//!
3//! This module provides functionality for tracking, collecting, and reporting
4//! memory usage metrics. It can be used to understand memory allocation patterns,
5//! identify memory leaks, and optimize memory-intensive operations.
6//!
7//! # Example
8//!
9//! ```rust,no_run
10//! use scirs2_core::memory::metrics::{MemoryMetricsCollector, MemoryMetricsConfig, MemoryEvent, MemoryEventType};
11//!
12//! // Create a metrics collector
13//! let config = MemoryMetricsConfig::default();
14//! let collector = MemoryMetricsCollector::new(config);
15//!
16//! // Record memory events
17//! collector.record_event(MemoryEvent::new(
18//!     MemoryEventType::Allocation,
19//!     "MyComponent",
20//!     1024,
21//!     0x1000,
22//! ));
23//!
24//! // Generate a report
25//! let report = collector.generate_report();
26//! println!("{}", report.format());
27//! ```
28
29mod analytics;
30mod collector;
31mod event;
32#[cfg(feature = "gpu")]
33mod gpu;
34mod profiler;
35mod reporter;
36mod snapshot;
37
38#[cfg(test)]
39mod test_utils;
40
41pub use analytics::{
42    AllocationPattern, LeakDetectionConfig, LeakDetectionResult, MemoryAnalytics,
43    MemoryEfficiencyMetrics, MemoryIssue, MemoryPatternAnalysis, OptimizationRecommendation,
44};
45pub use collector::{
46    AllocationStats, ComponentMemoryStats, MemoryMetricsCollector, MemoryMetricsConfig,
47    MemoryReport,
48};
49pub use event::{MemoryEvent, MemoryEventType};
50pub use profiler::{
51    MemoryProfiler, MemoryProfilerConfig, PerformanceImpactAnalysis, ProfilingResult,
52    ProfilingSession, ProfilingSummary, RiskAssessment,
53};
54pub use reporter::{format_bytes, format_duration};
55pub use snapshot::{
56    clear_snapshots, compare_snapshots, global_snapshot_manager, load_all_snapshots,
57    save_all_snapshots, take_snapshot, ComponentStatsDiff, MemorySnapshot, SnapshotComponentStats,
58    SnapshotDiff, SnapshotManager, SnapshotReport,
59};
60
61#[cfg(feature = "memory_visualization")]
62pub use reporter::ChartFormat;
63
64// Re-export snapshot visualization if feature is enabled
65
66#[cfg(feature = "gpu")]
67pub use gpu::{setup_gpu_memory_tracking, TrackedGpuBuffer, TrackedGpuContext};
68
69use crate::memory::{BufferPool, ChunkProcessor, ChunkProcessor2D};
70use ::ndarray::{ArrayBase, Data, Dimension, IxDyn, ViewRepr};
71use once_cell::sync::Lazy;
72use std::marker::PhantomData;
73use std::mem;
74use std::sync::Arc;
75
76/// Global memory metrics collector instance
77static GLOBAL_METRICS_COLLECTOR: Lazy<Arc<MemoryMetricsCollector>> =
78    Lazy::new(|| Arc::new(MemoryMetricsCollector::new(MemoryMetricsConfig::default())));
79
80/// Get the global memory metrics collector
81#[allow(dead_code)]
82pub fn global_metrics_collector() -> Arc<MemoryMetricsCollector> {
83    GLOBAL_METRICS_COLLECTOR.clone()
84}
85
86/// Track a memory allocation event in the global collector
87#[allow(dead_code)]
88pub fn track_allocation(component: impl Into<String>, size: usize, address: usize) {
89    let event = MemoryEvent::new(MemoryEventType::Allocation, component, size, address);
90    GLOBAL_METRICS_COLLECTOR.record_event(event);
91}
92
93/// Track a memory deallocation event in the global collector
94#[allow(dead_code)]
95pub fn track_deallocation(component: impl Into<String>, size: usize, address: usize) {
96    let event = MemoryEvent::new(MemoryEventType::Deallocation, component, size, address);
97    GLOBAL_METRICS_COLLECTOR.record_event(event);
98}
99
100/// Track a memory resize event in the global collector
101#[allow(dead_code)]
102pub fn track_resize(
103    component: impl Into<String>,
104    old_size: usize,
105    new_size: usize,
106    address: usize,
107) {
108    let event = MemoryEvent::new(MemoryEventType::Resize, component, new_size, address)
109        .with_metadata("old_size", old_size.to_string());
110    GLOBAL_METRICS_COLLECTOR.record_event(event);
111}
112
113/// Generate a memory report from the global collector
114#[allow(dead_code)]
115pub fn generate_memory_report() -> MemoryReport {
116    GLOBAL_METRICS_COLLECTOR.generate_report()
117}
118
119/// Format the current memory report as a string
120#[allow(dead_code)]
121pub fn format_memory_report() -> String {
122    GLOBAL_METRICS_COLLECTOR.generate_report().format()
123}
124
125/// Reset the global memory metrics collector
126#[allow(dead_code)]
127pub fn reset_memory_metrics() {
128    GLOBAL_METRICS_COLLECTOR.reset();
129}
130
131/// A buffer pool that automatically tracks memory allocations and deallocations
132pub struct TrackedBufferPool<T: Clone + Default> {
133    inner: BufferPool<T>,
134    component_name: String,
135    phantom: PhantomData<T>,
136}
137
138impl<T: Clone + Default> TrackedBufferPool<T> {
139    /// Create a new tracked buffer pool with the given component name
140    pub fn new(component_name: impl Into<String>) -> Self {
141        Self {
142            inner: BufferPool::new(),
143            component_name: component_name.into(),
144            phantom: PhantomData,
145        }
146    }
147
148    /// Acquire a vector from the pool, tracking the allocation
149    pub fn acquire_vec(&mut self, capacity: usize) -> Vec<T> {
150        let vec = self.inner.acquire_vec(capacity);
151        let size = capacity * mem::size_of::<T>();
152
153        // Track allocation
154        track_allocation(&self.component_name, size, &vec as *const _ as usize);
155
156        vec
157    }
158
159    /// Release a vector back to the pool, tracking the deallocation
160    pub fn release_vec(&mut self, vec: Vec<T>) {
161        let size = vec.capacity() * mem::size_of::<T>();
162
163        // Track deallocation
164        track_deallocation(&self.component_name, size, &vec as *const _ as usize);
165
166        self.inner.release_vec(vec);
167    }
168
169    /// Acquire an ndarray from the pool, tracking the allocation
170    pub fn acquire_array(&mut self, size: usize) -> crate::ndarray::Array1<T> {
171        let array = self.inner.acquire_array(size);
172        let mem_size = size * mem::size_of::<T>();
173
174        // Track allocation
175        track_allocation(&self.component_name, mem_size, array.as_ptr() as usize);
176
177        array
178    }
179
180    /// Release an ndarray back to the pool, tracking the deallocation
181    pub fn release_array(&mut self, array: crate::ndarray::Array1<T>) {
182        let size = array.len() * mem::size_of::<T>();
183
184        // Track deallocation
185        track_deallocation(&self.component_name, size, array.as_ptr() as usize);
186
187        self.inner.release_array(array);
188    }
189
190    /// Clear the pool, releasing all memory
191    pub fn clear(&mut self) {
192        self.inner.clear();
193    }
194}
195
196/// A chunk processor that tracks memory usage during processing
197pub struct TrackedChunkProcessor<'a, A, S, D>
198where
199    S: Data<Elem = A>,
200    D: Dimension,
201{
202    inner: ChunkProcessor<'a, A, S, D>,
203    component_name: String,
204}
205
206impl<'a, A, S, D> TrackedChunkProcessor<'a, A, S, D>
207where
208    S: Data<Elem = A>,
209    D: Dimension,
210{
211    /// Create a new tracked chunk processor
212    pub fn new(
213        component_name: impl Into<String>,
214        array: &'a ArrayBase<S, D>,
215        chunkshape: &[usize],
216    ) -> Self {
217        Self {
218            inner: ChunkProcessor::new(array, array.raw_dim()),
219            component_name: component_name.into(),
220        }
221    }
222
223    /// Process the array in chunks, tracking memory usage for each chunk
224    pub fn process_chunks_dyn<F>(&mut self, mut f: F)
225    where
226        F: FnMut(&ArrayBase<ViewRepr<&A>, IxDyn>, IxDyn),
227    {
228        // Create a wrapper function that tracks memory
229        let component_name = self.component_name.clone();
230        let tracked_f = move |chunk: &ArrayBase<ViewRepr<&A>, IxDyn>, coords: IxDyn| {
231            // Calculate memory size (approximate)
232            let size = chunk.len() * mem::size_of::<A>();
233
234            // Track memory allocation for the chunk
235            let address = chunk.as_ptr() as usize;
236            track_allocation(&component_name, size, address);
237
238            // Call the original function
239            f(chunk, coords);
240
241            // Track memory deallocation
242            track_deallocation(&component_name, size, address);
243        };
244
245        // Process chunks with the tracked function
246        self.inner.process_chunks_dyn(tracked_f);
247    }
248
249    /// Get the total number of chunks
250    pub fn num_chunks(&self) -> usize {
251        self.inner.num_chunks()
252    }
253}
254
255/// A 2D chunk processor that tracks memory usage during processing
256pub struct TrackedChunkProcessor2D<'a, A, S>
257where
258    S: Data<Elem = A>,
259{
260    inner: ChunkProcessor2D<'a, A, S>,
261    component_name: String,
262}
263
264impl<'a, A, S> TrackedChunkProcessor2D<'a, A, S>
265where
266    S: Data<Elem = A>,
267{
268    /// Create a new tracked 2D chunk processor
269    pub fn new(
270        array: &'a ArrayBase<S, crate::ndarray::Ix2>,
271        chunkshape: (usize, usize),
272        component_name: impl Into<String>,
273    ) -> Self {
274        Self {
275            inner: ChunkProcessor2D::new(array, chunkshape),
276            component_name: component_name.into(),
277        }
278    }
279
280    /// Process the 2D array in chunks, tracking memory usage for each chunk
281    pub fn process_chunks<F>(&mut self, mut f: F)
282    where
283        F: FnMut(&ArrayBase<ViewRepr<&A>, crate::ndarray::Ix2>, (usize, usize)),
284    {
285        // Create a wrapper function that tracks memory
286        let component_name = self.component_name.clone();
287        let tracked_f = move |chunk: &ArrayBase<ViewRepr<&A>, crate::ndarray::Ix2>,
288                              coords: (usize, usize)| {
289            // Calculate memory size (approximate)
290            let size = chunk.len() * mem::size_of::<A>();
291
292            // Track memory allocation for the chunk
293            let address = chunk.as_ptr() as usize;
294            track_allocation(&component_name, size, address);
295
296            // Call the original function
297            f(chunk, coords);
298
299            // Track memory deallocation
300            track_deallocation(&component_name, size, address);
301        };
302
303        // Process chunks with the tracked function
304        self.inner.process_chunks(tracked_f);
305    }
306}
307
308#[cfg(test)]
309mod tests {
310    use super::test_utils::MEMORY_METRICS_TEST_MUTEX;
311    use super::*;
312
313    #[test]
314    fn test_global_memory_metrics() {
315        // Lock the mutex to ensure test isolation
316        let lock = MEMORY_METRICS_TEST_MUTEX
317            .lock()
318            .unwrap_or_else(|poisoned| poisoned.into_inner());
319
320        // Reset metrics to start with clean state
321        reset_memory_metrics();
322
323        // Track some allocations
324        track_allocation("TestComponent", 1024, 0x1000);
325        track_allocation("TestComponent", 2048, 0x2000);
326        track_allocation("OtherComponent", 4096, 0x3000);
327
328        // Track a deallocation
329        track_deallocation("TestComponent", 1024, 0x1000);
330
331        // Generate a report
332        let report = generate_memory_report();
333
334        // Verify metrics
335        assert_eq!(report.total_current_usage, 6144); // 2048 + 4096
336        assert_eq!(report.total_allocation_count, 3);
337
338        // Check component stats
339        let test_comp = report
340            .component_stats
341            .get("TestComponent")
342            .expect("Operation failed");
343        assert_eq!(test_comp.current_usage, 2048);
344        assert_eq!(test_comp.allocation_count, 2);
345
346        // Reset and verify
347        reset_memory_metrics();
348        let report = generate_memory_report();
349        assert_eq!(report.total_current_usage, 0);
350    }
351}