use parking_lot::RwLock;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum BenchmarkError {
#[error("Benchmark failed: {0}")]
Failed(String),
#[error("Invalid configuration: {0}")]
InvalidConfig(String),
#[error("Operation timeout after {0:?}")]
Timeout(Duration),
#[error("Internal error: {0}")]
Internal(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BenchmarkType {
ConnectionEstablishment,
DhtQuery,
ProviderRecord,
MessageThroughput,
ConcurrentOps,
MemoryAllocation,
CpuUtilization,
Custom(u32),
}
impl std::fmt::Display for BenchmarkType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ConnectionEstablishment => write!(f, "Connection Establishment"),
Self::DhtQuery => write!(f, "DHT Query"),
Self::ProviderRecord => write!(f, "Provider Record"),
Self::MessageThroughput => write!(f, "Message Throughput"),
Self::ConcurrentOps => write!(f, "Concurrent Operations"),
Self::MemoryAllocation => write!(f, "Memory Allocation"),
Self::CpuUtilization => write!(f, "CPU Utilization"),
Self::Custom(id) => write!(f, "Custom Benchmark {}", id),
}
}
}
#[derive(Debug, Clone)]
pub struct BenchmarkConfig {
pub warmup_iterations: usize,
pub iterations: usize,
pub operation_timeout: Duration,
pub track_memory: bool,
pub track_cpu: bool,
pub sample_rate: usize,
pub confidence_level: f64,
}
impl Default for BenchmarkConfig {
fn default() -> Self {
Self {
warmup_iterations: 10,
iterations: 100,
operation_timeout: Duration::from_secs(30),
track_memory: true,
track_cpu: false,
sample_rate: 1,
confidence_level: 0.95,
}
}
}
impl BenchmarkConfig {
pub fn quick() -> Self {
Self {
warmup_iterations: 5,
iterations: 50,
..Default::default()
}
}
pub fn thorough() -> Self {
Self {
warmup_iterations: 20,
iterations: 500,
..Default::default()
}
}
pub fn production() -> Self {
Self {
warmup_iterations: 0,
iterations: 10,
track_memory: false,
track_cpu: false,
sample_rate: 10,
..Default::default()
}
}
}
#[derive(Debug, Clone)]
pub struct BenchmarkResult {
pub benchmark_type: BenchmarkType,
pub operations: usize,
pub successful_operations: usize,
pub avg_duration_ms: f64,
pub min_duration_ms: f64,
pub max_duration_ms: f64,
pub median_duration_ms: f64,
pub p95_latency_ms: f64,
pub p99_latency_ms: f64,
pub std_deviation_ms: f64,
pub throughput_ops: f64,
pub total_time_ms: f64,
pub memory_bytes: Option<u64>,
pub peak_memory_bytes: Option<u64>,
pub cpu_utilization: Option<f64>,
pub timestamp: Instant,
}
impl BenchmarkResult {
pub fn success_rate(&self) -> f64 {
if self.operations == 0 {
0.0
} else {
(self.successful_operations as f64 / self.operations as f64) * 100.0
}
}
pub fn meets_criteria(&self, max_avg_ms: f64, min_success_rate: f64) -> bool {
self.avg_duration_ms <= max_avg_ms && self.success_rate() >= min_success_rate
}
}
#[derive(Debug, Clone)]
struct PerformanceSample {
duration_ms: f64,
memory_bytes: Option<u64>,
success: bool,
}
pub struct PerformanceBenchmark {
config: BenchmarkConfig,
results: Arc<RwLock<HashMap<BenchmarkType, Vec<BenchmarkResult>>>>,
}
impl PerformanceBenchmark {
pub fn new(config: BenchmarkConfig) -> Self {
Self {
config,
results: Arc::new(RwLock::new(HashMap::new())),
}
}
#[allow(clippy::should_implement_trait)]
pub fn default() -> Self {
Self::new(BenchmarkConfig::default())
}
pub async fn bench_connection_establishment(
&self,
num_connections: usize,
) -> Result<BenchmarkResult, BenchmarkError> {
let start_time = Instant::now();
let mut samples = Vec::new();
for _ in 0..self.config.warmup_iterations.min(num_connections / 10) {
let sample_start = Instant::now();
tokio::time::sleep(Duration::from_micros(100)).await;
let duration = sample_start.elapsed();
samples.push(PerformanceSample {
duration_ms: duration.as_secs_f64() * 1000.0,
memory_bytes: None,
success: true,
});
}
samples.clear();
for _ in 0..num_connections.min(self.config.iterations) {
let sample_start = Instant::now();
tokio::time::sleep(Duration::from_micros(100 + (rand::random::<u64>() % 50))).await;
let duration = sample_start.elapsed();
samples.push(PerformanceSample {
duration_ms: duration.as_secs_f64() * 1000.0,
memory_bytes: if self.config.track_memory {
Some(1024)
} else {
None
},
success: true,
});
}
let result =
self.calculate_result(BenchmarkType::ConnectionEstablishment, samples, start_time);
self.results
.write()
.entry(BenchmarkType::ConnectionEstablishment)
.or_default()
.push(result.clone());
Ok(result)
}
pub async fn bench_dht_query(
&self,
num_queries: usize,
) -> Result<BenchmarkResult, BenchmarkError> {
let start_time = Instant::now();
let mut samples = Vec::new();
for _ in 0..self.config.warmup_iterations.min(num_queries / 10) {
let sample_start = Instant::now();
tokio::time::sleep(Duration::from_millis(5)).await;
let duration = sample_start.elapsed();
samples.push(PerformanceSample {
duration_ms: duration.as_secs_f64() * 1000.0,
memory_bytes: None,
success: true,
});
}
samples.clear();
for _ in 0..num_queries.min(self.config.iterations) {
let sample_start = Instant::now();
tokio::time::sleep(Duration::from_millis(5 + (rand::random::<u64>() % 10))).await;
let duration = sample_start.elapsed();
samples.push(PerformanceSample {
duration_ms: duration.as_secs_f64() * 1000.0,
memory_bytes: if self.config.track_memory {
Some(2048)
} else {
None
},
success: rand::random::<f64>() > 0.05, });
}
let result = self.calculate_result(BenchmarkType::DhtQuery, samples, start_time);
self.results
.write()
.entry(BenchmarkType::DhtQuery)
.or_default()
.push(result.clone());
Ok(result)
}
pub async fn bench_throughput(
&self,
num_messages: usize,
message_size: usize,
) -> Result<BenchmarkResult, BenchmarkError> {
let start_time = Instant::now();
let mut samples = Vec::new();
for _ in 0..num_messages.min(self.config.iterations) {
let sample_start = Instant::now();
let processing_time = message_size / 1000; tokio::time::sleep(Duration::from_micros(processing_time as u64)).await;
let duration = sample_start.elapsed();
samples.push(PerformanceSample {
duration_ms: duration.as_secs_f64() * 1000.0,
memory_bytes: if self.config.track_memory {
Some(message_size as u64)
} else {
None
},
success: true,
});
}
let result = self.calculate_result(BenchmarkType::MessageThroughput, samples, start_time);
self.results
.write()
.entry(BenchmarkType::MessageThroughput)
.or_default()
.push(result.clone());
Ok(result)
}
pub async fn bench_custom<F, Fut>(
&self,
bench_type: BenchmarkType,
operation: F,
) -> Result<BenchmarkResult, BenchmarkError>
where
F: Fn() -> Fut,
Fut: std::future::Future<Output = bool>,
{
let start_time = Instant::now();
let mut samples = Vec::new();
for _ in 0..self.config.warmup_iterations {
let sample_start = Instant::now();
let success = operation().await;
let duration = sample_start.elapsed();
samples.push(PerformanceSample {
duration_ms: duration.as_secs_f64() * 1000.0,
memory_bytes: None,
success,
});
}
samples.clear();
for _ in 0..self.config.iterations {
let sample_start = Instant::now();
let success = operation().await;
let duration = sample_start.elapsed();
samples.push(PerformanceSample {
duration_ms: duration.as_secs_f64() * 1000.0,
memory_bytes: None,
success,
});
}
let result = self.calculate_result(bench_type, samples, start_time);
self.results
.write()
.entry(bench_type)
.or_default()
.push(result.clone());
Ok(result)
}
fn calculate_result(
&self,
benchmark_type: BenchmarkType,
samples: Vec<PerformanceSample>,
start_time: Instant,
) -> BenchmarkResult {
let operations = samples.len();
let successful_operations = samples.iter().filter(|s| s.success).count();
let mut durations: Vec<f64> = samples.iter().map(|s| s.duration_ms).collect();
durations.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let min_duration_ms = durations.first().copied().unwrap_or(0.0);
let max_duration_ms = durations.last().copied().unwrap_or(0.0);
let avg_duration_ms = if !durations.is_empty() {
durations.iter().sum::<f64>() / durations.len() as f64
} else {
0.0
};
let median_duration_ms = if !durations.is_empty() {
durations[durations.len() / 2]
} else {
0.0
};
let p95_latency_ms = if !durations.is_empty() {
durations[(durations.len() as f64 * 0.95) as usize]
} else {
0.0
};
let p99_latency_ms = if !durations.is_empty() {
durations[(durations.len() as f64 * 0.99) as usize]
} else {
0.0
};
let variance = if !durations.is_empty() {
durations
.iter()
.map(|d| {
let diff = d - avg_duration_ms;
diff * diff
})
.sum::<f64>()
/ durations.len() as f64
} else {
0.0
};
let std_deviation_ms = variance.sqrt();
let total_time_ms = start_time.elapsed().as_secs_f64() * 1000.0;
let throughput_ops = if total_time_ms > 0.0 {
(operations as f64 / total_time_ms) * 1000.0
} else {
0.0
};
let memory_bytes = if self.config.track_memory {
samples
.iter()
.filter_map(|s| s.memory_bytes)
.sum::<u64>()
.checked_div(samples.len() as u64)
} else {
None
};
let peak_memory_bytes = if self.config.track_memory {
samples.iter().filter_map(|s| s.memory_bytes).max()
} else {
None
};
BenchmarkResult {
benchmark_type,
operations,
successful_operations,
avg_duration_ms,
min_duration_ms,
max_duration_ms,
median_duration_ms,
p95_latency_ms,
p99_latency_ms,
std_deviation_ms,
throughput_ops,
total_time_ms,
memory_bytes,
peak_memory_bytes,
cpu_utilization: None,
timestamp: start_time,
}
}
pub fn results(&self) -> HashMap<BenchmarkType, Vec<BenchmarkResult>> {
self.results.read().clone()
}
pub fn results_for(&self, benchmark_type: BenchmarkType) -> Option<Vec<BenchmarkResult>> {
self.results.read().get(&benchmark_type).cloned()
}
pub fn clear_results(&self) {
self.results.write().clear();
}
pub fn summary_report(&self) -> String {
let results = self.results.read();
let mut report = String::from("=== Performance Benchmark Summary ===\n\n");
for (bench_type, results_vec) in results.iter() {
report.push_str(&format!("{}:\n", bench_type));
if let Some(latest) = results_vec.last() {
report.push_str(&format!(" Operations: {}\n", latest.operations));
report.push_str(&format!(" Success Rate: {:.1}%\n", latest.success_rate()));
report.push_str(&format!(" Average: {:.2} ms\n", latest.avg_duration_ms));
report.push_str(&format!(" Median: {:.2} ms\n", latest.median_duration_ms));
report.push_str(&format!(" P95: {:.2} ms\n", latest.p95_latency_ms));
report.push_str(&format!(" P99: {:.2} ms\n", latest.p99_latency_ms));
report.push_str(&format!(
" Throughput: {:.2} ops/s\n",
latest.throughput_ops
));
if let Some(mem) = latest.memory_bytes {
report.push_str(&format!(" Memory: {} bytes\n", mem));
}
}
report.push('\n');
}
report
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_benchmark_config() {
let config = BenchmarkConfig::default();
assert_eq!(config.iterations, 100);
let quick = BenchmarkConfig::quick();
assert_eq!(quick.iterations, 50);
let thorough = BenchmarkConfig::thorough();
assert_eq!(thorough.iterations, 500);
}
#[test]
fn test_benchmark_type_display() {
assert_eq!(
format!("{}", BenchmarkType::ConnectionEstablishment),
"Connection Establishment"
);
assert_eq!(
format!("{}", BenchmarkType::Custom(42)),
"Custom Benchmark 42"
);
}
#[tokio::test]
async fn test_connection_benchmark() {
let benchmark = PerformanceBenchmark::new(BenchmarkConfig::quick());
let result = benchmark
.bench_connection_establishment(10)
.await
.expect("test: bench_connection_establishment should succeed");
assert_eq!(
result.benchmark_type,
BenchmarkType::ConnectionEstablishment
);
assert!(result.operations > 0);
assert!(result.avg_duration_ms >= 0.0);
}
#[tokio::test]
async fn test_dht_query_benchmark() {
let benchmark = PerformanceBenchmark::new(BenchmarkConfig::quick());
let result = benchmark
.bench_dht_query(10)
.await
.expect("test: bench_dht_query should succeed");
assert_eq!(result.benchmark_type, BenchmarkType::DhtQuery);
assert!(result.operations > 0);
assert!(result.success_rate() > 0.0);
}
#[tokio::test]
async fn test_throughput_benchmark() {
let benchmark = PerformanceBenchmark::new(BenchmarkConfig::quick());
let result = benchmark
.bench_throughput(20, 1024)
.await
.expect("test: bench_throughput should succeed");
assert_eq!(result.benchmark_type, BenchmarkType::MessageThroughput);
assert!(result.throughput_ops > 0.0);
}
#[tokio::test]
async fn test_custom_benchmark() {
let benchmark = PerformanceBenchmark::new(BenchmarkConfig::quick());
let result = benchmark
.bench_custom(BenchmarkType::Custom(1), || async {
tokio::time::sleep(Duration::from_micros(100)).await;
true
})
.await
.expect("test: bench_custom should succeed");
assert_eq!(result.benchmark_type, BenchmarkType::Custom(1));
assert_eq!(result.success_rate(), 100.0);
}
#[test]
fn test_benchmark_result_criteria() {
let result = BenchmarkResult {
benchmark_type: BenchmarkType::ConnectionEstablishment,
operations: 100,
successful_operations: 95,
avg_duration_ms: 10.0,
min_duration_ms: 5.0,
max_duration_ms: 20.0,
median_duration_ms: 9.0,
p95_latency_ms: 18.0,
p99_latency_ms: 19.0,
std_deviation_ms: 3.0,
throughput_ops: 100.0,
total_time_ms: 1000.0,
memory_bytes: None,
peak_memory_bytes: None,
cpu_utilization: None,
timestamp: Instant::now(),
};
assert_eq!(result.success_rate(), 95.0);
assert!(result.meets_criteria(15.0, 90.0));
assert!(!result.meets_criteria(5.0, 90.0));
assert!(!result.meets_criteria(15.0, 98.0));
}
#[tokio::test]
async fn test_results_storage() {
let benchmark = PerformanceBenchmark::new(BenchmarkConfig::quick());
benchmark
.bench_connection_establishment(5)
.await
.expect("test: bench_connection_establishment should succeed");
benchmark
.bench_dht_query(5)
.await
.expect("test: bench_dht_query should succeed");
let results = benchmark.results();
assert!(results.contains_key(&BenchmarkType::ConnectionEstablishment));
assert!(results.contains_key(&BenchmarkType::DhtQuery));
let conn_results = benchmark
.results_for(BenchmarkType::ConnectionEstablishment)
.expect("test: results_for ConnectionEstablishment should return Some");
assert_eq!(conn_results.len(), 1);
}
#[tokio::test]
async fn test_clear_results() {
let benchmark = PerformanceBenchmark::new(BenchmarkConfig::quick());
benchmark
.bench_connection_establishment(5)
.await
.expect("test: bench_connection_establishment should succeed");
assert!(!benchmark.results().is_empty());
benchmark.clear_results();
assert!(benchmark.results().is_empty());
}
#[tokio::test]
async fn test_summary_report() {
let benchmark = PerformanceBenchmark::new(BenchmarkConfig::quick());
benchmark
.bench_connection_establishment(5)
.await
.expect("test: bench_connection_establishment should succeed");
benchmark
.bench_dht_query(5)
.await
.expect("test: bench_dht_query should succeed");
let report = benchmark.summary_report();
assert!(report.contains("Performance Benchmark Summary"));
assert!(report.contains("Connection Establishment"));
assert!(report.contains("DHT Query"));
}
}