Skip to main content

cbtop/export_reporting/
exporter.rs

1//! Report exporter for multiple output formats.
2
3use std::io::Write;
4use std::path::Path;
5
6use super::types::{BenchmarkReport, ComparisonReport, ExportFormat};
7
8/// Report exporter
9#[derive(Debug)]
10pub struct ReportExporter;
11
12impl ReportExporter {
13    /// Export benchmark report to JSON
14    pub fn benchmark_to_json(report: &BenchmarkReport) -> String {
15        let metrics_json: Vec<String> = report
16            .metrics
17            .iter()
18            .map(|m| {
19                format!(
20                    r#"{{"name":"{}","value":{},"unit":"{}"}}"#,
21                    m.name, m.value, m.unit
22                )
23            })
24            .collect();
25
26        let metadata_json: Vec<String> = report
27            .metadata
28            .iter()
29            .map(|(k, v)| format!(r#""{}":"{}""#, k, v))
30            .collect();
31
32        format!(
33            r#"{{"title":"{}","timestamp":"{}","workload":"{}","backend":"{}","size":{},"metrics":[{}],"metadata":{{{}}}}}"#,
34            report.title,
35            report.timestamp,
36            report.workload,
37            report.backend,
38            report.size,
39            metrics_json.join(","),
40            metadata_json.join(",")
41        )
42    }
43
44    /// Export benchmark report to CSV
45    pub fn benchmark_to_csv(report: &BenchmarkReport) -> String {
46        let mut lines = vec!["metric,value,unit".to_string()];
47        for m in &report.metrics {
48            lines.push(format!("{},{},{}", m.name, m.value, m.unit));
49        }
50        lines.join("\n")
51    }
52
53    /// Export benchmark report to Markdown
54    pub fn benchmark_to_markdown(report: &BenchmarkReport) -> String {
55        let mut md = format!("# {}\n\n", report.title);
56        md.push_str(&format!("**Workload**: {}  \n", report.workload));
57        md.push_str(&format!("**Backend**: {}  \n", report.backend));
58        md.push_str(&format!("**Size**: {}  \n\n", report.size));
59
60        md.push_str("## Metrics\n\n");
61        md.push_str("| Metric | Value | Unit |\n");
62        md.push_str("|--------|-------|------|\n");
63        for m in &report.metrics {
64            md.push_str(&format!("| {} | {:.4} | {} |\n", m.name, m.value, m.unit));
65        }
66
67        md
68    }
69
70    /// Export benchmark report to HTML
71    pub fn benchmark_to_html(report: &BenchmarkReport) -> String {
72        let mut html = String::from("<!DOCTYPE html>\n<html>\n<head>\n");
73        html.push_str(&format!("<title>{}</title>\n", report.title));
74        html.push_str("<style>table{border-collapse:collapse;}th,td{border:1px solid #ccc;padding:8px;}</style>\n");
75        html.push_str("</head>\n<body>\n");
76        html.push_str(&format!("<h1>{}</h1>\n", report.title));
77        html.push_str(&format!(
78            "<p><strong>Workload:</strong> {}</p>\n",
79            report.workload
80        ));
81        html.push_str(&format!(
82            "<p><strong>Backend:</strong> {}</p>\n",
83            report.backend
84        ));
85        html.push_str(&format!("<p><strong>Size:</strong> {}</p>\n", report.size));
86
87        html.push_str("<h2>Metrics</h2>\n");
88        html.push_str("<table>\n<tr><th>Metric</th><th>Value</th><th>Unit</th></tr>\n");
89        for m in &report.metrics {
90            html.push_str(&format!(
91                "<tr><td>{}</td><td>{:.4}</td><td>{}</td></tr>\n",
92                m.name, m.value, m.unit
93            ));
94        }
95        html.push_str("</table>\n</body>\n</html>");
96
97        html
98    }
99
100    /// Export comparison report to JSON
101    pub fn comparison_to_json(report: &ComparisonReport) -> String {
102        let entries_json: Vec<String> = report
103            .entries
104            .iter()
105            .map(|e| {
106                format!(
107                    r#"{{"metric":"{}","baseline":{},"current":{},"change":{:.2},"unit":"{}"}}"#,
108                    e.metric,
109                    e.baseline,
110                    e.current,
111                    e.percent_change(),
112                    e.unit
113                )
114            })
115            .collect();
116
117        format!(
118            r#"{{"title":"{}","baseline":"{}","current":"{}","has_regressions":{},"entries":[{}]}}"#,
119            report.title,
120            report.baseline_label,
121            report.current_label,
122            report.has_regressions(),
123            entries_json.join(",")
124        )
125    }
126
127    /// Export comparison report to Markdown
128    pub fn comparison_to_markdown(report: &ComparisonReport) -> String {
129        let mut md = format!("# {}\n\n", report.title);
130        md.push_str(&format!(
131            "Comparing **{}** vs **{}**\n\n",
132            report.baseline_label, report.current_label
133        ));
134
135        md.push_str("| Metric | Baseline | Current | Change |\n");
136        md.push_str("|--------|----------|---------|--------|\n");
137        for e in &report.entries {
138            let change = e.percent_change();
139            let indicator = if change.abs() > report.regression_threshold {
140                "\u{26a0}\u{fe0f}"
141            } else {
142                "\u{2705}"
143            };
144            md.push_str(&format!(
145                "| {} | {:.4} {} | {:.4} {} | {:.2}% {} |\n",
146                e.metric, e.baseline, e.unit, e.current, e.unit, change, indicator
147            ));
148        }
149
150        if report.has_regressions() {
151            md.push_str("\n## Regressions Detected\n\n");
152            for e in report.get_regressions() {
153                md.push_str(&format!(
154                    "- **{}**: {:.2}% change\n",
155                    e.metric,
156                    e.percent_change()
157                ));
158            }
159        }
160
161        md
162    }
163
164    /// Export to specified format
165    pub fn export_benchmark(report: &BenchmarkReport, format: ExportFormat) -> String {
166        match format {
167            ExportFormat::Json => Self::benchmark_to_json(report),
168            ExportFormat::Csv => Self::benchmark_to_csv(report),
169            ExportFormat::Markdown => Self::benchmark_to_markdown(report),
170            ExportFormat::Html => Self::benchmark_to_html(report),
171        }
172    }
173
174    /// Export comparison to specified format
175    pub fn export_comparison(report: &ComparisonReport, format: ExportFormat) -> String {
176        match format {
177            ExportFormat::Json => Self::comparison_to_json(report),
178            ExportFormat::Markdown => Self::comparison_to_markdown(report),
179            ExportFormat::Csv | ExportFormat::Html => Self::comparison_to_json(report),
180        }
181    }
182
183    /// Write report to file
184    pub fn write_to_file<P: AsRef<Path>>(content: &str, path: P) -> std::io::Result<()> {
185        let mut file = std::fs::File::create(path)?;
186        file.write_all(content.as_bytes())?;
187        Ok(())
188    }
189}