memscope_rs/analyzer/
metrics.rs1use crate::analyzer::report::{MetricsReport, TypeMetric};
4use crate::view::MemoryView;
5use std::collections::HashMap;
6use tracing::debug;
7
8pub struct MetricsAnalysis {
12 view: MemoryView,
13}
14
15impl MetricsAnalysis {
16 pub fn from_view(view: &MemoryView) -> Self {
18 debug!("Creating MetricsAnalysis with {} allocations", view.len());
19 Self { view: view.clone() }
20 }
21
22 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 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 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 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 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#[derive(Debug, Clone)]
104pub struct AllocationMetric {
105 pub ptr: usize,
107 pub size: usize,
109 pub type_name: Option<String>,
111 pub var_name: Option<String>,
113 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#[derive(Debug, Clone, Default)]
131pub struct ThreadMetric {
132 pub thread_id: u64,
134 pub allocation_count: usize,
136 pub total_bytes: usize,
138}
139
140#[derive(Debug, Clone, Default)]
142pub struct SizeDistribution {
143 pub min: usize,
145 pub max: usize,
147 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}