memscope_rs/analysis/closure/
analyzer.rs1use crate::analysis::closure::types::*;
2use crate::capture::types::AllocationInfo;
3use std::collections::HashMap;
4use std::sync::{Arc, Mutex, OnceLock};
5
6static GLOBAL_CLOSURE_ANALYZER: OnceLock<Arc<ClosureAnalyzer>> = OnceLock::new();
7
8pub fn get_global_closure_analyzer() -> Arc<ClosureAnalyzer> {
9 GLOBAL_CLOSURE_ANALYZER
10 .get_or_init(|| Arc::new(ClosureAnalyzer::new()))
11 .clone()
12}
13
14pub struct ClosureAnalyzer {
15 closures: Mutex<HashMap<usize, ClosureInfo>>,
16 capture_events: Mutex<Vec<CaptureEvent>>,
17 lifetime_graph: Mutex<LifetimeGraph>,
18}
19
20impl ClosureAnalyzer {
21 pub fn new() -> Self {
22 Self {
23 closures: Mutex::new(HashMap::new()),
24 capture_events: Mutex::new(Vec::new()),
25 lifetime_graph: Mutex::new(LifetimeGraph::new()),
26 }
27 }
28
29 pub fn register_closure(&self, closure_ptr: usize, captures: Vec<CaptureInfo>) {
30 let closure_info = ClosureInfo {
31 ptr: closure_ptr,
32 captures: captures.clone(),
33 creation_timestamp: current_timestamp(),
34 thread_id: format!("{:?}", std::thread::current().id()),
35 call_site: capture_call_site(),
36 memory_footprint: self.calculate_closure_footprint(&captures),
37 optimization_potential: self.analyze_optimization_potential(&captures),
38 };
39
40 for capture in &captures {
41 let event = CaptureEvent {
42 closure_ptr,
43 captured_var: capture.clone(),
44 event_type: CaptureEventType::Captured,
45 timestamp: current_timestamp(),
46 };
47
48 if let Ok(mut events) = self.capture_events.lock() {
49 events.push(event);
50 }
51 }
52
53 if let Ok(mut graph) = self.lifetime_graph.lock() {
54 graph.add_closure_relationships(closure_ptr, &captures);
55 }
56
57 if let Ok(mut closures) = self.closures.lock() {
58 closures.insert(closure_ptr, closure_info);
59 }
60 }
61
62 pub fn track_closure_drop(&self, closure_ptr: usize) {
63 if let Ok(mut closures) = self.closures.lock() {
64 if let Some(closure_info) = closures.get_mut(&closure_ptr) {
65 for capture in &closure_info.captures {
66 let event = CaptureEvent {
67 closure_ptr,
68 captured_var: capture.clone(),
69 event_type: CaptureEventType::Released,
70 timestamp: current_timestamp(),
71 };
72
73 if let Ok(mut events) = self.capture_events.lock() {
74 events.push(event);
75 }
76 }
77 }
78 closures.remove(&closure_ptr);
79 }
80
81 if let Ok(mut graph) = self.lifetime_graph.lock() {
82 graph.remove_closure(closure_ptr);
83 }
84 }
85
86 pub fn analyze_closure_patterns(
87 &self,
88 allocations: &[AllocationInfo],
89 ) -> ClosureAnalysisReport {
90 let mut detected_closures = Vec::new();
91 let mut capture_statistics = CaptureStatistics::default();
92
93 for allocation in allocations {
94 if let Some(type_name) = &allocation.type_name {
95 if self.is_closure_type(type_name) {
96 if let Some(analysis) = self.analyze_closure_allocation(allocation) {
97 detected_closures.push(analysis);
98 }
99 }
100 }
101 }
102
103 if let Ok(closures) = self.closures.lock() {
104 capture_statistics = self.calculate_capture_statistics(&closures);
105 }
106
107 let optimization_suggestions = self.generate_optimization_suggestions(&detected_closures);
108 let lifetime_analysis = self.analyze_capture_lifetimes();
109
110 ClosureAnalysisReport {
111 detected_closures,
112 capture_statistics,
113 optimization_suggestions,
114 lifetime_analysis,
115 analysis_timestamp: current_timestamp(),
116 }
117 }
118
119 fn is_closure_type(&self, type_name: &str) -> bool {
120 type_name.contains("closure")
121 || type_name.contains("{{closure}}")
122 || type_name.starts_with("fn(")
123 || type_name.contains("dyn Fn")
124 || type_name.contains("impl Fn")
125 }
126
127 fn analyze_closure_allocation(&self, allocation: &AllocationInfo) -> Option<DetectedClosure> {
128 let type_name = allocation.type_name.as_ref()?;
129
130 Some(DetectedClosure {
131 ptr: allocation.ptr,
132 type_name: type_name.clone(),
133 size: allocation.size,
134 estimated_captures: self.estimate_captures_from_size(allocation.size),
135 closure_type: self.classify_closure_type(type_name),
136 creation_context: CreationContext {
137 scope_name: allocation.scope_name.clone(),
138 thread_id: format!("{:?}", allocation.thread_id),
139 timestamp: allocation.timestamp_alloc,
140 },
141 memory_impact: self.assess_memory_impact(allocation.size),
142 })
143 }
144
145 fn estimate_captures_from_size(&self, size: usize) -> usize {
146 if size <= 8 {
147 0
148 } else if size <= 32 {
149 2
150 } else if size <= 128 {
151 8
152 } else {
153 size / 16
154 }
155 }
156
157 fn classify_closure_type(&self, type_name: &str) -> ClosureType {
158 if type_name.contains("FnOnce") {
159 ClosureType::FnOnce
160 } else if type_name.contains("FnMut") {
161 ClosureType::FnMut
162 } else if type_name.contains("Fn") {
163 ClosureType::Fn
164 } else {
165 ClosureType::Unknown
166 }
167 }
168
169 fn assess_memory_impact(&self, size: usize) -> MemoryImpact {
170 match size {
171 0..=16 => MemoryImpact::Minimal,
172 17..=64 => MemoryImpact::Low,
173 65..=256 => MemoryImpact::Medium,
174 257..=1024 => MemoryImpact::High,
175 _ => MemoryImpact::VeryHigh,
176 }
177 }
178
179 fn calculate_closure_footprint(&self, captures: &[CaptureInfo]) -> ClosureFootprint {
180 let total_size = captures.iter().map(|c| c.size).sum();
181 let by_value_count = captures
182 .iter()
183 .filter(|c| c.mode == CaptureMode::ByValue)
184 .count();
185 let by_ref_count = captures
186 .iter()
187 .filter(|c| c.mode == CaptureMode::ByReference)
188 .count();
189 let by_mut_ref_count = captures
190 .iter()
191 .filter(|c| c.mode == CaptureMode::ByMutableReference)
192 .count();
193
194 ClosureFootprint {
195 total_size,
196 capture_count: captures.len(),
197 by_value_count,
198 by_ref_count,
199 by_mut_ref_count,
200 estimated_heap_usage: self.estimate_heap_usage(captures),
201 }
202 }
203
204 fn estimate_heap_usage(&self, captures: &[CaptureInfo]) -> usize {
205 captures
206 .iter()
207 .filter(|c| c.mode == CaptureMode::ByValue)
208 .filter(|c| self.is_heap_allocated_type(&c.var_type))
209 .map(|c| c.size)
210 .sum()
211 }
212
213 fn is_heap_allocated_type(&self, type_name: &str) -> bool {
214 type_name.contains("Vec")
215 || type_name.contains("String")
216 || type_name.contains("HashMap")
217 || type_name.contains("Box")
218 || type_name.contains("Arc")
219 || type_name.contains("Rc")
220 }
221
222 fn analyze_optimization_potential(&self, captures: &[CaptureInfo]) -> OptimizationPotential {
223 let mut suggestions = Vec::new();
224 let mut potential_savings = 0;
225
226 for capture in captures {
227 if capture.mode == CaptureMode::ByValue && capture.size > 64 {
228 suggestions.push(format!(
229 "Consider capturing '{}' by reference instead of by value to save {} bytes",
230 capture.var_name, capture.size
231 ));
232 potential_savings += capture.size;
233 }
234 }
235
236 let mut_captures = captures
237 .iter()
238 .filter(|c| c.mode == CaptureMode::ByMutableReference)
239 .count();
240 if mut_captures > captures.len() / 2 {
241 suggestions.push("Consider if all mutable captures are necessary".to_string());
242 }
243
244 let heap_captures = captures
245 .iter()
246 .filter(|c| c.mode == CaptureMode::ByValue && self.is_heap_allocated_type(&c.var_type))
247 .count();
248
249 if heap_captures > 0 {
250 suggestions
251 .push("Consider using move semantics for heap-allocated captures".to_string());
252 }
253
254 OptimizationPotential {
255 level: if potential_savings > 256 {
256 OptimizationLevel::High
257 } else if potential_savings > 64 {
258 OptimizationLevel::Medium
259 } else if !suggestions.is_empty() {
260 OptimizationLevel::Low
261 } else {
262 OptimizationLevel::None
263 },
264 potential_savings,
265 suggestions,
266 }
267 }
268
269 fn calculate_capture_statistics(
270 &self,
271 closures: &HashMap<usize, ClosureInfo>,
272 ) -> CaptureStatistics {
273 let total_closures = closures.len();
274 let total_captures = closures.values().map(|c| c.captures.len()).sum();
275
276 let mut by_mode = HashMap::new();
277 let mut by_type = HashMap::new();
278 let mut total_memory = 0;
279
280 for closure in closures.values() {
281 total_memory += closure.memory_footprint.total_size;
282
283 for capture in &closure.captures {
284 *by_mode.entry(capture.mode.clone()).or_insert(0) += 1;
285 *by_type.entry(capture.var_type.clone()).or_insert(0) += 1;
286 }
287 }
288
289 let avg_captures_per_closure = if total_closures > 0 {
290 total_captures as f64 / total_closures as f64
291 } else {
292 0.0
293 };
294
295 CaptureStatistics {
296 total_closures,
297 total_captures,
298 avg_captures_per_closure,
299 total_memory_usage: total_memory,
300 captures_by_mode: by_mode,
301 captures_by_type: by_type,
302 }
303 }
304
305 fn generate_optimization_suggestions(
306 &self,
307 closures: &[DetectedClosure],
308 ) -> Vec<OptimizationSuggestion> {
309 let mut suggestions = Vec::new();
310
311 let high_memory_closures = closures
312 .iter()
313 .filter(|c| matches!(c.memory_impact, MemoryImpact::High | MemoryImpact::VeryHigh))
314 .count();
315
316 if high_memory_closures > 0 {
317 suggestions.push(OptimizationSuggestion {
318 category: OptimizationCategory::Memory,
319 priority: SuggestionPriority::High,
320 description: format!(
321 "Found {high_memory_closures} closures with high memory usage",
322 ),
323 recommendation: "Consider reducing capture size or using references".to_string(),
324 estimated_impact: "20-50% memory reduction".to_string(),
325 });
326 }
327
328 let fnonce_count = closures
329 .iter()
330 .filter(|c| c.closure_type == ClosureType::FnOnce)
331 .count();
332
333 if fnonce_count > closures.len() / 2 {
334 suggestions.push(OptimizationSuggestion {
335 category: OptimizationCategory::Performance,
336 priority: SuggestionPriority::Medium,
337 description: "Many FnOnce closures detected".to_string(),
338 recommendation: "Consider if Fn or FnMut traits would be more appropriate"
339 .to_string(),
340 estimated_impact: "Improved reusability".to_string(),
341 });
342 }
343
344 suggestions
345 }
346
347 fn analyze_capture_lifetimes(&self) -> LifetimeAnalysis {
348 if let Ok(graph) = self.lifetime_graph.lock() {
349 graph.analyze_lifetimes()
350 } else {
351 LifetimeAnalysis::default()
352 }
353 }
354}
355
356impl Default for ClosureAnalyzer {
357 fn default() -> Self {
358 Self::new()
359 }
360}
361
362fn current_timestamp() -> u64 {
363 std::time::SystemTime::now()
364 .duration_since(std::time::UNIX_EPOCH)
365 .unwrap_or_default()
366 .as_nanos() as u64
367}
368
369fn capture_call_site() -> String {
370 format!("{}:{}", file!(), line!())
372}