Skip to main content

memscope_rs/analyzer/
metrics.rs

1//! Metrics analysis module.
2
3use crate::analyzer::report::{MetricsReport, TypeMetric};
4use crate::view::MemoryView;
5use std::collections::HashMap;
6use tracing::debug;
7
8/// Metrics analysis module.
9///
10/// Provides memory metrics and statistics.
11pub struct MetricsAnalysis {
12    view: MemoryView,
13}
14
15impl MetricsAnalysis {
16    /// Create from view.
17    pub fn from_view(view: &MemoryView) -> Self {
18        debug!("Creating MetricsAnalysis with {} allocations", view.len());
19        Self { view: view.clone() }
20    }
21
22    /// Get metrics summary.
23    pub fn summary(&self) -> MetricsReport {
24        debug!("Computing metrics summary");
25        let stats = self.view.snapshot();
26        let report = MetricsReport {
27            allocation_count: stats.stats.active_allocations,
28            total_bytes: stats.stats.current_memory,
29            peak_bytes: stats.stats.peak_memory,
30            thread_count: stats.thread_stats.len(),
31            by_type: self.by_type(),
32        };
33        debug!(
34            "Metrics summary: {} allocations, {} bytes, {} types, {} threads",
35            report.allocation_count,
36            report.total_bytes,
37            report.by_type.len(),
38            report.thread_count
39        );
40        report
41    }
42
43    /// Get top allocations by size.
44    pub fn top_by_size(&self, n: usize) -> Vec<AllocationMetric> {
45        debug!("Getting top {} allocations by size", n);
46        let mut allocs: Vec<_> = self.view.allocations();
47        allocs.sort_by_key(|a| std::cmp::Reverse(a.size));
48        let result: Vec<AllocationMetric> = allocs
49            .into_iter()
50            .take(n)
51            .map(AllocationMetric::from_allocation)
52            .collect();
53        debug!("Returning {} top allocations", result.len());
54        result
55    }
56
57    /// Get allocations by type.
58    pub fn by_type(&self) -> HashMap<String, TypeMetric> {
59        let mut types: HashMap<String, TypeMetric> = HashMap::new();
60        for a in self.view.allocations() {
61            let type_name = a.type_name.clone().unwrap_or_else(|| "unknown".to_string());
62            let entry = types.entry(type_name).or_default();
63            entry.count += 1;
64            entry.total_bytes += a.size;
65        }
66        debug!("Computed metrics for {} types", types.len());
67        types
68    }
69
70    /// Get allocations by thread.
71    pub fn by_thread(&self) -> HashMap<u64, ThreadMetric> {
72        let mut threads: HashMap<u64, ThreadMetric> = HashMap::new();
73        for a in self.view.allocations() {
74            let entry = threads.entry(a.thread_id).or_default();
75            entry.thread_id = a.thread_id;
76            entry.allocation_count += 1;
77            entry.total_bytes += a.size;
78        }
79        debug!("Computed metrics for {} threads", threads.len());
80        threads
81    }
82
83    /// Get size distribution.
84    pub fn size_distribution(&self) -> SizeDistribution {
85        let allocations = self.view.allocations();
86        let sizes: Vec<usize> = allocations.iter().map(|a| a.size).collect();
87
88        if sizes.is_empty() {
89            debug!("Size distribution: no allocations");
90            return SizeDistribution::default();
91        }
92
93        let min = *sizes.iter().min().unwrap_or(&0);
94        let max = *sizes.iter().max().unwrap_or(&0);
95        let avg = sizes.iter().sum::<usize>() / sizes.len();
96
97        debug!("Size distribution: min={}, max={}, avg={}", min, max, avg);
98        SizeDistribution { min, max, avg }
99    }
100}
101
102/// Metric for a single allocation.
103#[derive(Debug, Clone)]
104pub struct AllocationMetric {
105    /// Memory pointer
106    pub ptr: usize,
107    /// Allocation size
108    pub size: usize,
109    /// Type name
110    pub type_name: Option<String>,
111    /// Variable name
112    pub var_name: Option<String>,
113    /// Thread ID
114    pub thread_id: u64,
115}
116
117impl AllocationMetric {
118    fn from_allocation(alloc: &crate::snapshot::ActiveAllocation) -> Self {
119        Self {
120            ptr: alloc.ptr.unwrap_or(0),
121            size: alloc.size,
122            type_name: alloc.type_name.clone(),
123            var_name: alloc.var_name.clone(),
124            thread_id: alloc.thread_id,
125        }
126    }
127}
128
129/// Metric for a thread.
130#[derive(Debug, Clone, Default)]
131pub struct ThreadMetric {
132    /// Thread ID
133    pub thread_id: u64,
134    /// Number of allocations
135    pub allocation_count: usize,
136    /// Total bytes
137    pub total_bytes: usize,
138}
139
140/// Size distribution statistics.
141#[derive(Debug, Clone, Default)]
142pub struct SizeDistribution {
143    /// Minimum allocation size
144    pub min: usize,
145    /// Maximum allocation size
146    pub max: usize,
147    /// Average allocation size
148    pub avg: usize,
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154    use crate::event_store::MemoryEvent;
155
156    #[test]
157    fn test_metrics_summary() {
158        let events = vec![
159            MemoryEvent::allocate(0x1000, 64, 1),
160            MemoryEvent::allocate(0x2000, 128, 2),
161        ];
162        let view = MemoryView::from_events(events);
163        let analysis = MetricsAnalysis::from_view(&view);
164        let summary = analysis.summary();
165        assert_eq!(summary.allocation_count, 2);
166        assert_eq!(summary.total_bytes, 192);
167    }
168
169    #[test]
170    fn test_top_by_size() {
171        let events = vec![
172            MemoryEvent::allocate(0x1000, 64, 1),
173            MemoryEvent::allocate(0x2000, 256, 1),
174            MemoryEvent::allocate(0x3000, 128, 1),
175        ];
176        let view = MemoryView::from_events(events);
177        let analysis = MetricsAnalysis::from_view(&view);
178        let top = analysis.top_by_size(2);
179        assert_eq!(top.len(), 2);
180        assert_eq!(top[0].size, 256);
181        assert_eq!(top[1].size, 128);
182    }
183}