Skip to main content

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