Skip to main content

memscope_rs/analysis/closure/
analyzer.rs

1use 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    // Use file!() and line!() macros to capture call site
371    format!("{}:{}", file!(), line!())
372}