scirs2_core/memory/metrics/
mod.rs1mod 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#[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
76static GLOBAL_METRICS_COLLECTOR: Lazy<Arc<MemoryMetricsCollector>> =
78 Lazy::new(|| Arc::new(MemoryMetricsCollector::new(MemoryMetricsConfig::default())));
79
80#[allow(dead_code)]
82pub fn global_metrics_collector() -> Arc<MemoryMetricsCollector> {
83 GLOBAL_METRICS_COLLECTOR.clone()
84}
85
86#[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#[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#[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#[allow(dead_code)]
115pub fn generate_memory_report() -> MemoryReport {
116 GLOBAL_METRICS_COLLECTOR.generate_report()
117}
118
119#[allow(dead_code)]
121pub fn format_memory_report() -> String {
122 GLOBAL_METRICS_COLLECTOR.generate_report().format()
123}
124
125#[allow(dead_code)]
127pub fn reset_memory_metrics() {
128 GLOBAL_METRICS_COLLECTOR.reset();
129}
130
131pub 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 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 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(&self.component_name, size, &vec as *const _ as usize);
155
156 vec
157 }
158
159 pub fn release_vec(&mut self, vec: Vec<T>) {
161 let size = vec.capacity() * mem::size_of::<T>();
162
163 track_deallocation(&self.component_name, size, &vec as *const _ as usize);
165
166 self.inner.release_vec(vec);
167 }
168
169 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(&self.component_name, mem_size, array.as_ptr() as usize);
176
177 array
178 }
179
180 pub fn release_array(&mut self, array: crate::ndarray::Array1<T>) {
182 let size = array.len() * mem::size_of::<T>();
183
184 track_deallocation(&self.component_name, size, array.as_ptr() as usize);
186
187 self.inner.release_array(array);
188 }
189
190 pub fn clear(&mut self) {
192 self.inner.clear();
193 }
194}
195
196pub 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 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 pub fn process_chunks_dyn<F>(&mut self, mut f: F)
225 where
226 F: FnMut(&ArrayBase<ViewRepr<&A>, IxDyn>, IxDyn),
227 {
228 let component_name = self.component_name.clone();
230 let tracked_f = move |chunk: &ArrayBase<ViewRepr<&A>, IxDyn>, coords: IxDyn| {
231 let size = chunk.len() * mem::size_of::<A>();
233
234 let address = chunk.as_ptr() as usize;
236 track_allocation(&component_name, size, address);
237
238 f(chunk, coords);
240
241 track_deallocation(&component_name, size, address);
243 };
244
245 self.inner.process_chunks_dyn(tracked_f);
247 }
248
249 pub fn num_chunks(&self) -> usize {
251 self.inner.num_chunks()
252 }
253}
254
255pub 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 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 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 let component_name = self.component_name.clone();
287 let tracked_f = move |chunk: &ArrayBase<ViewRepr<&A>, crate::ndarray::Ix2>,
288 coords: (usize, usize)| {
289 let size = chunk.len() * mem::size_of::<A>();
291
292 let address = chunk.as_ptr() as usize;
294 track_allocation(&component_name, size, address);
295
296 f(chunk, coords);
298
299 track_deallocation(&component_name, size, address);
301 };
302
303 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 let lock = MEMORY_METRICS_TEST_MUTEX
317 .lock()
318 .unwrap_or_else(|poisoned| poisoned.into_inner());
319
320 reset_memory_metrics();
322
323 track_allocation("TestComponent", 1024, 0x1000);
325 track_allocation("TestComponent", 2048, 0x2000);
326 track_allocation("OtherComponent", 4096, 0x3000);
327
328 track_deallocation("TestComponent", 1024, 0x1000);
330
331 let report = generate_memory_report();
333
334 assert_eq!(report.total_current_usage, 6144); assert_eq!(report.total_allocation_count, 3);
337
338 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_memory_metrics();
348 let report = generate_memory_report();
349 assert_eq!(report.total_current_usage, 0);
350 }
351}