1use std::io::Write;
4use std::path::Path;
5
6use super::types::{BenchmarkReport, ComparisonReport, ExportFormat};
7
8#[derive(Debug)]
10pub struct ReportExporter;
11
12impl ReportExporter {
13 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 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 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 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 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 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 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 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 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}