use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenchmarkReport {
pub schema_version: String,
pub metadata: BenchmarkMetadata,
pub benchmarks: Vec<BenchmarkResult>,
pub summary: BenchmarkSummary,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenchmarkMetadata {
pub benchmark_suite: String,
pub timestamp: String,
pub git_commit: String,
pub git_branch: String,
pub operator: String,
pub hardware: HardwareInfo,
pub software: SoftwareInfo,
pub environment: EnvironmentConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HardwareInfo {
pub cpu: CpuInfo,
pub memory: MemoryInfo,
pub storage: Option<StorageInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CpuInfo {
pub model: String,
pub cores: usize,
pub threads: usize,
pub frequency_mhz: u64,
pub cache_l1: String,
pub cache_l2: String,
pub cache_l3: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryInfo {
pub total_gb: f64,
#[serde(rename = "type")]
pub memory_type: String,
pub frequency_mhz: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageInfo {
#[serde(rename = "type")]
pub storage_type: String,
pub model: String,
pub capacity_gb: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SoftwareInfo {
pub os: String,
pub kernel: String,
pub rustc: String,
pub cargo: String,
pub llvm: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnvironmentConfig {
pub cpu_governor: String,
pub turbo_boost: String,
pub swap: String,
pub isolation: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenchmarkResult {
pub name: String,
pub category: String,
pub scope: String,
pub binary: String,
pub optimization_profile: String,
pub measurements: Measurements,
pub comparison: Option<Comparison>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Measurements {
pub warmup_runs: usize,
pub measured_runs: usize,
pub raw_values_ms: Vec<f64>,
pub outliers_removed: Vec<f64>,
pub statistics: Statistics,
pub distribution: Distribution,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Statistics {
pub mean_ms: f64,
pub median_ms: f64,
pub std_dev_ms: f64,
pub min_ms: f64,
pub max_ms: f64,
pub coefficient_of_variation: f64,
pub confidence_interval_95: (f64, f64),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Distribution {
pub normality_test: String,
pub normality_p_value: f64,
pub is_normal: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Comparison {
pub baseline_commit: String,
pub baseline_mean_ms: f64,
pub speedup_ratio: f64,
pub speedup_ci_95: (f64, f64),
pub t_test_p_value: f64,
pub effect_size_cohens_d: f64,
pub significant_improvement: bool,
pub significant_regression: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenchmarkSummary {
pub total_benchmarks: usize,
pub successful: usize,
pub failed: usize,
pub total_runtime_seconds: f64,
pub significant_improvements: usize,
pub significant_regressions: usize,
pub metrics: HashMap<String, f64>,
}
impl BenchmarkReport {
#[must_use]
pub fn new(suite_name: &str, metadata: BenchmarkMetadata) -> Self {
let mut metadata = metadata;
if metadata.benchmark_suite.is_empty() && !suite_name.is_empty() {
metadata.benchmark_suite = suite_name.to_string();
}
Self {
schema_version: "1.0".to_string(),
metadata,
benchmarks: Vec::new(),
summary: BenchmarkSummary {
total_benchmarks: 0,
successful: 0,
failed: 0,
total_runtime_seconds: 0.0,
significant_improvements: 0,
significant_regressions: 0,
metrics: HashMap::new(),
},
}
}
pub fn add_benchmark(&mut self, result: BenchmarkResult) {
self.benchmarks.push(result);
self.update_summary();
}
fn update_summary(&mut self) {
self.summary.total_benchmarks = self.benchmarks.len();
let mut improvements = 0;
let mut regressions = 0;
for bench in &self.benchmarks {
if let Some(comp) = &bench.comparison {
if comp.significant_improvement {
improvements += 1;
}
if comp.significant_regression {
regressions += 1;
}
}
}
self.summary.failed = regressions;
self.summary.successful = self.benchmarks.len().saturating_sub(regressions);
self.summary.total_runtime_seconds =
self.benchmarks.iter().map(|b| b.measurements.statistics.mean_ms / 1000.0).sum();
self.summary.significant_improvements = improvements;
self.summary.significant_regressions = regressions;
}
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(self)
}
pub fn to_json_file(&self, path: &std::path::Path) -> std::io::Result<()> {
let json = self.to_json().map_err(std::io::Error::other)?;
std::fs::write(path, json)
}
pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
serde_json::from_str(json)
}
pub fn from_json_file(path: &std::path::Path) -> std::io::Result<Self> {
let json = std::fs::read_to_string(path)?;
Self::from_json(&json).map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_benchmark_report_serialization() {
let metadata = BenchmarkMetadata {
benchmark_suite: "test-suite".to_string(),
timestamp: "2025-11-18T10:30:00Z".to_string(),
git_commit: "abc123".to_string(),
git_branch: "main".to_string(),
operator: "test".to_string(),
hardware: HardwareInfo {
cpu: CpuInfo {
model: "Test CPU".to_string(),
cores: 4,
threads: 8,
frequency_mhz: 3000,
cache_l1: "32KB".to_string(),
cache_l2: "256KB".to_string(),
cache_l3: "8MB".to_string(),
},
memory: MemoryInfo {
total_gb: 16.0,
memory_type: "DDR4".to_string(),
frequency_mhz: Some(2400),
},
storage: None,
},
software: SoftwareInfo {
os: "Linux".to_string(),
kernel: "5.15.0".to_string(),
rustc: "1.75.0".to_string(),
cargo: "1.75.0".to_string(),
llvm: "17.0".to_string(),
},
environment: EnvironmentConfig {
cpu_governor: "performance".to_string(),
turbo_boost: "disabled".to_string(),
swap: "disabled".to_string(),
isolation: None,
},
};
let report = BenchmarkReport::new("test-suite", metadata);
let json = report.to_json().expect("Failed to serialize");
assert!(json.contains("\"schema_version\": \"1.0\""));
let deserialized = BenchmarkReport::from_json(&json).expect("Failed to deserialize");
assert_eq!(deserialized.schema_version, "1.0");
assert_eq!(deserialized.metadata.benchmark_suite, "test-suite");
}
}