#[derive(Debug, Clone)]
pub struct BenchmarkMetric {
pub name: String,
pub samples: Vec<f64>,
pub unit: String,
}
impl BenchmarkMetric {
pub fn new(name: impl Into<String>, samples: Vec<f64>, unit: impl Into<String>) -> Self {
Self {
name: name.into(),
samples,
unit: unit.into(),
}
}
pub fn mean(&self) -> f64 {
if self.samples.is_empty() {
return 0.0;
}
self.samples.iter().sum::<f64>() / self.samples.len() as f64
}
pub fn std_dev(&self) -> f64 {
if self.samples.len() < 2 {
return 0.0;
}
let mean = self.mean();
let variance = self.samples.iter().map(|x| (x - mean).powi(2)).sum::<f64>()
/ (self.samples.len() - 1) as f64;
variance.sqrt()
}
pub fn cv(&self) -> f64 {
let mean = self.mean();
if mean.abs() < 1e-10 {
return 0.0;
}
(self.std_dev() / mean) * 100.0
}
}
#[derive(Debug, Clone)]
pub struct BenchmarkResults {
pub commit: String,
pub branch: String,
pub timestamp_ns: u64,
pub metrics: Vec<BenchmarkMetric>,
pub duration_ms: u64,
pub host: String,
}
impl BenchmarkResults {
pub fn new(commit: impl Into<String>, branch: impl Into<String>) -> Self {
Self {
commit: commit.into(),
branch: branch.into(),
timestamp_ns: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0),
metrics: Vec::new(),
duration_ms: 0,
host: hostname(),
}
}
pub fn add_metric(&mut self, metric: BenchmarkMetric) {
self.metrics.push(metric);
}
pub fn get_metric(&self, name: &str) -> Option<&BenchmarkMetric> {
self.metrics.iter().find(|m| m.name == name)
}
}
fn hostname() -> String {
std::env::var("HOSTNAME")
.or_else(|_| std::env::var("HOST"))
.unwrap_or_else(|_| "unknown".to_string())
}