1use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct BenchmarkReport {
16 pub schema_version: String,
18
19 pub metadata: BenchmarkMetadata,
21
22 pub benchmarks: Vec<BenchmarkResult>,
24
25 pub summary: BenchmarkSummary,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct BenchmarkMetadata {
32 pub benchmark_suite: String,
34
35 pub timestamp: String,
37
38 pub git_commit: String,
40
41 pub git_branch: String,
43
44 pub operator: String,
46
47 pub hardware: HardwareInfo,
49
50 pub software: SoftwareInfo,
52
53 pub environment: EnvironmentConfig,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct HardwareInfo {
60 pub cpu: CpuInfo,
62
63 pub memory: MemoryInfo,
65
66 pub storage: Option<StorageInfo>,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct CpuInfo {
73 pub model: String,
75
76 pub cores: usize,
78
79 pub threads: usize,
81
82 pub frequency_mhz: u64,
84
85 pub cache_l1: String,
87
88 pub cache_l2: String,
90
91 pub cache_l3: String,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct MemoryInfo {
98 pub total_gb: f64,
100
101 #[serde(rename = "type")]
103 pub memory_type: String,
104
105 pub frequency_mhz: Option<u64>,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct StorageInfo {
112 #[serde(rename = "type")]
114 pub storage_type: String,
115
116 pub model: String,
118
119 pub capacity_gb: u64,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct SoftwareInfo {
126 pub os: String,
128
129 pub kernel: String,
131
132 pub rustc: String,
134
135 pub cargo: String,
137
138 pub llvm: String,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct EnvironmentConfig {
145 pub cpu_governor: String,
147
148 pub turbo_boost: String,
150
151 pub swap: String,
153
154 pub isolation: Option<String>,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct BenchmarkResult {
161 pub name: String,
163
164 pub category: String,
166
167 pub scope: String,
169
170 pub binary: String,
172
173 pub optimization_profile: String,
175
176 pub measurements: Measurements,
178
179 pub comparison: Option<Comparison>,
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct Measurements {
186 pub warmup_runs: usize,
188
189 pub measured_runs: usize,
191
192 pub raw_values_ms: Vec<f64>,
194
195 pub outliers_removed: Vec<f64>,
197
198 pub statistics: Statistics,
200
201 pub distribution: Distribution,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct Statistics {
208 pub mean_ms: f64,
210
211 pub median_ms: f64,
213
214 pub std_dev_ms: f64,
216
217 pub min_ms: f64,
219
220 pub max_ms: f64,
222
223 pub coefficient_of_variation: f64,
225
226 pub confidence_interval_95: (f64, f64),
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct Distribution {
233 pub normality_test: String,
235
236 pub normality_p_value: f64,
238
239 pub is_normal: bool,
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize)]
245pub struct Comparison {
246 pub baseline_commit: String,
248
249 pub baseline_mean_ms: f64,
251
252 pub speedup_ratio: f64,
254
255 pub speedup_ci_95: (f64, f64),
257
258 pub t_test_p_value: f64,
260
261 pub effect_size_cohens_d: f64,
263
264 pub significant_improvement: bool,
266
267 pub significant_regression: bool,
269}
270
271#[derive(Debug, Clone, Serialize, Deserialize)]
273pub struct BenchmarkSummary {
274 pub total_benchmarks: usize,
276
277 pub successful: usize,
279
280 pub failed: usize,
282
283 pub total_runtime_seconds: f64,
285
286 pub significant_improvements: usize,
288
289 pub significant_regressions: usize,
291
292 pub metrics: HashMap<String, f64>,
294}
295
296impl BenchmarkReport {
297 #[must_use]
299 pub fn new(suite_name: &str, metadata: BenchmarkMetadata) -> Self {
300 let mut metadata = metadata;
302 if metadata.benchmark_suite.is_empty() && !suite_name.is_empty() {
303 metadata.benchmark_suite = suite_name.to_string();
304 }
305 Self {
306 schema_version: "1.0".to_string(),
307 metadata,
308 benchmarks: Vec::new(),
309 summary: BenchmarkSummary {
310 total_benchmarks: 0,
311 successful: 0,
312 failed: 0,
313 total_runtime_seconds: 0.0,
314 significant_improvements: 0,
315 significant_regressions: 0,
316 metrics: HashMap::new(),
317 },
318 }
319 }
320
321 pub fn add_benchmark(&mut self, result: BenchmarkResult) {
323 self.benchmarks.push(result);
324 self.update_summary();
325 }
326
327 fn update_summary(&mut self) {
329 self.summary.total_benchmarks = self.benchmarks.len();
330
331 let mut improvements = 0;
333 let mut regressions = 0;
334
335 for bench in &self.benchmarks {
336 if let Some(comp) = &bench.comparison {
337 if comp.significant_improvement {
338 improvements += 1;
339 }
340 if comp.significant_regression {
341 regressions += 1;
342 }
343 }
344 }
345
346 self.summary.failed = regressions;
348 self.summary.successful = self.benchmarks.len().saturating_sub(regressions);
349
350 self.summary.total_runtime_seconds =
352 self.benchmarks.iter().map(|b| b.measurements.statistics.mean_ms / 1000.0).sum();
353
354 self.summary.significant_improvements = improvements;
355 self.summary.significant_regressions = regressions;
356 }
357
358 pub fn to_json(&self) -> Result<String, serde_json::Error> {
364 serde_json::to_string_pretty(self)
365 }
366
367 pub fn to_json_file(&self, path: &std::path::Path) -> std::io::Result<()> {
373 let json = self.to_json().map_err(std::io::Error::other)?;
374 std::fs::write(path, json)
375 }
376
377 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
383 serde_json::from_str(json)
384 }
385
386 pub fn from_json_file(path: &std::path::Path) -> std::io::Result<Self> {
392 let json = std::fs::read_to_string(path)?;
393 Self::from_json(&json).map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
394 }
395}
396
397#[cfg(test)]
398mod tests {
399 use super::*;
400
401 #[test]
402 fn test_benchmark_report_serialization() {
403 let metadata = BenchmarkMetadata {
404 benchmark_suite: "test-suite".to_string(),
405 timestamp: "2025-11-18T10:30:00Z".to_string(),
406 git_commit: "abc123".to_string(),
407 git_branch: "main".to_string(),
408 operator: "test".to_string(),
409 hardware: HardwareInfo {
410 cpu: CpuInfo {
411 model: "Test CPU".to_string(),
412 cores: 4,
413 threads: 8,
414 frequency_mhz: 3000,
415 cache_l1: "32KB".to_string(),
416 cache_l2: "256KB".to_string(),
417 cache_l3: "8MB".to_string(),
418 },
419 memory: MemoryInfo {
420 total_gb: 16.0,
421 memory_type: "DDR4".to_string(),
422 frequency_mhz: Some(2400),
423 },
424 storage: None,
425 },
426 software: SoftwareInfo {
427 os: "Linux".to_string(),
428 kernel: "5.15.0".to_string(),
429 rustc: "1.75.0".to_string(),
430 cargo: "1.75.0".to_string(),
431 llvm: "17.0".to_string(),
432 },
433 environment: EnvironmentConfig {
434 cpu_governor: "performance".to_string(),
435 turbo_boost: "disabled".to_string(),
436 swap: "disabled".to_string(),
437 isolation: None,
438 },
439 };
440
441 let report = BenchmarkReport::new("test-suite", metadata);
442
443 let json = report.to_json().expect("Failed to serialize");
445 assert!(json.contains("\"schema_version\": \"1.0\""));
446
447 let deserialized = BenchmarkReport::from_json(&json).expect("Failed to deserialize");
449 assert_eq!(deserialized.schema_version, "1.0");
450 assert_eq!(deserialized.metadata.benchmark_suite, "test-suite");
451 }
452}