memscope_rs/metrics/
analyzer.rs

1use super::{MetricValue, MetricsCollector};
2use std::collections::HashMap;
3use std::time::Duration;
4
5/// Memory analysis performance analyzer
6/// Focused on offline memory profiling and analysis efficiency
7pub struct PerformanceAnalyzer {
8    /// Baseline benchmarks for comparison
9    baselines: HashMap<String, Benchmark>,
10    /// Performance thresholds for memory operations
11    thresholds: AnalysisThresholds,
12}
13
14/// Performance benchmark for memory analysis operations
15#[derive(Debug, Clone)]
16pub struct Benchmark {
17    /// Operation name (e.g., "allocation_tracking", "symbol_resolution")
18    pub operation: String,
19    /// Average execution time
20    pub avg_duration: Duration,
21    /// Memory overhead in bytes
22    pub memory_overhead: usize,
23    /// Throughput (operations per second)
24    pub throughput: f64,
25    /// Accuracy percentage (0.0 to 1.0)
26    pub accuracy: f64,
27    /// Sample size used for benchmark
28    pub sample_size: usize,
29}
30
31/// Performance thresholds for memory analysis
32#[derive(Debug, Clone)]
33pub struct AnalysisThresholds {
34    /// Max acceptable tracking overhead (percentage of app memory)
35    pub max_tracking_overhead: f64,
36    /// Max allocation tracking latency (microseconds)
37    pub max_allocation_latency: Duration,
38    /// Max symbol resolution time per frame (milliseconds)
39    pub max_symbol_resolution_time: Duration,
40    /// Min acceptable tracking completeness (0.0 to 1.0)
41    pub min_tracking_completeness: f64,
42    /// Max memory usage for analysis tools (MB)
43    pub max_analysis_memory: usize,
44}
45
46/// Comprehensive performance report for memory analysis
47#[derive(Debug, Clone)]
48pub struct PerformanceReport {
49    /// Overall analysis efficiency score (0.0 to 1.0)
50    pub efficiency_score: f64,
51    /// Memory tracking performance
52    pub tracking_performance: TrackingPerformance,
53    /// Symbol resolution performance  
54    pub symbol_performance: SymbolPerformance,
55    /// Smart pointer analysis performance
56    pub pointer_performance: PointerPerformance,
57    /// Memory usage efficiency
58    pub memory_efficiency: MemoryEfficiency,
59    /// Recommendations for improvement
60    pub recommendations: Vec<String>,
61}
62
63/// Memory tracking performance metrics
64#[derive(Debug, Clone, Default)]
65pub struct TrackingPerformance {
66    /// Average allocation tracking time
67    pub avg_allocation_time: Duration,
68    /// Tracking completeness percentage
69    pub completeness: f64,
70    /// Memory overhead of tracking
71    pub overhead_bytes: usize,
72    /// Allocations tracked per second
73    pub throughput: f64,
74}
75
76/// Symbol resolution performance metrics
77#[derive(Debug, Clone, Default)]
78pub struct SymbolPerformance {
79    /// Average symbol resolution time
80    pub avg_resolution_time: Duration,
81    /// Cache hit ratio
82    pub cache_hit_ratio: f64,
83    /// Symbols resolved per second
84    pub resolution_rate: f64,
85    /// Memory used by symbol cache
86    pub cache_memory_usage: usize,
87}
88
89/// Smart pointer analysis performance
90#[derive(Debug, Clone, Default)]
91pub struct PointerPerformance {
92    /// Time to analyze pointer patterns
93    pub analysis_time: Duration,
94    /// Leak detection accuracy
95    pub leak_detection_accuracy: f64,
96    /// Pointers analyzed per second
97    pub analysis_rate: f64,
98}
99
100/// Memory usage efficiency of analysis tools
101#[derive(Debug, Clone, Default)]
102pub struct MemoryEfficiency {
103    /// Total memory used by analysis tools
104    pub total_memory_mb: f64,
105    /// Memory usage per tracked allocation
106    pub memory_per_allocation: f64,
107    /// Memory growth rate (MB per hour)
108    pub growth_rate: f64,
109    /// Memory fragmentation level
110    pub fragmentation_ratio: f64,
111}
112
113impl PerformanceAnalyzer {
114    /// Create analyzer with default thresholds
115    pub fn new() -> Self {
116        Self {
117            baselines: HashMap::new(),
118            thresholds: AnalysisThresholds::default(),
119        }
120    }
121
122    /// Create analyzer with custom thresholds
123    pub fn with_thresholds(thresholds: AnalysisThresholds) -> Self {
124        Self {
125            baselines: HashMap::new(),
126            thresholds,
127        }
128    }
129
130    /// Analyze current performance metrics
131    pub fn analyze_performance(&self, collector: &MetricsCollector) -> PerformanceReport {
132        let tracking_perf = self.analyze_tracking_performance(collector);
133        let symbol_perf = self.analyze_symbol_performance(collector);
134        let pointer_perf = self.analyze_pointer_performance(collector);
135        let memory_eff = self.analyze_memory_efficiency(collector);
136
137        let efficiency_score = self.calculate_efficiency_score(
138            &tracking_perf,
139            &symbol_perf,
140            &pointer_perf,
141            &memory_eff,
142        );
143
144        let recommendations =
145            self.generate_recommendations(&tracking_perf, &symbol_perf, &pointer_perf, &memory_eff);
146
147        PerformanceReport {
148            efficiency_score,
149            tracking_performance: tracking_perf,
150            symbol_performance: symbol_perf,
151            pointer_performance: pointer_perf,
152            memory_efficiency: memory_eff,
153            recommendations,
154        }
155    }
156
157    /// Set baseline benchmark for operation
158    pub fn set_baseline(&mut self, operation: &str, benchmark: Benchmark) {
159        self.baselines.insert(operation.to_string(), benchmark);
160    }
161
162    /// Compare current performance against baseline
163    pub fn compare_to_baseline(
164        &self,
165        operation: &str,
166        current: &Benchmark,
167    ) -> Option<PerformanceComparison> {
168        self.baselines
169            .get(operation)
170            .map(|baseline| PerformanceComparison {
171                operation: operation.to_string(),
172                baseline: baseline.clone(),
173                current: current.clone(),
174                duration_ratio: current.avg_duration.as_nanos() as f64
175                    / baseline.avg_duration.as_nanos() as f64,
176                memory_ratio: current.memory_overhead as f64 / baseline.memory_overhead as f64,
177                throughput_ratio: current.throughput / baseline.throughput,
178                accuracy_diff: current.accuracy - baseline.accuracy,
179            })
180    }
181
182    fn analyze_tracking_performance(&self, collector: &MetricsCollector) -> TrackingPerformance {
183        let avg_allocation_time = collector
184            .get_metric("allocation_tracking_time")
185            .and_then(|m| match &m.value {
186                MetricValue::Timer(timer) => Some(timer.average_duration()),
187                _ => None,
188            })
189            .unwrap_or(Duration::from_nanos(0));
190
191        let completeness = collector
192            .get_metric("tracking_completeness")
193            .and_then(|m| match &m.value {
194                MetricValue::Gauge(value) => Some(*value),
195                _ => None,
196            })
197            .unwrap_or(0.0);
198
199        let overhead_bytes = collector
200            .get_metric("tracking_memory_overhead")
201            .and_then(|m| match &m.value {
202                MetricValue::Gauge(value) => Some(*value as usize),
203                _ => None,
204            })
205            .unwrap_or(0);
206
207        let throughput = collector
208            .get_metric("allocations_per_second")
209            .and_then(|m| match &m.value {
210                MetricValue::Rate(rate) => Some(rate.current_rate),
211                _ => None,
212            })
213            .unwrap_or(0.0);
214
215        TrackingPerformance {
216            avg_allocation_time,
217            completeness,
218            overhead_bytes,
219            throughput,
220        }
221    }
222
223    fn analyze_symbol_performance(&self, collector: &MetricsCollector) -> SymbolPerformance {
224        let avg_resolution_time = collector
225            .get_metric("symbol_resolution_time")
226            .and_then(|m| match &m.value {
227                MetricValue::Timer(timer) => Some(timer.average_duration()),
228                _ => None,
229            })
230            .unwrap_or(Duration::from_nanos(0));
231
232        let cache_hit_ratio = collector
233            .get_metric("symbol_cache_hit_ratio")
234            .and_then(|m| match &m.value {
235                MetricValue::Gauge(value) => Some(*value),
236                _ => None,
237            })
238            .unwrap_or(0.0);
239
240        let resolution_rate = collector
241            .get_metric("symbols_resolved_per_second")
242            .and_then(|m| match &m.value {
243                MetricValue::Rate(rate) => Some(rate.current_rate),
244                _ => None,
245            })
246            .unwrap_or(0.0);
247
248        let cache_memory_usage = collector
249            .get_metric("symbol_cache_memory")
250            .and_then(|m| match &m.value {
251                MetricValue::Gauge(value) => Some(*value as usize),
252                _ => None,
253            })
254            .unwrap_or(0);
255
256        SymbolPerformance {
257            avg_resolution_time,
258            cache_hit_ratio,
259            resolution_rate,
260            cache_memory_usage,
261        }
262    }
263
264    fn analyze_pointer_performance(&self, collector: &MetricsCollector) -> PointerPerformance {
265        let analysis_time = collector
266            .get_metric("pointer_analysis_time")
267            .and_then(|m| match &m.value {
268                MetricValue::Timer(timer) => Some(timer.average_duration()),
269                _ => None,
270            })
271            .unwrap_or(Duration::from_nanos(0));
272
273        let leak_detection_accuracy = collector
274            .get_metric("leak_detection_accuracy")
275            .and_then(|m| match &m.value {
276                MetricValue::Gauge(value) => Some(*value),
277                _ => None,
278            })
279            .unwrap_or(0.0);
280
281        let analysis_rate = collector
282            .get_metric("pointers_analyzed_per_second")
283            .and_then(|m| match &m.value {
284                MetricValue::Rate(rate) => Some(rate.current_rate),
285                _ => None,
286            })
287            .unwrap_or(0.0);
288
289        PointerPerformance {
290            analysis_time,
291            leak_detection_accuracy,
292            analysis_rate,
293        }
294    }
295
296    fn analyze_memory_efficiency(&self, collector: &MetricsCollector) -> MemoryEfficiency {
297        let total_memory_mb = collector
298            .get_metric("total_analysis_memory")
299            .and_then(|m| match &m.value {
300                MetricValue::Gauge(value) => Some(*value),
301                _ => None,
302            })
303            .unwrap_or(0.0);
304
305        let memory_per_allocation = collector
306            .get_metric("memory_per_tracked_allocation")
307            .and_then(|m| match &m.value {
308                MetricValue::Gauge(value) => Some(*value),
309                _ => None,
310            })
311            .unwrap_or(0.0);
312
313        let growth_rate = collector
314            .get_metric("memory_growth_rate")
315            .and_then(|m| match &m.value {
316                MetricValue::Gauge(value) => Some(*value),
317                _ => None,
318            })
319            .unwrap_or(0.0);
320
321        let fragmentation_ratio = collector
322            .get_metric("memory_fragmentation")
323            .and_then(|m| match &m.value {
324                MetricValue::Gauge(value) => Some(*value),
325                _ => None,
326            })
327            .unwrap_or(0.0);
328
329        MemoryEfficiency {
330            total_memory_mb,
331            memory_per_allocation,
332            growth_rate,
333            fragmentation_ratio,
334        }
335    }
336
337    fn calculate_efficiency_score(
338        &self,
339        tracking: &TrackingPerformance,
340        symbol: &SymbolPerformance,
341        pointer: &PointerPerformance,
342        memory: &MemoryEfficiency,
343    ) -> f64 {
344        let tracking_score = self.score_tracking_performance(tracking);
345        let symbol_score = self.score_symbol_performance(symbol);
346        let pointer_score = self.score_pointer_performance(pointer);
347        let memory_score = self.score_memory_efficiency(memory);
348
349        // Weighted average (tracking is most important for memory analysis)
350        tracking_score * 0.4 + symbol_score * 0.25 + pointer_score * 0.2 + memory_score * 0.15
351    }
352
353    fn score_tracking_performance(&self, tracking: &TrackingPerformance) -> f64 {
354        let mut score = 1.0;
355
356        // Penalize high latency
357        if tracking.avg_allocation_time > self.thresholds.max_allocation_latency {
358            score *= 0.7;
359        }
360
361        // Penalize low completeness
362        if tracking.completeness < self.thresholds.min_tracking_completeness {
363            score *= tracking.completeness / self.thresholds.min_tracking_completeness;
364        }
365
366        // Reward high throughput
367        if tracking.throughput > 10000.0 {
368            score *= 1.1;
369        }
370
371        score.clamp(0.0, 1.0)
372    }
373
374    fn score_symbol_performance(&self, symbol: &SymbolPerformance) -> f64 {
375        let mut score = 1.0;
376
377        // Penalize slow symbol resolution
378        if symbol.avg_resolution_time > self.thresholds.max_symbol_resolution_time {
379            score *= 0.8;
380        }
381
382        // Reward high cache hit ratio
383        score *= symbol.cache_hit_ratio;
384
385        // Penalize excessive cache memory usage
386        if symbol.cache_memory_usage > 100 * 1024 * 1024 {
387            // 100MB
388            score *= 0.9;
389        }
390
391        score.clamp(0.0, 1.0)
392    }
393
394    fn score_pointer_performance(&self, _pointer: &PointerPerformance) -> f64 {
395        let mut score: f64 = 1.0;
396
397        // Reward high leak detection accuracy
398        score *= _pointer.leak_detection_accuracy;
399
400        // Penalize slow analysis
401        if _pointer.analysis_time > Duration::from_millis(100) {
402            score *= 0.8;
403        }
404
405        score.clamp(0.0, 1.0)
406    }
407
408    fn score_memory_efficiency(&self, memory: &MemoryEfficiency) -> f64 {
409        let mut score: f64 = 1.0;
410
411        // Penalize excessive memory usage
412        if memory.total_memory_mb > self.thresholds.max_analysis_memory as f64 {
413            score *= 0.7;
414        }
415
416        // Penalize high fragmentation
417        if memory.fragmentation_ratio > 0.3 {
418            score *= 0.8;
419        }
420
421        // Penalize rapid growth
422        if memory.growth_rate > 10.0 {
423            // 10MB/hour
424            score *= 0.9;
425        }
426
427        score.clamp(0.0, 1.0)
428    }
429
430    fn generate_recommendations(
431        &self,
432        tracking: &TrackingPerformance,
433        symbol: &SymbolPerformance,
434        _pointer: &PointerPerformance,
435        memory: &MemoryEfficiency,
436    ) -> Vec<String> {
437        let mut recommendations = Vec::new();
438
439        // Tracking recommendations
440        if tracking.completeness < 0.95 {
441            recommendations
442                .push("Improve tracking completeness by reducing lock contention".to_string());
443        }
444        if tracking.avg_allocation_time > Duration::from_micros(100) {
445            recommendations.push("Optimize allocation tracking path for lower latency".to_string());
446        }
447
448        // Symbol recommendations
449        if symbol.cache_hit_ratio < 0.8 {
450            recommendations.push("Increase symbol cache size to improve hit ratio".to_string());
451        }
452        if symbol.avg_resolution_time > Duration::from_millis(10) {
453            recommendations.push("Consider preloading frequently used symbols".to_string());
454        }
455
456        // Memory recommendations
457        if memory.total_memory_mb > 512.0 {
458            recommendations
459                .push("Consider reducing memory usage or implementing memory limits".to_string());
460        }
461        if memory.fragmentation_ratio > 0.2 {
462            recommendations.push("Implement memory compaction to reduce fragmentation".to_string());
463        }
464
465        recommendations
466    }
467}
468
469/// Performance comparison between baseline and current
470#[derive(Debug, Clone)]
471pub struct PerformanceComparison {
472    /// Operation being compared
473    pub operation: String,
474    /// Baseline benchmark
475    pub baseline: Benchmark,
476    /// Current benchmark
477    pub current: Benchmark,
478    /// Duration ratio (current/baseline)
479    pub duration_ratio: f64,
480    /// Memory ratio (current/baseline)
481    pub memory_ratio: f64,
482    /// Throughput ratio (current/baseline)
483    pub throughput_ratio: f64,
484    /// Accuracy difference (current - baseline)
485    pub accuracy_diff: f64,
486}
487
488impl Default for AnalysisThresholds {
489    fn default() -> Self {
490        Self {
491            max_tracking_overhead: 0.05, // 5% of app memory
492            max_allocation_latency: Duration::from_micros(50),
493            max_symbol_resolution_time: Duration::from_millis(5),
494            min_tracking_completeness: 0.95,
495            max_analysis_memory: 512, // 512MB
496        }
497    }
498}
499
500impl Default for PerformanceAnalyzer {
501    fn default() -> Self {
502        Self::new()
503    }
504}
505
506#[cfg(test)]
507mod tests {
508    use super::*;
509
510    #[test]
511    fn test_performance_analyzer_creation() {
512        let analyzer = PerformanceAnalyzer::new();
513        assert!(analyzer.baselines.is_empty());
514
515        let custom_thresholds = AnalysisThresholds {
516            max_tracking_overhead: 0.1,
517            ..Default::default()
518        };
519        let analyzer = PerformanceAnalyzer::with_thresholds(custom_thresholds);
520        assert_eq!(analyzer.thresholds.max_tracking_overhead, 0.1);
521    }
522
523    #[test]
524    fn test_benchmark_comparison() {
525        let mut analyzer = PerformanceAnalyzer::new();
526
527        let baseline = Benchmark {
528            operation: "allocation_tracking".to_string(),
529            avg_duration: Duration::from_micros(100),
530            memory_overhead: 1024,
531            throughput: 1000.0,
532            accuracy: 0.95,
533            sample_size: 10000,
534        };
535
536        analyzer.set_baseline("allocation_tracking", baseline.clone());
537
538        let current = Benchmark {
539            operation: "allocation_tracking".to_string(),
540            avg_duration: Duration::from_micros(120),
541            memory_overhead: 1200,
542            throughput: 900.0,
543            accuracy: 0.97,
544            sample_size: 10000,
545        };
546
547        let comparison = analyzer.compare_to_baseline("allocation_tracking", &current);
548        assert!(comparison.is_some());
549
550        let comparison = comparison.expect("Comparison should exist");
551        assert!(comparison.duration_ratio > 1.0); // Slower
552        assert!(comparison.memory_ratio > 1.0); // More memory
553        assert!(comparison.throughput_ratio < 1.0); // Lower throughput
554        assert!(comparison.accuracy_diff > 0.0); // Better accuracy
555    }
556
557    #[test]
558    fn test_efficiency_scoring() {
559        let analyzer = PerformanceAnalyzer::new();
560
561        let good_tracking = TrackingPerformance {
562            avg_allocation_time: Duration::from_micros(10),
563            completeness: 0.98,
564            overhead_bytes: 1024,
565            throughput: 50000.0,
566        };
567
568        let score = analyzer.score_tracking_performance(&good_tracking);
569        assert!(score > 0.9);
570
571        let bad_tracking = TrackingPerformance {
572            avg_allocation_time: Duration::from_millis(1),
573            completeness: 0.8,
574            overhead_bytes: 10240,
575            throughput: 100.0,
576        };
577
578        let score = analyzer.score_tracking_performance(&bad_tracking);
579        assert!(score < 0.7);
580    }
581}