scirs2_core/memory/metrics/
collector.rs

1//! Memory metrics collection and analysis
2//!
3//! This module provides functionality for collecting and analyzing memory usage metrics.
4
5use std::collections::{HashMap, VecDeque};
6use std::sync::{Mutex, RwLock};
7use std::time::{Duration, Instant};
8
9use crate::memory::metrics::event::{MemoryEvent, MemoryEventType};
10use rand::prelude::*;
11use rand::Rng;
12#[cfg(feature = "memory_metrics")]
13#[cfg(feature = "serialization")]
14use serde::{Deserialize, Serialize};
15
16// Define a simple Random struct for sampling when the random feature is not enabled
17struct Random {
18    rng: StdRng,
19}
20
21impl Default for Random {
22    fn default() -> Self {
23        Self {
24            rng: StdRng::seed_from_u64(0), // Use a fixed seed for simplicity
25        }
26    }
27}
28
29impl Random {
30    fn gen_range(&mut self, range: std::ops::Range<f64>) -> f64 {
31        self.rng.gen_range(range)
32    }
33}
34
35/// Memory metrics configuration
36#[derive(Debug, Clone)]
37pub struct MemoryMetricsConfig {
38    /// Whether to collect events
39    pub enabled: bool,
40    /// Whether to capture call stacks (requires memory_call_stack feature)
41    pub capture_call_stacks: bool,
42    /// Maximum number of events to store
43    pub max_events: usize,
44    /// Whether to aggregate events in real-time
45    pub real_time_aggregation: bool,
46    /// Event sampling rate (1.0 = all events, 0.1 = 10% of events)
47    pub samplingrate: f64,
48}
49
50impl Default for MemoryMetricsConfig {
51    fn default() -> Self {
52        Self {
53            enabled: true,
54            capture_call_stacks: cfg!(feature = "memory_call_stack"),
55            max_events: 10000,
56            real_time_aggregation: true,
57            samplingrate: 1.0,
58        }
59    }
60}
61
62/// Allocation statistics for a component
63#[derive(Debug, Clone)]
64pub struct AllocationStats {
65    /// Number of allocations
66    pub count: usize,
67    /// Total bytes allocated
68    pub total_bytes: usize,
69    /// Average allocation size
70    pub average_size: f64,
71    /// Peak memory usage
72    pub peak_usage: usize,
73}
74
75/// Component memory statistics
76#[derive(Debug, Clone)]
77#[cfg_attr(feature = "memory_metrics", derive(Serialize, Deserialize))]
78pub struct ComponentMemoryStats {
79    /// Current memory usage
80    pub current_usage: usize,
81    /// Peak memory usage
82    pub peak_usage: usize,
83    /// Number of allocations
84    pub allocation_count: usize,
85    /// Total bytes allocated (including released memory)
86    pub total_allocated: usize,
87    /// Average allocation size
88    pub avg_allocation_size: f64,
89}
90
91/// Memory usage report
92#[derive(Debug, Clone)]
93#[cfg_attr(
94    feature = "memory_metrics",
95    derive(serde::Serialize, serde::Deserialize)
96)]
97pub struct MemoryReport {
98    /// Total current memory usage across all components
99    pub total_current_usage: usize,
100    /// Total peak memory usage across all components
101    pub total_peak_usage: usize,
102    /// Total number of allocations across all components
103    pub total_allocation_count: usize,
104    /// Total bytes allocated across all components
105    pub total_allocated_bytes: usize,
106    /// Component-specific statistics
107    pub component_stats: HashMap<String, ComponentMemoryStats>,
108    /// Duration since tracking started
109    pub duration: Duration,
110}
111
112/// Memory metrics collector for tracking and analyzing memory usage
113pub struct MemoryMetricsCollector {
114    /// Configuration
115    config: MemoryMetricsConfig,
116    /// Collected events (if not aggregated)
117    events: RwLock<VecDeque<MemoryEvent>>,
118    /// Current memory usage by component
119    current_usage: RwLock<HashMap<String, usize>>,
120    /// Peak memory usage by component
121    peak_usage: RwLock<HashMap<String, usize>>,
122    /// Allocation count by component
123    allocation_count: RwLock<HashMap<String, usize>>,
124    /// Total allocated bytes by component
125    total_allocated: RwLock<HashMap<String, usize>>,
126    /// Average allocation size by component
127    avg_allocation_size: RwLock<HashMap<String, f64>>,
128    /// Start time
129    start_time: Instant,
130    /// Random number generator for sampling
131    rng: Mutex<Random>,
132}
133
134impl MemoryMetricsCollector {
135    /// Create a new memory metrics collector
136    pub fn new(config: MemoryMetricsConfig) -> Self {
137        Self {
138            config,
139            events: RwLock::new(VecDeque::with_capacity(1000)),
140            current_usage: RwLock::new(HashMap::new()),
141            peak_usage: RwLock::new(HashMap::new()),
142            allocation_count: RwLock::new(HashMap::new()),
143            total_allocated: RwLock::new(HashMap::new()),
144            avg_allocation_size: RwLock::new(HashMap::new()),
145            start_time: Instant::now(),
146            rng: Mutex::new(Random::default()),
147        }
148    }
149
150    /// Record a memory event
151    pub fn record_event(&self, event: MemoryEvent) {
152        if !self.config.enabled {
153            return;
154        }
155
156        // Sample events if sampling rate < 1.0
157        if self.config.samplingrate < 1.0 {
158            let mut rng = self.rng.lock().expect("Operation failed");
159            if rng.gen_range(0.0..1.0) > self.config.samplingrate {
160                return;
161            }
162        }
163
164        // Update aggregated metrics in real-time if enabled
165        if self.config.real_time_aggregation {
166            self.update_metrics(&event);
167        }
168
169        // Store the event if we're keeping raw events
170        if self.config.max_events > 0 {
171            let mut events = self.events.write().expect("Operation failed");
172            events.push_back(event);
173
174            // Limit the number of stored events
175            while events.len() > self.config.max_events {
176                events.pop_front();
177            }
178        }
179    }
180
181    /// Update aggregated metrics based on an event
182    fn update_metrics(&self, event: &MemoryEvent) {
183        match event.event_type {
184            MemoryEventType::Allocation => {
185                // Update current usage
186                let mut current_usage = self.current_usage.write().expect("Operation failed");
187                let component_usage = current_usage.entry(event.component.clone()).or_insert(0);
188                *component_usage += event.size;
189
190                // Update peak usage if current > peak
191                let mut peak_usage = self.peak_usage.write().expect("Operation failed");
192                let peak = peak_usage.entry(event.component.clone()).or_insert(0);
193                *peak = (*peak).max(*component_usage);
194
195                // Update allocation count
196                let mut allocation_count = self.allocation_count.write().expect("Operation failed");
197                let count = allocation_count.entry(event.component.clone()).or_insert(0);
198                *count += 1;
199
200                // Update total allocated
201                let mut total_allocated = self.total_allocated.write().expect("Operation failed");
202                let total = total_allocated.entry(event.component.clone()).or_insert(0);
203                *total += event.size;
204
205                // Update average allocation size
206                let mut avg_allocation_size =
207                    self.avg_allocation_size.write().expect("Operation failed");
208                let avg = avg_allocation_size
209                    .entry(event.component.clone())
210                    .or_insert(0.0);
211                *avg = (*avg * (*count as f64 - 1.0) + event.size as f64) / *count as f64;
212            }
213            MemoryEventType::Deallocation => {
214                // Update current usage
215                let mut current_usage = self.current_usage.write().expect("Operation failed");
216                let component_usage = current_usage.entry(event.component.clone()).or_insert(0);
217                *component_usage = component_usage.saturating_sub(event.size);
218            }
219            MemoryEventType::Resize => {
220                // Handle resize events (could be positive or negative change)
221                if let Some(old_size) = event
222                    .metadata
223                    .get("old_size")
224                    .and_then(|s| s.parse::<usize>().ok())
225                {
226                    let size_diff = event.size as isize - old_size as isize;
227
228                    let mut current_usage = self.current_usage.write().expect("Operation failed");
229                    let component_usage = current_usage.entry(event.component.clone()).or_insert(0);
230
231                    if size_diff > 0 {
232                        *component_usage += size_diff as usize;
233                    } else {
234                        *component_usage = component_usage.saturating_sub((-size_diff) as usize);
235                    }
236
237                    // Update peak usage if needed
238                    let mut peak_usage = self.peak_usage.write().expect("Operation failed");
239                    let peak = peak_usage.entry(event.component.clone()).or_insert(0);
240                    *peak = (*peak).max(*component_usage);
241                }
242            }
243            MemoryEventType::Access | MemoryEventType::Transfer => {
244                // These event types don't affect memory usage metrics
245            }
246        }
247    }
248
249    /// Get the current memory usage for a specific component
250    pub fn get_current_usage(&self, component: &str) -> usize {
251        let current_usage = self.current_usage.read().expect("Operation failed");
252        *current_usage.get(component).unwrap_or(&0)
253    }
254
255    /// Get the peak memory usage for a specific component
256    pub fn get_peak_usage(&self, component: &str) -> usize {
257        let peak_usage = self.peak_usage.read().expect("Operation failed");
258        *peak_usage.get(component).unwrap_or(&0)
259    }
260
261    /// Get total memory usage across all components
262    pub fn get_total_current_usage(&self) -> usize {
263        let current_usage = self.current_usage.read().expect("Operation failed");
264        current_usage.values().sum()
265    }
266
267    /// Get peak memory usage across all components
268    pub fn get_total_peak_usage(&self) -> usize {
269        let peak_usage = self.peak_usage.read().expect("Operation failed");
270
271        // Either sum of component peaks or peak of total current usage
272        let component_sum: usize = peak_usage.values().sum();
273
274        // In a real implementation, we'd track the total peak as well
275        // For simplicity, just return the sum of component peaks
276        component_sum
277    }
278
279    /// Get allocation statistics for a component
280    pub fn get_allocation_stats(&self, component: &str) -> Option<AllocationStats> {
281        let allocation_count = self.allocation_count.read().expect("Operation failed");
282        let count = *allocation_count.get(component)?;
283
284        let total_allocated = self.total_allocated.read().expect("Operation failed");
285        let total = *total_allocated.get(component)?;
286
287        let avg_allocation_size = self.avg_allocation_size.read().expect("Operation failed");
288        let avg = *avg_allocation_size.get(component)?;
289
290        let peak_usage = self.peak_usage.read().expect("Operation failed");
291        let peak = *peak_usage.get(component)?;
292
293        Some(AllocationStats {
294            count,
295            total_bytes: total,
296            average_size: avg,
297            peak_usage: peak,
298        })
299    }
300
301    /// Generate a memory report
302    pub fn generate_report(&self) -> MemoryReport {
303        let current_usage = self.current_usage.read().expect("Operation failed");
304        let peak_usage = self.peak_usage.read().expect("Operation failed");
305        let allocation_count = self.allocation_count.read().expect("Operation failed");
306        let total_allocated = self.total_allocated.read().expect("Operation failed");
307        let avg_allocation_size = self.avg_allocation_size.read().expect("Operation failed");
308
309        let mut component_stats = HashMap::new();
310
311        // Collect all component names from all maps
312        let mut components = std::collections::HashSet::new();
313        components.extend(current_usage.keys().cloned());
314        components.extend(peak_usage.keys().cloned());
315        components.extend(allocation_count.keys().cloned());
316
317        // Build component stats
318        for component in components {
319            let stats = ComponentMemoryStats {
320                current_usage: *current_usage.get(&component).unwrap_or(&0),
321                peak_usage: *peak_usage.get(&component).unwrap_or(&0),
322                allocation_count: *allocation_count.get(&component).unwrap_or(&0),
323                total_allocated: *total_allocated.get(&component).unwrap_or(&0),
324                avg_allocation_size: *avg_allocation_size.get(&component).unwrap_or(&0.0),
325            };
326
327            component_stats.insert(component, stats);
328        }
329
330        MemoryReport {
331            total_current_usage: current_usage.values().sum(),
332            total_peak_usage: self.get_total_peak_usage(),
333            total_allocation_count: allocation_count.values().sum(),
334            total_allocated_bytes: total_allocated.values().sum(),
335            component_stats,
336            duration: self.start_time.elapsed(),
337        }
338    }
339
340    /// Reset all metrics
341    pub fn reset(&self) {
342        let mut events = self.events.write().expect("Operation failed");
343        events.clear();
344
345        let mut current_usage = self.current_usage.write().expect("Operation failed");
346        current_usage.clear();
347
348        let mut peak_usage = self.peak_usage.write().expect("Operation failed");
349        peak_usage.clear();
350
351        let mut allocation_count = self.allocation_count.write().expect("Operation failed");
352        allocation_count.clear();
353
354        let mut total_allocated = self.total_allocated.write().expect("Operation failed");
355        total_allocated.clear();
356
357        let mut avg_allocation_size = self.avg_allocation_size.write().expect("Operation failed");
358        avg_allocation_size.clear();
359    }
360
361    /// Get all recorded events
362    pub fn get_events(&self) -> Vec<MemoryEvent> {
363        let events = self.events.read().expect("Operation failed");
364        events.iter().cloned().collect()
365    }
366
367    /// Export the report as JSON (avoiding serialization of non-serializable fields)
368    pub fn to_json(&self) -> serde_json::Value {
369        // Generate a report first to get all the computed values
370        let report = self.generate_report();
371
372        let mut json_obj = serde_json::Map::new();
373
374        json_obj.insert(
375            "total_allocation_count".to_string(),
376            serde_json::Value::Number(report.total_allocation_count.into()),
377        );
378        json_obj.insert(
379            "total_peak_usage".to_string(),
380            serde_json::Value::Number(report.total_peak_usage.into()),
381        );
382        json_obj.insert(
383            "total_current_usage".to_string(),
384            serde_json::Value::Number(report.total_current_usage.into()),
385        );
386        json_obj.insert(
387            "total_allocated_bytes".to_string(),
388            serde_json::Value::Number(report.total_allocated_bytes.into()),
389        );
390
391        // Serialize component stats manually
392        let component_stats: serde_json::Value = report
393            .component_stats
394            .iter()
395            .map(|(k, v)| {
396                (
397                    k.clone(),
398                    serde_json::json!({
399                        "current_usage": v.current_usage,
400                        "peak_usage": v.peak_usage,
401                        "allocation_count": v.allocation_count,
402                        "total_allocated": v.total_allocated,
403                        "avg_allocation_size": v.avg_allocation_size
404                    }),
405                )
406            })
407            .collect::<serde_json::Map<String, serde_json::Value>>()
408            .into();
409
410        json_obj.insert("component_stats".to_string(), component_stats);
411        json_obj.insert(
412            "duration_secs".to_string(),
413            serde_json::Value::Number(report.duration.as_secs().into()),
414        );
415
416        serde_json::Value::Object(json_obj)
417    }
418}
419
420#[cfg(test)]
421mod tests {
422    use super::*;
423    use crate::memory::metrics::event::MemoryEventType;
424
425    #[test]
426    fn test_memory_metrics_collector() {
427        let config = MemoryMetricsConfig {
428            enabled: true,
429            capture_call_stacks: false,
430            max_events: 100,
431            real_time_aggregation: true,
432            samplingrate: 1.0,
433        };
434
435        let collector = MemoryMetricsCollector::new(config);
436
437        // Record allocation events
438        collector.record_event(MemoryEvent::new(
439            MemoryEventType::Allocation,
440            "Component1",
441            1024,
442            0x1000,
443        ));
444
445        collector.record_event(MemoryEvent::new(
446            MemoryEventType::Allocation,
447            "Component1",
448            2048,
449            0x2000,
450        ));
451
452        collector.record_event(MemoryEvent::new(
453            MemoryEventType::Allocation,
454            "Component2",
455            4096,
456            0x3000,
457        ));
458
459        // Check current usage
460        assert_eq!(collector.get_current_usage("Component1"), 3072);
461        assert_eq!(collector.get_current_usage("Component2"), 4096);
462        assert_eq!(collector.get_total_current_usage(), 7168);
463
464        // Record deallocation event
465        collector.record_event(MemoryEvent::new(
466            MemoryEventType::Deallocation,
467            "Component1",
468            1024,
469            0x1000,
470        ));
471
472        // Check updated usage
473        assert_eq!(collector.get_current_usage("Component1"), 2048);
474        assert_eq!(collector.get_total_current_usage(), 6144);
475
476        // Check allocation stats
477        let comp1_stats = collector
478            .get_allocation_stats("Component1")
479            .expect("Operation failed");
480        assert_eq!(comp1_stats.count, 2);
481        assert_eq!(comp1_stats.total_bytes, 3072);
482        assert_eq!(comp1_stats.peak_usage, 3072);
483
484        // Generate report
485        let report = collector.generate_report();
486        assert_eq!(report.total_current_usage, 6144);
487        assert_eq!(report.total_allocation_count, 3);
488
489        // Check component stats in report
490        let comp1_report = report
491            .component_stats
492            .get("Component1")
493            .expect("Operation failed");
494        assert_eq!(comp1_report.current_usage, 2048);
495        assert_eq!(comp1_report.allocation_count, 2);
496    }
497}