memscope_rs/analysis/
top_n.rs1use crate::capture::types::AllocationInfo;
7use std::collections::HashMap;
8
9pub struct TopNAnalyzer {
11 allocations: Vec<AllocationInfo>,
12}
13
14impl TopNAnalyzer {
15 pub fn new(allocations: Vec<AllocationInfo>) -> Self {
17 Self { allocations }
18 }
19
20 pub fn top_allocation_sites(&self, n: usize) -> Vec<AllocationSite> {
22 let mut sites = HashMap::new();
23 for alloc in &self.allocations {
24 let key = alloc
25 .stack_trace
26 .as_ref()
27 .and_then(|s| s.first().cloned())
28 .unwrap_or_else(|| "unknown".to_string());
29 let entry = sites.entry(key.clone()).or_insert(AllocationSite {
30 name: key,
31 total_bytes: 0,
32 allocation_count: 0,
33 });
34 entry.total_bytes += alloc.size;
35 entry.allocation_count += 1;
36 }
37
38 let mut result: Vec<_> = sites.into_values().collect();
39 result.sort_by_key(|b| std::cmp::Reverse(b.total_bytes));
40 result.truncate(n);
41 result
42 }
43
44 pub fn top_leaked_bytes(&self, n: usize) -> Vec<LeakedAllocation> {
46 let mut leaked: Vec<LeakedAllocation> = self
47 .allocations
48 .iter()
49 .filter(|alloc| alloc.timestamp_dealloc.is_none())
50 .map(|alloc| LeakedAllocation {
51 ptr: alloc.ptr,
52 size: alloc.size,
53 type_name: alloc.type_name.clone(),
54 stack_trace: alloc.stack_trace.clone(),
55 timestamp_alloc: alloc.timestamp_alloc,
56 })
57 .collect();
58
59 leaked.sort_by_key(|b| std::cmp::Reverse(b.size));
60 leaked.truncate(n);
61 leaked
62 }
63
64 pub fn top_temporary_churn(&self, n: usize, threshold_ms: u64) -> Vec<TemporaryChurn> {
66 let mut churn = HashMap::new();
67 for alloc in &self.allocations {
68 if let Some(dealloc_ts) = alloc.timestamp_dealloc {
69 let lifetime_ms = dealloc_ts - alloc.timestamp_alloc;
70 if lifetime_ms < threshold_ms {
71 let key = alloc
72 .stack_trace
73 .as_ref()
74 .and_then(|s| s.first().cloned())
75 .unwrap_or_else(|| "unknown".to_string());
76 let entry = churn.entry(key.clone()).or_insert(TemporaryChurn {
77 name: key,
78 allocation_count: 0,
79 total_bytes: 0,
80 average_lifetime_ms: 0.0,
81 });
82 entry.allocation_count += 1;
83 entry.total_bytes += alloc.size;
84 }
85 }
86 }
87
88 let mut result: Vec<_> = churn.into_values().collect();
89 result.sort_by_key(|b| std::cmp::Reverse(b.allocation_count));
90 result.truncate(n);
91 result
92 }
93
94 pub fn summary(&self) -> TopNSummary {
96 let total_allocations = self.allocations.len();
97 let total_bytes: usize = self.allocations.iter().map(|a| a.size).sum();
98 let leaked_count = self
99 .allocations
100 .iter()
101 .filter(|a| a.timestamp_dealloc.is_none())
102 .count();
103 let leaked_bytes: usize = self
104 .allocations
105 .iter()
106 .filter(|a| a.timestamp_dealloc.is_none())
107 .map(|a| a.size)
108 .sum();
109
110 TopNSummary {
111 total_allocations,
112 total_bytes,
113 leaked_count,
114 leaked_bytes,
115 }
116 }
117}
118
119#[derive(Debug, Clone)]
121pub struct AllocationSite {
122 pub name: String,
123 pub total_bytes: usize,
124 pub allocation_count: usize,
125}
126
127#[derive(Debug, Clone)]
129pub struct LeakedAllocation {
130 pub ptr: usize,
131 pub size: usize,
132 pub type_name: Option<String>,
133 pub stack_trace: Option<Vec<String>>,
134 pub timestamp_alloc: u64,
135}
136
137#[derive(Debug, Clone)]
139pub struct TemporaryChurn {
140 pub name: String,
141 pub allocation_count: usize,
142 pub total_bytes: usize,
143 pub average_lifetime_ms: f64,
144}
145
146#[derive(Debug, Clone)]
148pub struct TopNSummary {
149 pub total_allocations: usize,
150 pub total_bytes: usize,
151 pub leaked_count: usize,
152 pub leaked_bytes: usize,
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn test_top_allocation_sites_empty() {
161 let analyzer = TopNAnalyzer::new(vec![]);
162 let top = analyzer.top_allocation_sites(10);
163 assert!(top.is_empty());
164 }
165
166 #[test]
167 fn test_top_leaked_bytes_empty() {
168 let analyzer = TopNAnalyzer::new(vec![]);
169 let leaked = analyzer.top_leaked_bytes(10);
170 assert!(leaked.is_empty());
171 }
172
173 #[test]
174 fn test_top_temporary_churn_empty() {
175 let analyzer = TopNAnalyzer::new(vec![]);
176 let churn = analyzer.top_temporary_churn(10, 100);
177 assert!(churn.is_empty());
178 }
179
180 #[test]
181 fn test_summary_empty() {
182 let analyzer = TopNAnalyzer::new(vec![]);
183 let summary = analyzer.summary();
184 assert_eq!(summary.total_allocations, 0);
185 assert_eq!(summary.total_bytes, 0);
186 assert_eq!(summary.leaked_count, 0);
187 assert_eq!(summary.leaked_bytes, 0);
188 }
189}