1use parking_lot::RwLock;
31use std::collections::HashMap;
32use std::sync::Arc;
33use std::time::{Duration, Instant};
34use thiserror::Error;
35
36#[derive(Debug, Error)]
38pub enum BenchmarkError {
39 #[error("Benchmark failed: {0}")]
41 Failed(String),
42
43 #[error("Invalid configuration: {0}")]
45 InvalidConfig(String),
46
47 #[error("Operation timeout after {0:?}")]
49 Timeout(Duration),
50
51 #[error("Internal error: {0}")]
53 Internal(String),
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
58pub enum BenchmarkType {
59 ConnectionEstablishment,
61 DhtQuery,
63 ProviderRecord,
65 MessageThroughput,
67 ConcurrentOps,
69 MemoryAllocation,
71 CpuUtilization,
73 Custom(u32),
75}
76
77impl std::fmt::Display for BenchmarkType {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 match self {
80 Self::ConnectionEstablishment => write!(f, "Connection Establishment"),
81 Self::DhtQuery => write!(f, "DHT Query"),
82 Self::ProviderRecord => write!(f, "Provider Record"),
83 Self::MessageThroughput => write!(f, "Message Throughput"),
84 Self::ConcurrentOps => write!(f, "Concurrent Operations"),
85 Self::MemoryAllocation => write!(f, "Memory Allocation"),
86 Self::CpuUtilization => write!(f, "CPU Utilization"),
87 Self::Custom(id) => write!(f, "Custom Benchmark {}", id),
88 }
89 }
90}
91
92#[derive(Debug, Clone)]
94pub struct BenchmarkConfig {
95 pub warmup_iterations: usize,
97
98 pub iterations: usize,
100
101 pub operation_timeout: Duration,
103
104 pub track_memory: bool,
106
107 pub track_cpu: bool,
109
110 pub sample_rate: usize,
112
113 pub confidence_level: f64,
115}
116
117impl Default for BenchmarkConfig {
118 fn default() -> Self {
119 Self {
120 warmup_iterations: 10,
121 iterations: 100,
122 operation_timeout: Duration::from_secs(30),
123 track_memory: true,
124 track_cpu: false,
125 sample_rate: 1,
126 confidence_level: 0.95,
127 }
128 }
129}
130
131impl BenchmarkConfig {
132 pub fn quick() -> Self {
134 Self {
135 warmup_iterations: 5,
136 iterations: 50,
137 ..Default::default()
138 }
139 }
140
141 pub fn thorough() -> Self {
143 Self {
144 warmup_iterations: 20,
145 iterations: 500,
146 ..Default::default()
147 }
148 }
149
150 pub fn production() -> Self {
152 Self {
153 warmup_iterations: 0,
154 iterations: 10,
155 track_memory: false,
156 track_cpu: false,
157 sample_rate: 10,
158 ..Default::default()
159 }
160 }
161}
162
163#[derive(Debug, Clone)]
165pub struct BenchmarkResult {
166 pub benchmark_type: BenchmarkType,
168
169 pub operations: usize,
171
172 pub successful_operations: usize,
174
175 pub avg_duration_ms: f64,
177
178 pub min_duration_ms: f64,
180
181 pub max_duration_ms: f64,
183
184 pub median_duration_ms: f64,
186
187 pub p95_latency_ms: f64,
189
190 pub p99_latency_ms: f64,
192
193 pub std_deviation_ms: f64,
195
196 pub throughput_ops: f64,
198
199 pub total_time_ms: f64,
201
202 pub memory_bytes: Option<u64>,
204
205 pub peak_memory_bytes: Option<u64>,
207
208 pub cpu_utilization: Option<f64>,
210
211 pub timestamp: Instant,
213}
214
215impl BenchmarkResult {
216 pub fn success_rate(&self) -> f64 {
218 if self.operations == 0 {
219 0.0
220 } else {
221 (self.successful_operations as f64 / self.operations as f64) * 100.0
222 }
223 }
224
225 pub fn meets_criteria(&self, max_avg_ms: f64, min_success_rate: f64) -> bool {
227 self.avg_duration_ms <= max_avg_ms && self.success_rate() >= min_success_rate
228 }
229}
230
231#[derive(Debug, Clone)]
233struct PerformanceSample {
234 duration_ms: f64,
235 memory_bytes: Option<u64>,
236 success: bool,
237}
238
239pub struct PerformanceBenchmark {
241 config: BenchmarkConfig,
242 results: Arc<RwLock<HashMap<BenchmarkType, Vec<BenchmarkResult>>>>,
243}
244
245impl PerformanceBenchmark {
246 pub fn new(config: BenchmarkConfig) -> Self {
248 Self {
249 config,
250 results: Arc::new(RwLock::new(HashMap::new())),
251 }
252 }
253
254 #[allow(clippy::should_implement_trait)]
256 pub fn default() -> Self {
257 Self::new(BenchmarkConfig::default())
258 }
259
260 pub async fn bench_connection_establishment(
262 &self,
263 num_connections: usize,
264 ) -> Result<BenchmarkResult, BenchmarkError> {
265 let start_time = Instant::now();
266 let mut samples = Vec::new();
267
268 for _ in 0..self.config.warmup_iterations.min(num_connections / 10) {
270 let sample_start = Instant::now();
271 tokio::time::sleep(Duration::from_micros(100)).await;
273 let duration = sample_start.elapsed();
274 samples.push(PerformanceSample {
275 duration_ms: duration.as_secs_f64() * 1000.0,
276 memory_bytes: None,
277 success: true,
278 });
279 }
280 samples.clear();
281
282 for _ in 0..num_connections.min(self.config.iterations) {
284 let sample_start = Instant::now();
285 tokio::time::sleep(Duration::from_micros(100 + (rand::random::<u64>() % 50))).await;
287 let duration = sample_start.elapsed();
288
289 samples.push(PerformanceSample {
290 duration_ms: duration.as_secs_f64() * 1000.0,
291 memory_bytes: if self.config.track_memory {
292 Some(1024)
293 } else {
294 None
295 },
296 success: true,
297 });
298 }
299
300 let result =
301 self.calculate_result(BenchmarkType::ConnectionEstablishment, samples, start_time);
302
303 self.results
305 .write()
306 .entry(BenchmarkType::ConnectionEstablishment)
307 .or_default()
308 .push(result.clone());
309
310 Ok(result)
311 }
312
313 pub async fn bench_dht_query(
315 &self,
316 num_queries: usize,
317 ) -> Result<BenchmarkResult, BenchmarkError> {
318 let start_time = Instant::now();
319 let mut samples = Vec::new();
320
321 for _ in 0..self.config.warmup_iterations.min(num_queries / 10) {
323 let sample_start = Instant::now();
324 tokio::time::sleep(Duration::from_millis(5)).await;
325 let duration = sample_start.elapsed();
326 samples.push(PerformanceSample {
327 duration_ms: duration.as_secs_f64() * 1000.0,
328 memory_bytes: None,
329 success: true,
330 });
331 }
332 samples.clear();
333
334 for _ in 0..num_queries.min(self.config.iterations) {
336 let sample_start = Instant::now();
337 tokio::time::sleep(Duration::from_millis(5 + (rand::random::<u64>() % 10))).await;
339 let duration = sample_start.elapsed();
340
341 samples.push(PerformanceSample {
342 duration_ms: duration.as_secs_f64() * 1000.0,
343 memory_bytes: if self.config.track_memory {
344 Some(2048)
345 } else {
346 None
347 },
348 success: rand::random::<f64>() > 0.05, });
350 }
351
352 let result = self.calculate_result(BenchmarkType::DhtQuery, samples, start_time);
353
354 self.results
355 .write()
356 .entry(BenchmarkType::DhtQuery)
357 .or_default()
358 .push(result.clone());
359
360 Ok(result)
361 }
362
363 pub async fn bench_throughput(
365 &self,
366 num_messages: usize,
367 message_size: usize,
368 ) -> Result<BenchmarkResult, BenchmarkError> {
369 let start_time = Instant::now();
370 let mut samples = Vec::new();
371
372 for _ in 0..num_messages.min(self.config.iterations) {
374 let sample_start = Instant::now();
375 let processing_time = message_size / 1000; tokio::time::sleep(Duration::from_micros(processing_time as u64)).await;
378 let duration = sample_start.elapsed();
379
380 samples.push(PerformanceSample {
381 duration_ms: duration.as_secs_f64() * 1000.0,
382 memory_bytes: if self.config.track_memory {
383 Some(message_size as u64)
384 } else {
385 None
386 },
387 success: true,
388 });
389 }
390
391 let result = self.calculate_result(BenchmarkType::MessageThroughput, samples, start_time);
392
393 self.results
394 .write()
395 .entry(BenchmarkType::MessageThroughput)
396 .or_default()
397 .push(result.clone());
398
399 Ok(result)
400 }
401
402 pub async fn bench_custom<F, Fut>(
404 &self,
405 bench_type: BenchmarkType,
406 operation: F,
407 ) -> Result<BenchmarkResult, BenchmarkError>
408 where
409 F: Fn() -> Fut,
410 Fut: std::future::Future<Output = bool>,
411 {
412 let start_time = Instant::now();
413 let mut samples = Vec::new();
414
415 for _ in 0..self.config.warmup_iterations {
417 let sample_start = Instant::now();
418 let success = operation().await;
419 let duration = sample_start.elapsed();
420 samples.push(PerformanceSample {
421 duration_ms: duration.as_secs_f64() * 1000.0,
422 memory_bytes: None,
423 success,
424 });
425 }
426 samples.clear();
427
428 for _ in 0..self.config.iterations {
430 let sample_start = Instant::now();
431 let success = operation().await;
432 let duration = sample_start.elapsed();
433
434 samples.push(PerformanceSample {
435 duration_ms: duration.as_secs_f64() * 1000.0,
436 memory_bytes: None,
437 success,
438 });
439 }
440
441 let result = self.calculate_result(bench_type, samples, start_time);
442
443 self.results
444 .write()
445 .entry(bench_type)
446 .or_default()
447 .push(result.clone());
448
449 Ok(result)
450 }
451
452 fn calculate_result(
454 &self,
455 benchmark_type: BenchmarkType,
456 samples: Vec<PerformanceSample>,
457 start_time: Instant,
458 ) -> BenchmarkResult {
459 let operations = samples.len();
460 let successful_operations = samples.iter().filter(|s| s.success).count();
461
462 let mut durations: Vec<f64> = samples.iter().map(|s| s.duration_ms).collect();
463 durations.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
464
465 let min_duration_ms = durations.first().copied().unwrap_or(0.0);
466 let max_duration_ms = durations.last().copied().unwrap_or(0.0);
467 let avg_duration_ms = if !durations.is_empty() {
468 durations.iter().sum::<f64>() / durations.len() as f64
469 } else {
470 0.0
471 };
472
473 let median_duration_ms = if !durations.is_empty() {
474 durations[durations.len() / 2]
475 } else {
476 0.0
477 };
478
479 let p95_latency_ms = if !durations.is_empty() {
480 durations[(durations.len() as f64 * 0.95) as usize]
481 } else {
482 0.0
483 };
484
485 let p99_latency_ms = if !durations.is_empty() {
486 durations[(durations.len() as f64 * 0.99) as usize]
487 } else {
488 0.0
489 };
490
491 let variance = if !durations.is_empty() {
492 durations
493 .iter()
494 .map(|d| {
495 let diff = d - avg_duration_ms;
496 diff * diff
497 })
498 .sum::<f64>()
499 / durations.len() as f64
500 } else {
501 0.0
502 };
503 let std_deviation_ms = variance.sqrt();
504
505 let total_time_ms = start_time.elapsed().as_secs_f64() * 1000.0;
506 let throughput_ops = if total_time_ms > 0.0 {
507 (operations as f64 / total_time_ms) * 1000.0
508 } else {
509 0.0
510 };
511
512 let memory_bytes = if self.config.track_memory {
513 samples
514 .iter()
515 .filter_map(|s| s.memory_bytes)
516 .sum::<u64>()
517 .checked_div(samples.len() as u64)
518 } else {
519 None
520 };
521
522 let peak_memory_bytes = if self.config.track_memory {
523 samples.iter().filter_map(|s| s.memory_bytes).max()
524 } else {
525 None
526 };
527
528 BenchmarkResult {
529 benchmark_type,
530 operations,
531 successful_operations,
532 avg_duration_ms,
533 min_duration_ms,
534 max_duration_ms,
535 median_duration_ms,
536 p95_latency_ms,
537 p99_latency_ms,
538 std_deviation_ms,
539 throughput_ops,
540 total_time_ms,
541 memory_bytes,
542 peak_memory_bytes,
543 cpu_utilization: None,
544 timestamp: start_time,
545 }
546 }
547
548 pub fn results(&self) -> HashMap<BenchmarkType, Vec<BenchmarkResult>> {
550 self.results.read().clone()
551 }
552
553 pub fn results_for(&self, benchmark_type: BenchmarkType) -> Option<Vec<BenchmarkResult>> {
555 self.results.read().get(&benchmark_type).cloned()
556 }
557
558 pub fn clear_results(&self) {
560 self.results.write().clear();
561 }
562
563 pub fn summary_report(&self) -> String {
565 let results = self.results.read();
566 let mut report = String::from("=== Performance Benchmark Summary ===\n\n");
567
568 for (bench_type, results_vec) in results.iter() {
569 report.push_str(&format!("{}:\n", bench_type));
570
571 if let Some(latest) = results_vec.last() {
572 report.push_str(&format!(" Operations: {}\n", latest.operations));
573 report.push_str(&format!(" Success Rate: {:.1}%\n", latest.success_rate()));
574 report.push_str(&format!(" Average: {:.2} ms\n", latest.avg_duration_ms));
575 report.push_str(&format!(" Median: {:.2} ms\n", latest.median_duration_ms));
576 report.push_str(&format!(" P95: {:.2} ms\n", latest.p95_latency_ms));
577 report.push_str(&format!(" P99: {:.2} ms\n", latest.p99_latency_ms));
578 report.push_str(&format!(
579 " Throughput: {:.2} ops/s\n",
580 latest.throughput_ops
581 ));
582
583 if let Some(mem) = latest.memory_bytes {
584 report.push_str(&format!(" Memory: {} bytes\n", mem));
585 }
586 }
587
588 report.push('\n');
589 }
590
591 report
592 }
593}
594
595#[cfg(test)]
596mod tests {
597 use super::*;
598
599 #[test]
600 fn test_benchmark_config() {
601 let config = BenchmarkConfig::default();
602 assert_eq!(config.iterations, 100);
603
604 let quick = BenchmarkConfig::quick();
605 assert_eq!(quick.iterations, 50);
606
607 let thorough = BenchmarkConfig::thorough();
608 assert_eq!(thorough.iterations, 500);
609 }
610
611 #[test]
612 fn test_benchmark_type_display() {
613 assert_eq!(
614 format!("{}", BenchmarkType::ConnectionEstablishment),
615 "Connection Establishment"
616 );
617 assert_eq!(
618 format!("{}", BenchmarkType::Custom(42)),
619 "Custom Benchmark 42"
620 );
621 }
622
623 #[tokio::test]
624 async fn test_connection_benchmark() {
625 let benchmark = PerformanceBenchmark::new(BenchmarkConfig::quick());
626 let result = benchmark.bench_connection_establishment(10).await.unwrap();
627
628 assert_eq!(
629 result.benchmark_type,
630 BenchmarkType::ConnectionEstablishment
631 );
632 assert!(result.operations > 0);
633 assert!(result.avg_duration_ms >= 0.0);
634 }
635
636 #[tokio::test]
637 async fn test_dht_query_benchmark() {
638 let benchmark = PerformanceBenchmark::new(BenchmarkConfig::quick());
639 let result = benchmark.bench_dht_query(10).await.unwrap();
640
641 assert_eq!(result.benchmark_type, BenchmarkType::DhtQuery);
642 assert!(result.operations > 0);
643 assert!(result.success_rate() > 0.0);
644 }
645
646 #[tokio::test]
647 async fn test_throughput_benchmark() {
648 let benchmark = PerformanceBenchmark::new(BenchmarkConfig::quick());
649 let result = benchmark.bench_throughput(20, 1024).await.unwrap();
650
651 assert_eq!(result.benchmark_type, BenchmarkType::MessageThroughput);
652 assert!(result.throughput_ops > 0.0);
653 }
654
655 #[tokio::test]
656 async fn test_custom_benchmark() {
657 let benchmark = PerformanceBenchmark::new(BenchmarkConfig::quick());
658
659 let result = benchmark
660 .bench_custom(BenchmarkType::Custom(1), || async {
661 tokio::time::sleep(Duration::from_micros(100)).await;
662 true
663 })
664 .await
665 .unwrap();
666
667 assert_eq!(result.benchmark_type, BenchmarkType::Custom(1));
668 assert_eq!(result.success_rate(), 100.0);
669 }
670
671 #[test]
672 fn test_benchmark_result_criteria() {
673 let result = BenchmarkResult {
674 benchmark_type: BenchmarkType::ConnectionEstablishment,
675 operations: 100,
676 successful_operations: 95,
677 avg_duration_ms: 10.0,
678 min_duration_ms: 5.0,
679 max_duration_ms: 20.0,
680 median_duration_ms: 9.0,
681 p95_latency_ms: 18.0,
682 p99_latency_ms: 19.0,
683 std_deviation_ms: 3.0,
684 throughput_ops: 100.0,
685 total_time_ms: 1000.0,
686 memory_bytes: None,
687 peak_memory_bytes: None,
688 cpu_utilization: None,
689 timestamp: Instant::now(),
690 };
691
692 assert_eq!(result.success_rate(), 95.0);
693 assert!(result.meets_criteria(15.0, 90.0));
694 assert!(!result.meets_criteria(5.0, 90.0));
695 assert!(!result.meets_criteria(15.0, 98.0));
696 }
697
698 #[tokio::test]
699 async fn test_results_storage() {
700 let benchmark = PerformanceBenchmark::new(BenchmarkConfig::quick());
701
702 benchmark.bench_connection_establishment(5).await.unwrap();
703 benchmark.bench_dht_query(5).await.unwrap();
704
705 let results = benchmark.results();
706 assert!(results.contains_key(&BenchmarkType::ConnectionEstablishment));
707 assert!(results.contains_key(&BenchmarkType::DhtQuery));
708
709 let conn_results = benchmark
710 .results_for(BenchmarkType::ConnectionEstablishment)
711 .unwrap();
712 assert_eq!(conn_results.len(), 1);
713 }
714
715 #[tokio::test]
716 async fn test_clear_results() {
717 let benchmark = PerformanceBenchmark::new(BenchmarkConfig::quick());
718
719 benchmark.bench_connection_establishment(5).await.unwrap();
720 assert!(!benchmark.results().is_empty());
721
722 benchmark.clear_results();
723 assert!(benchmark.results().is_empty());
724 }
725
726 #[tokio::test]
727 async fn test_summary_report() {
728 let benchmark = PerformanceBenchmark::new(BenchmarkConfig::quick());
729
730 benchmark.bench_connection_establishment(5).await.unwrap();
731 benchmark.bench_dht_query(5).await.unwrap();
732
733 let report = benchmark.summary_report();
734 assert!(report.contains("Performance Benchmark Summary"));
735 assert!(report.contains("Connection Establishment"));
736 assert!(report.contains("DHT Query"));
737 }
738}