use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct BenchmarkResults {
pub median_latency: Duration,
pub p95_latency: Duration,
pub p99_latency: Duration,
pub throughput: f64,
pub samples: usize,
pub last_update: Instant,
}
impl BenchmarkResults {
pub fn new(
median_latency: Duration,
p95_latency: Duration,
p99_latency: Duration,
throughput: f64,
samples: usize,
) -> Self {
BenchmarkResults {
median_latency,
p95_latency,
p99_latency,
throughput,
samples,
last_update: Instant::now(),
}
}
pub fn from_samples(latencies: &mut [Duration]) -> Self {
assert!(!latencies.is_empty(), "Cannot create benchmark results from empty samples");
latencies.sort();
let samples = latencies.len();
let median = latencies[samples / 2];
let p95 = latencies[(samples * 95) / 100];
let p99 = latencies[(samples * 99) / 100];
let total_duration: Duration = latencies.iter().sum();
let throughput = samples as f64 / total_duration.as_secs_f64();
BenchmarkResults {
median_latency: median,
p95_latency: p95,
p99_latency: p99,
throughput,
samples,
last_update: Instant::now(),
}
}
pub fn is_stale(&self, threshold: Duration) -> bool {
self.last_update.elapsed() > threshold
}
}
impl Default for BenchmarkResults {
fn default() -> Self {
BenchmarkResults {
median_latency: Duration::from_micros(1),
p95_latency: Duration::from_micros(2),
p99_latency: Duration::from_micros(3),
throughput: 1_000_000.0, samples: 0,
last_update: Instant::now(),
}
}
}
pub struct MicroBenchmark {
warmup_iterations: usize,
measurement_iterations: usize,
}
impl MicroBenchmark {
pub fn new(warmup_iterations: usize, measurement_iterations: usize) -> Self {
MicroBenchmark {
warmup_iterations,
measurement_iterations,
}
}
pub fn run<F>(&self, mut operation: F) -> BenchmarkResults
where
F: FnMut(),
{
for _ in 0..self.warmup_iterations {
operation();
}
let mut latencies = Vec::with_capacity(self.measurement_iterations);
for _ in 0..self.measurement_iterations {
let start = Instant::now();
operation();
latencies.push(start.elapsed());
}
BenchmarkResults::from_samples(&mut latencies)
}
pub fn run_with_data<D, F>(&self, mut data_gen: D, mut operation: F) -> BenchmarkResults
where
D: FnMut() -> Vec<u8>,
F: FnMut(&[u8]),
{
let test_data = data_gen();
for _ in 0..self.warmup_iterations {
operation(&test_data);
}
let mut latencies = Vec::with_capacity(self.measurement_iterations);
for _ in 0..self.measurement_iterations {
let start = Instant::now();
operation(&test_data);
latencies.push(start.elapsed());
}
BenchmarkResults::from_samples(&mut latencies)
}
}
impl Default for MicroBenchmark {
fn default() -> Self {
MicroBenchmark {
warmup_iterations: 10,
measurement_iterations: 100,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_benchmark_results_creation() {
let results = BenchmarkResults::new(
Duration::from_micros(100),
Duration::from_micros(150),
Duration::from_micros(200),
10_000.0,
100,
);
assert_eq!(results.median_latency, Duration::from_micros(100));
assert_eq!(results.throughput, 10_000.0);
assert_eq!(results.samples, 100);
}
#[test]
fn test_benchmark_results_from_samples() {
let mut latencies = vec![
Duration::from_micros(100),
Duration::from_micros(200),
Duration::from_micros(150),
Duration::from_micros(180),
Duration::from_micros(120),
];
let results = BenchmarkResults::from_samples(&mut latencies);
assert_eq!(results.samples, 5);
assert!(results.median_latency >= Duration::from_micros(100));
assert!(results.median_latency <= Duration::from_micros(200));
assert!(results.throughput > 0.0);
}
#[test]
fn test_micro_benchmark_run() {
let benchmark = MicroBenchmark::new(5, 10);
let mut counter = 0;
let results = benchmark.run(|| {
counter += 1;
std::hint::black_box(counter);
});
assert_eq!(results.samples, 10);
assert!(results.median_latency > Duration::ZERO);
assert!(results.throughput > 0.0);
}
#[test]
fn test_micro_benchmark_with_data() {
let benchmark = MicroBenchmark::default();
let data_gen = || vec![0x01; 1024];
let operation = |data: &[u8]| {
let sum = data.iter().fold(0u64, |acc, &x| acc + x as u64);
std::hint::black_box(sum);
};
let results = benchmark.run_with_data(data_gen, operation);
assert_eq!(results.samples, 100);
assert!(results.throughput > 0.0);
}
#[test]
fn test_benchmark_staleness() {
let results = BenchmarkResults::default();
assert!(!results.is_stale(Duration::from_secs(1)));
let old_results = BenchmarkResults {
last_update: Instant::now() - Duration::from_secs(10),
..Default::default()
};
assert!(old_results.is_stale(Duration::from_secs(5)));
}
}