use std::collections::HashMap;
use std::time::{Duration, SystemTime};
#[derive(Debug, Clone, Default)]
pub struct WorkloadSize {
pub elements: usize,
pub edges: Option<usize>,
pub bytes: Option<usize>,
}
impl WorkloadSize {
#[must_use]
pub fn new(elements: usize) -> Self {
Self {
elements,
edges: None,
bytes: None,
}
}
#[must_use]
pub fn with_edges(mut self, edges: usize) -> Self {
self.edges = Some(edges);
self
}
#[must_use]
pub fn with_bytes(mut self, bytes: usize) -> Self {
self.bytes = Some(bytes);
self
}
}
#[derive(Debug, Clone)]
pub struct BenchmarkResult {
pub workload_id: String,
pub size: usize,
pub throughput_ops: f64,
pub total_time: Duration,
pub iterations: Option<usize>,
pub converged: Option<bool>,
pub measurement_times: Vec<Duration>,
pub custom_metrics: HashMap<String, f64>,
}
impl BenchmarkResult {
#[must_use]
pub fn new(workload_id: impl Into<String>, size: usize, total_time: Duration) -> Self {
let throughput_ops = if total_time.as_secs_f64() > 0.0 {
size as f64 / total_time.as_secs_f64()
} else {
0.0
};
Self {
workload_id: workload_id.into(),
size,
throughput_ops,
total_time,
iterations: None,
converged: None,
measurement_times: vec![total_time],
custom_metrics: HashMap::new(),
}
}
pub fn from_measurements(
workload_id: impl Into<String>,
size: usize,
iterations: Option<usize>,
converged: Option<bool>,
measurements: &[Duration],
) -> Self {
let total_time = if measurements.is_empty() {
Duration::ZERO
} else {
let sum: Duration = measurements.iter().sum();
sum / measurements.len() as u32
};
let throughput_ops = if total_time.as_secs_f64() > 0.0 {
let ops = if let Some(iters) = iterations {
size * iters
} else {
size
};
ops as f64 / total_time.as_secs_f64()
} else {
0.0
};
Self {
workload_id: workload_id.into(),
size,
throughput_ops,
total_time,
iterations,
converged,
measurement_times: measurements.to_vec(),
custom_metrics: HashMap::new(),
}
}
#[must_use]
pub fn with_metric(mut self, key: impl Into<String>, value: f64) -> Self {
self.custom_metrics.insert(key.into(), value);
self
}
#[must_use]
pub fn throughput_mops(&self) -> f64 {
self.throughput_ops / 1_000_000.0
}
#[must_use]
pub fn total_time_ms(&self) -> f64 {
self.total_time.as_secs_f64() * 1000.0
}
#[must_use]
pub fn throughput_stddev(&self) -> f64 {
if self.measurement_times.len() < 2 {
return 0.0;
}
let throughputs: Vec<f64> = self
.measurement_times
.iter()
.filter_map(|t| {
let secs = t.as_secs_f64();
if secs > 0.0 {
Some(self.size as f64 / secs)
} else {
None
}
})
.collect();
if throughputs.is_empty() {
return 0.0;
}
let mean = throughputs.iter().sum::<f64>() / throughputs.len() as f64;
let variance =
throughputs.iter().map(|t| (t - mean).powi(2)).sum::<f64>() / throughputs.len() as f64;
variance.sqrt()
}
}
#[derive(Debug, Clone)]
pub struct BenchmarkBaseline {
pub results: HashMap<(String, usize), BenchmarkResult>,
pub version: String,
pub timestamp: SystemTime,
}
impl BenchmarkBaseline {
#[must_use]
pub fn from_results(results: &[BenchmarkResult], version: &str) -> Self {
let mut map = HashMap::new();
for result in results {
map.insert((result.workload_id.clone(), result.size), result.clone());
}
Self {
results: map,
version: version.to_string(),
timestamp: SystemTime::now(),
}
}
#[must_use]
pub fn get(&self, workload_id: &str, size: usize) -> Option<&BenchmarkResult> {
self.results.get(&(workload_id.to_string(), size))
}
#[must_use]
pub fn len(&self) -> usize {
self.results.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.results.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_workload_size() {
let size = WorkloadSize::new(1000)
.with_edges(5000)
.with_bytes(1024 * 1024);
assert_eq!(size.elements, 1000);
assert_eq!(size.edges, Some(5000));
assert_eq!(size.bytes, Some(1024 * 1024));
}
#[test]
fn test_benchmark_result_new() {
let result = BenchmarkResult::new("test", 1000, Duration::from_millis(100));
assert_eq!(result.workload_id, "test");
assert_eq!(result.size, 1000);
assert!(result.throughput_ops > 0.0);
assert_eq!(result.total_time, Duration::from_millis(100));
}
#[test]
fn test_benchmark_result_from_measurements() {
let measurements = vec![
Duration::from_millis(100),
Duration::from_millis(110),
Duration::from_millis(90),
];
let result =
BenchmarkResult::from_measurements("test", 1000, Some(50), Some(true), &measurements);
assert_eq!(result.workload_id, "test");
assert_eq!(result.iterations, Some(50));
assert_eq!(result.converged, Some(true));
assert!(result.throughput_ops > 0.0);
}
#[test]
fn test_benchmark_result_throughput_mops() {
let result = BenchmarkResult::new("test", 1_000_000, Duration::from_secs(1));
assert!((result.throughput_mops() - 1.0).abs() < 0.001);
}
#[test]
fn test_benchmark_baseline() {
let results = vec![
BenchmarkResult::new("workload_a", 1000, Duration::from_millis(100)),
BenchmarkResult::new("workload_b", 2000, Duration::from_millis(200)),
];
let baseline = BenchmarkBaseline::from_results(&results, "v1.0");
assert_eq!(baseline.len(), 2);
assert!(!baseline.is_empty());
assert!(baseline.get("workload_a", 1000).is_some());
assert!(baseline.get("workload_a", 2000).is_none());
}
}