memscope_rs/metrics/
reporter.rs

1use super::{MetricsCollector, PerformanceReport};
2use std::fmt::Write;
3
4/// Memory analysis metrics reporter
5/// Generates reports focused on offline memory profiling efficiency
6pub struct MetricsReporter {
7    /// Output format configuration
8    format: ReportFormat,
9    /// Alert thresholds for memory analysis performance
10    alert_thresholds: Vec<AlertThreshold>,
11    /// Whether to include detailed breakdown
12    include_details: bool,
13}
14
15/// Available report output formats
16#[derive(Debug, Clone, PartialEq)]
17pub enum ReportFormat {
18    /// Human-readable text format
19    PlainText,
20    /// Structured JSON format for tooling
21    Json,
22    /// Markdown format for documentation
23    Markdown,
24    /// CSV format for spreadsheet analysis
25    Csv,
26}
27
28/// Performance alert threshold
29#[derive(Debug, Clone)]
30pub struct AlertThreshold {
31    /// Metric name to monitor
32    pub metric_name: String,
33    /// Threshold value
34    pub threshold: f64,
35    /// Alert condition
36    pub condition: AlertCondition,
37    /// Alert severity level
38    pub severity: AlertSeverity,
39    /// Human-readable description
40    pub description: String,
41}
42
43/// Alert condition types
44#[derive(Debug, Clone, PartialEq)]
45pub enum AlertCondition {
46    /// Value exceeds threshold
47    GreaterThan,
48    /// Value is below threshold
49    LessThan,
50    /// Value equals threshold
51    Equals,
52}
53
54/// Alert severity levels
55#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
56pub enum AlertSeverity {
57    /// Informational notice
58    Info,
59    /// Performance warning
60    Warning,
61    /// Performance problem
62    Error,
63    /// Critical performance issue
64    Critical,
65}
66
67impl MetricsReporter {
68    /// Create new reporter with plain text format
69    pub fn new() -> Self {
70        Self {
71            format: ReportFormat::PlainText,
72            alert_thresholds: Self::default_memory_thresholds(),
73            include_details: true,
74        }
75    }
76
77    /// Create reporter with specific format
78    pub fn with_format(format: ReportFormat) -> Self {
79        Self {
80            format,
81            alert_thresholds: Self::default_memory_thresholds(),
82            include_details: true,
83        }
84    }
85
86    /// Set whether to include detailed breakdowns
87    pub fn with_details(mut self, include_details: bool) -> Self {
88        self.include_details = include_details;
89        self
90    }
91
92    /// Add custom alert threshold
93    pub fn add_alert_threshold(mut self, threshold: AlertThreshold) -> Self {
94        self.alert_thresholds.push(threshold);
95        self
96    }
97
98    /// Generate comprehensive performance report
99    pub fn generate_report(&self, report: &PerformanceReport) -> String {
100        match self.format {
101            ReportFormat::PlainText => self.generate_text_report(report),
102            ReportFormat::Json => self.generate_json_report(report),
103            ReportFormat::Markdown => self.generate_markdown_report(report),
104            ReportFormat::Csv => self.generate_csv_report(report),
105        }
106    }
107
108    /// Generate metrics summary
109    pub fn generate_metrics_summary(&self, collector: &MetricsCollector) -> String {
110        let summary = collector.get_summary();
111
112        match self.format {
113            ReportFormat::PlainText => {
114                format!(
115                    "Metrics Summary:\n\
116                     - Total Metrics: {}\n\
117                     - Active Metrics: {}\n\
118                     - Update Rate: {:.2}/sec\n\
119                     - Uptime: {:.2}h\n\
120                     - Sample Rate: {:.1}%\n",
121                    summary.total_metrics,
122                    summary.active_metrics,
123                    summary.update_rate,
124                    summary.uptime.as_secs_f64() / 3600.0,
125                    summary.sample_rate * 100.0
126                )
127            }
128            ReportFormat::Json => {
129                format!(
130                    r#"{{"total_metrics": {}, "active_metrics": {}, "update_rate": {:.2}, "uptime_hours": {:.2}, "sample_rate": {:.3}}}"#,
131                    summary.total_metrics,
132                    summary.active_metrics,
133                    summary.update_rate,
134                    summary.uptime.as_secs_f64() / 3600.0,
135                    summary.sample_rate
136                )
137            }
138            _ => self.generate_text_report(&PerformanceReport {
139                efficiency_score: 0.0,
140                tracking_performance: Default::default(),
141                symbol_performance: Default::default(),
142                pointer_performance: Default::default(),
143                memory_efficiency: Default::default(),
144                recommendations: vec![],
145            }),
146        }
147    }
148
149    /// Check for performance alerts
150    pub fn check_alerts(&self, collector: &MetricsCollector) -> Vec<TriggeredAlert> {
151        let mut alerts = Vec::new();
152
153        for threshold in &self.alert_thresholds {
154            if let Some(metric) = collector.get_metric(&threshold.metric_name) {
155                let current_value = self.extract_metric_value(metric);
156
157                let triggered = match threshold.condition {
158                    AlertCondition::GreaterThan => current_value > threshold.threshold,
159                    AlertCondition::LessThan => current_value < threshold.threshold,
160                    AlertCondition::Equals => (current_value - threshold.threshold).abs() < 0.001,
161                };
162
163                if triggered {
164                    alerts.push(TriggeredAlert {
165                        metric_name: threshold.metric_name.clone(),
166                        current_value,
167                        threshold_value: threshold.threshold,
168                        condition: threshold.condition.clone(),
169                        severity: threshold.severity.clone(),
170                        description: threshold.description.clone(),
171                    });
172                }
173            }
174        }
175
176        // Sort by severity (most critical first)
177        alerts.sort_by(|a, b| b.severity.cmp(&a.severity));
178        alerts
179    }
180
181    fn default_memory_thresholds() -> Vec<AlertThreshold> {
182        vec![
183            AlertThreshold {
184                metric_name: "tracking_completeness".to_string(),
185                threshold: 0.95,
186                condition: AlertCondition::LessThan,
187                severity: AlertSeverity::Warning,
188                description: "Memory tracking completeness below 95%".to_string(),
189            },
190            AlertThreshold {
191                metric_name: "allocation_tracking_time".to_string(),
192                threshold: 100.0, // microseconds
193                condition: AlertCondition::GreaterThan,
194                severity: AlertSeverity::Error,
195                description: "Allocation tracking latency exceeds 100µs".to_string(),
196            },
197            AlertThreshold {
198                metric_name: "symbol_cache_hit_ratio".to_string(),
199                threshold: 0.8,
200                condition: AlertCondition::LessThan,
201                severity: AlertSeverity::Warning,
202                description: "Symbol cache hit ratio below 80%".to_string(),
203            },
204            AlertThreshold {
205                metric_name: "total_analysis_memory".to_string(),
206                threshold: 512.0, // MB
207                condition: AlertCondition::GreaterThan,
208                severity: AlertSeverity::Critical,
209                description: "Analysis memory usage exceeds 512MB".to_string(),
210            },
211            AlertThreshold {
212                metric_name: "memory_fragmentation".to_string(),
213                threshold: 0.3,
214                condition: AlertCondition::GreaterThan,
215                severity: AlertSeverity::Warning,
216                description: "Memory fragmentation exceeds 30%".to_string(),
217            },
218        ]
219    }
220
221    fn generate_text_report(&self, report: &PerformanceReport) -> String {
222        let mut output = String::new();
223
224        writeln!(output, "=== Memory Analysis Performance Report ===").expect("Write failed");
225        writeln!(
226            output,
227            "Overall Efficiency Score: {:.1}%",
228            report.efficiency_score * 100.0
229        )
230        .expect("Write failed");
231        writeln!(output).expect("Write failed");
232
233        // Tracking Performance
234        writeln!(output, "Memory Tracking Performance:").expect("Write failed");
235        writeln!(
236            output,
237            "  - Average Allocation Time: {:.2}µs",
238            report.tracking_performance.avg_allocation_time.as_micros()
239        )
240        .expect("Write failed");
241        writeln!(
242            output,
243            "  - Tracking Completeness: {:.1}%",
244            report.tracking_performance.completeness * 100.0
245        )
246        .expect("Write failed");
247        writeln!(
248            output,
249            "  - Memory Overhead: {:.2}MB",
250            report.tracking_performance.overhead_bytes as f64 / (1024.0 * 1024.0)
251        )
252        .expect("Write failed");
253        writeln!(
254            output,
255            "  - Throughput: {:.0} allocs/sec",
256            report.tracking_performance.throughput
257        )
258        .expect("Write failed");
259        writeln!(output).expect("Write failed");
260
261        // Symbol Performance
262        writeln!(output, "Symbol Resolution Performance:").expect("Write failed");
263        writeln!(
264            output,
265            "  - Average Resolution Time: {:.2}ms",
266            report.symbol_performance.avg_resolution_time.as_millis()
267        )
268        .expect("Write failed");
269        writeln!(
270            output,
271            "  - Cache Hit Ratio: {:.1}%",
272            report.symbol_performance.cache_hit_ratio * 100.0
273        )
274        .expect("Write failed");
275        writeln!(
276            output,
277            "  - Resolution Rate: {:.0} symbols/sec",
278            report.symbol_performance.resolution_rate
279        )
280        .expect("Write failed");
281        writeln!(
282            output,
283            "  - Cache Memory: {:.2}MB",
284            report.symbol_performance.cache_memory_usage as f64 / (1024.0 * 1024.0)
285        )
286        .expect("Write failed");
287        writeln!(output).expect("Write failed");
288
289        // Smart Pointer Performance
290        writeln!(output, "Smart Pointer Analysis:").expect("Write failed");
291        writeln!(
292            output,
293            "  - Analysis Time: {:.2}ms",
294            report.pointer_performance.analysis_time.as_millis()
295        )
296        .expect("Write failed");
297        writeln!(
298            output,
299            "  - Leak Detection Accuracy: {:.1}%",
300            report.pointer_performance.leak_detection_accuracy * 100.0
301        )
302        .expect("Write failed");
303        writeln!(
304            output,
305            "  - Analysis Rate: {:.0} pointers/sec",
306            report.pointer_performance.analysis_rate
307        )
308        .expect("Write failed");
309        writeln!(output).expect("Write failed");
310
311        // Memory Efficiency
312        writeln!(output, "Memory Usage Efficiency:").expect("Write failed");
313        writeln!(
314            output,
315            "  - Total Memory: {:.1}MB",
316            report.memory_efficiency.total_memory_mb
317        )
318        .expect("Write failed");
319        writeln!(
320            output,
321            "  - Memory per Allocation: {:.1} bytes",
322            report.memory_efficiency.memory_per_allocation
323        )
324        .expect("Write failed");
325        writeln!(
326            output,
327            "  - Growth Rate: {:.2}MB/hour",
328            report.memory_efficiency.growth_rate
329        )
330        .expect("Write failed");
331        writeln!(
332            output,
333            "  - Fragmentation: {:.1}%",
334            report.memory_efficiency.fragmentation_ratio * 100.0
335        )
336        .expect("Write failed");
337        writeln!(output).expect("Write failed");
338
339        // Recommendations
340        if !report.recommendations.is_empty() {
341            writeln!(output, "Performance Recommendations:").expect("Write failed");
342            for (i, rec) in report.recommendations.iter().enumerate() {
343                writeln!(output, "  {}. {}", i + 1, rec).expect("Write failed");
344            }
345        }
346
347        output
348    }
349
350    fn generate_json_report(&self, report: &PerformanceReport) -> String {
351        format!(
352            r#"{{
353  "efficiency_score": {:.3},
354  "tracking_performance": {{
355    "avg_allocation_time_us": {:.2},
356    "completeness": {:.3},
357    "overhead_bytes": {},
358    "throughput": {:.2}
359  }},
360  "symbol_performance": {{
361    "avg_resolution_time_ms": {:.2},
362    "cache_hit_ratio": {:.3},
363    "resolution_rate": {:.2},
364    "cache_memory_usage": {}
365  }},
366  "pointer_performance": {{
367    "analysis_time_ms": {:.2},
368    "leak_detection_accuracy": {:.3},
369    "analysis_rate": {:.2}
370  }},
371  "memory_efficiency": {{
372    "total_memory_mb": {:.2},
373    "memory_per_allocation": {:.2},
374    "growth_rate": {:.2},
375    "fragmentation_ratio": {:.3}
376  }},
377  "recommendations": [{}]
378}}"#,
379            report.efficiency_score,
380            report.tracking_performance.avg_allocation_time.as_micros(),
381            report.tracking_performance.completeness,
382            report.tracking_performance.overhead_bytes,
383            report.tracking_performance.throughput,
384            report.symbol_performance.avg_resolution_time.as_millis(),
385            report.symbol_performance.cache_hit_ratio,
386            report.symbol_performance.resolution_rate,
387            report.symbol_performance.cache_memory_usage,
388            report.pointer_performance.analysis_time.as_millis(),
389            report.pointer_performance.leak_detection_accuracy,
390            report.pointer_performance.analysis_rate,
391            report.memory_efficiency.total_memory_mb,
392            report.memory_efficiency.memory_per_allocation,
393            report.memory_efficiency.growth_rate,
394            report.memory_efficiency.fragmentation_ratio,
395            report
396                .recommendations
397                .iter()
398                .map(|r| format!("\"{}\"", r.replace('"', "\\\"")))
399                .collect::<Vec<_>>()
400                .join(", ")
401        )
402    }
403
404    fn generate_markdown_report(&self, report: &PerformanceReport) -> String {
405        let mut output = String::new();
406
407        writeln!(output, "# Memory Analysis Performance Report").expect("Write failed");
408        writeln!(output).expect("Write failed");
409        writeln!(
410            output,
411            "**Overall Efficiency Score:** {:.1}%",
412            report.efficiency_score * 100.0
413        )
414        .expect("Write failed");
415        writeln!(output).expect("Write failed");
416
417        writeln!(output, "## Performance Metrics").expect("Write failed");
418        writeln!(output).expect("Write failed");
419
420        writeln!(output, "### Memory Tracking").expect("Write failed");
421        writeln!(output, "| Metric | Value |").expect("Write failed");
422        writeln!(output, "|--------|-------|").expect("Write failed");
423        writeln!(
424            output,
425            "| Allocation Time | {:.2}µs |",
426            report.tracking_performance.avg_allocation_time.as_micros()
427        )
428        .expect("Write failed");
429        writeln!(
430            output,
431            "| Completeness | {:.1}% |",
432            report.tracking_performance.completeness * 100.0
433        )
434        .expect("Write failed");
435        writeln!(
436            output,
437            "| Memory Overhead | {:.2}MB |",
438            report.tracking_performance.overhead_bytes as f64 / (1024.0 * 1024.0)
439        )
440        .expect("Write failed");
441        writeln!(
442            output,
443            "| Throughput | {:.0} allocs/sec |",
444            report.tracking_performance.throughput
445        )
446        .expect("Write failed");
447        writeln!(output).expect("Write failed");
448
449        writeln!(output, "### Symbol Resolution").expect("Write failed");
450        writeln!(output, "| Metric | Value |").expect("Write failed");
451        writeln!(output, "|--------|-------|").expect("Write failed");
452        writeln!(
453            output,
454            "| Resolution Time | {:.2}ms |",
455            report.symbol_performance.avg_resolution_time.as_millis()
456        )
457        .expect("Write failed");
458        writeln!(
459            output,
460            "| Cache Hit Ratio | {:.1}% |",
461            report.symbol_performance.cache_hit_ratio * 100.0
462        )
463        .expect("Write failed");
464        writeln!(
465            output,
466            "| Resolution Rate | {:.0} symbols/sec |",
467            report.symbol_performance.resolution_rate
468        )
469        .expect("Write failed");
470        writeln!(output).expect("Write failed");
471
472        if !report.recommendations.is_empty() {
473            writeln!(output, "## Recommendations").expect("Write failed");
474            writeln!(output).expect("Write failed");
475            for rec in &report.recommendations {
476                writeln!(output, "- {}", rec).expect("Write failed");
477            }
478        }
479
480        output
481    }
482
483    fn generate_csv_report(&self, report: &PerformanceReport) -> String {
484        let mut output = String::new();
485
486        writeln!(output, "metric_category,metric_name,value,unit").expect("Write failed");
487        writeln!(
488            output,
489            "overall,efficiency_score,{:.3},percentage",
490            report.efficiency_score
491        )
492        .expect("Write failed");
493        writeln!(
494            output,
495            "tracking,avg_allocation_time,{:.2},microseconds",
496            report.tracking_performance.avg_allocation_time.as_micros()
497        )
498        .expect("Write failed");
499        writeln!(
500            output,
501            "tracking,completeness,{:.3},ratio",
502            report.tracking_performance.completeness
503        )
504        .expect("Write failed");
505        writeln!(
506            output,
507            "tracking,overhead_bytes,{},bytes",
508            report.tracking_performance.overhead_bytes
509        )
510        .expect("Write failed");
511        writeln!(
512            output,
513            "tracking,throughput,{:.2},allocations_per_second",
514            report.tracking_performance.throughput
515        )
516        .expect("Write failed");
517        writeln!(
518            output,
519            "symbol,avg_resolution_time,{:.2},milliseconds",
520            report.symbol_performance.avg_resolution_time.as_millis()
521        )
522        .expect("Write failed");
523        writeln!(
524            output,
525            "symbol,cache_hit_ratio,{:.3},ratio",
526            report.symbol_performance.cache_hit_ratio
527        )
528        .expect("Write failed");
529        writeln!(
530            output,
531            "symbol,resolution_rate,{:.2},symbols_per_second",
532            report.symbol_performance.resolution_rate
533        )
534        .expect("Write failed");
535        writeln!(
536            output,
537            "memory,total_memory_mb,{:.2},megabytes",
538            report.memory_efficiency.total_memory_mb
539        )
540        .expect("Write failed");
541        writeln!(
542            output,
543            "memory,fragmentation_ratio,{:.3},ratio",
544            report.memory_efficiency.fragmentation_ratio
545        )
546        .expect("Write failed");
547
548        output
549    }
550
551    fn extract_metric_value(&self, metric: &super::Metric) -> f64 {
552        match &metric.value {
553            super::MetricValue::Counter(counter) => {
554                counter.load(std::sync::atomic::Ordering::Relaxed) as f64
555            }
556            super::MetricValue::Gauge(value) => *value,
557            super::MetricValue::Histogram(hist) => hist.average(),
558            super::MetricValue::Timer(timer) => timer.average_duration().as_millis() as f64,
559            super::MetricValue::Rate(rate) => rate.current_rate,
560        }
561    }
562}
563
564/// Alert that has been triggered
565#[derive(Debug, Clone)]
566pub struct TriggeredAlert {
567    /// Name of the metric that triggered
568    pub metric_name: String,
569    /// Current value of the metric
570    pub current_value: f64,
571    /// Threshold value that was exceeded
572    pub threshold_value: f64,
573    /// The condition that was met
574    pub condition: AlertCondition,
575    /// Severity of the alert
576    pub severity: AlertSeverity,
577    /// Human-readable description
578    pub description: String,
579}
580
581impl Default for MetricsReporter {
582    fn default() -> Self {
583        Self::new()
584    }
585}
586
587#[cfg(test)]
588mod tests {
589    use super::*;
590
591    #[test]
592    fn test_reporter_creation() {
593        let reporter = MetricsReporter::new();
594        assert_eq!(reporter.format, ReportFormat::PlainText);
595        assert!(reporter.include_details);
596        assert!(!reporter.alert_thresholds.is_empty());
597    }
598
599    #[test]
600    fn test_report_formats() {
601        let report = PerformanceReport {
602            efficiency_score: 0.85,
603            tracking_performance: Default::default(),
604            symbol_performance: Default::default(),
605            pointer_performance: Default::default(),
606            memory_efficiency: Default::default(),
607            recommendations: vec!["Test recommendation".to_string()],
608        };
609
610        let text_reporter = MetricsReporter::with_format(ReportFormat::PlainText);
611        let text_report = text_reporter.generate_report(&report);
612        assert!(text_report.contains("Memory Analysis Performance Report"));
613        assert!(text_report.contains("85.0%"));
614
615        let json_reporter = MetricsReporter::with_format(ReportFormat::Json);
616        let json_report = json_reporter.generate_report(&report);
617        assert!(json_report.contains("efficiency_score"));
618        assert!(json_report.contains("0.850"));
619
620        let md_reporter = MetricsReporter::with_format(ReportFormat::Markdown);
621        let md_report = md_reporter.generate_report(&report);
622        assert!(md_report.contains("# Memory Analysis Performance Report"));
623        assert!(md_report.contains("## Recommendations"));
624    }
625
626    #[test]
627    fn test_alert_threshold() {
628        let threshold = AlertThreshold {
629            metric_name: "test_metric".to_string(),
630            threshold: 100.0,
631            condition: AlertCondition::GreaterThan,
632            severity: AlertSeverity::Warning,
633            description: "Test alert".to_string(),
634        };
635
636        assert_eq!(threshold.metric_name, "test_metric");
637        assert_eq!(threshold.condition, AlertCondition::GreaterThan);
638        assert_eq!(threshold.severity, AlertSeverity::Warning);
639    }
640
641    #[test]
642    fn test_alert_severity_ordering() {
643        assert!(AlertSeverity::Critical > AlertSeverity::Error);
644        assert!(AlertSeverity::Error > AlertSeverity::Warning);
645        assert!(AlertSeverity::Warning > AlertSeverity::Info);
646    }
647}