ipfrs_network/
benchmarking.rs

1//! Performance Benchmarking - Comprehensive benchmarking utilities for network components
2//!
3//! This module provides utilities to benchmark various network operations:
4//! - Connection establishment latency
5//! - DHT query performance
6//! - Throughput measurements
7//! - Concurrent operation scalability
8//! - Memory usage tracking
9//! - CPU utilization
10//!
11//! Useful for performance regression testing and optimization.
12//!
13//! # Example
14//!
15//! ```rust
16//! use ipfrs_network::{PerformanceBenchmark, BenchmarkConfig};
17//!
18//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
19//! let config = BenchmarkConfig::default();
20//! let benchmark = PerformanceBenchmark::new(config);
21//!
22//! // Run a connection benchmark
23//! let result = benchmark.bench_connection_establishment(100).await?;
24//! println!("Average connection time: {:.2} ms", result.avg_duration_ms);
25//! println!("P95 latency: {:.2} ms", result.p95_latency_ms);
26//! # Ok(())
27//! # }
28//! ```
29
30use parking_lot::RwLock;
31use std::collections::HashMap;
32use std::sync::Arc;
33use std::time::{Duration, Instant};
34use thiserror::Error;
35
36/// Errors that can occur during benchmarking
37#[derive(Debug, Error)]
38pub enum BenchmarkError {
39    /// Benchmark failed
40    #[error("Benchmark failed: {0}")]
41    Failed(String),
42
43    /// Invalid configuration
44    #[error("Invalid configuration: {0}")]
45    InvalidConfig(String),
46
47    /// Operation timeout
48    #[error("Operation timeout after {0:?}")]
49    Timeout(Duration),
50
51    /// Internal error
52    #[error("Internal error: {0}")]
53    Internal(String),
54}
55
56/// Type of benchmark operation
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
58pub enum BenchmarkType {
59    /// Connection establishment
60    ConnectionEstablishment,
61    /// DHT query operations
62    DhtQuery,
63    /// Provider record operations
64    ProviderRecord,
65    /// Message throughput
66    MessageThroughput,
67    /// Concurrent operations
68    ConcurrentOps,
69    /// Memory allocation
70    MemoryAllocation,
71    /// CPU utilization
72    CpuUtilization,
73    /// Custom benchmark
74    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/// Configuration for performance benchmarking
93#[derive(Debug, Clone)]
94pub struct BenchmarkConfig {
95    /// Number of warmup iterations
96    pub warmup_iterations: usize,
97
98    /// Number of benchmark iterations
99    pub iterations: usize,
100
101    /// Timeout for each operation
102    pub operation_timeout: Duration,
103
104    /// Enable memory tracking
105    pub track_memory: bool,
106
107    /// Enable CPU tracking
108    pub track_cpu: bool,
109
110    /// Sample rate for tracking (1 = every operation, 10 = every 10th operation)
111    pub sample_rate: usize,
112
113    /// Confidence level for statistical calculations (e.g., 0.95 for 95%)
114    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    /// Configuration for quick benchmarks (fewer iterations)
133    pub fn quick() -> Self {
134        Self {
135            warmup_iterations: 5,
136            iterations: 50,
137            ..Default::default()
138        }
139    }
140
141    /// Configuration for thorough benchmarks (more iterations)
142    pub fn thorough() -> Self {
143        Self {
144            warmup_iterations: 20,
145            iterations: 500,
146            ..Default::default()
147        }
148    }
149
150    /// Configuration for production monitoring (minimal overhead)
151    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/// Result of a benchmark run
164#[derive(Debug, Clone)]
165pub struct BenchmarkResult {
166    /// Type of benchmark
167    pub benchmark_type: BenchmarkType,
168
169    /// Number of operations completed
170    pub operations: usize,
171
172    /// Number of successful operations
173    pub successful_operations: usize,
174
175    /// Average duration in milliseconds
176    pub avg_duration_ms: f64,
177
178    /// Minimum duration in milliseconds
179    pub min_duration_ms: f64,
180
181    /// Maximum duration in milliseconds
182    pub max_duration_ms: f64,
183
184    /// Median duration (P50) in milliseconds
185    pub median_duration_ms: f64,
186
187    /// P95 latency in milliseconds
188    pub p95_latency_ms: f64,
189
190    /// P99 latency in milliseconds
191    pub p99_latency_ms: f64,
192
193    /// Standard deviation
194    pub std_deviation_ms: f64,
195
196    /// Throughput in operations per second
197    pub throughput_ops: f64,
198
199    /// Total time spent in milliseconds
200    pub total_time_ms: f64,
201
202    /// Memory usage in bytes (if tracked)
203    pub memory_bytes: Option<u64>,
204
205    /// Peak memory usage in bytes (if tracked)
206    pub peak_memory_bytes: Option<u64>,
207
208    /// CPU utilization percentage (if tracked)
209    pub cpu_utilization: Option<f64>,
210
211    /// Timestamp when benchmark started
212    pub timestamp: Instant,
213}
214
215impl BenchmarkResult {
216    /// Calculate success rate
217    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    /// Check if benchmark meets performance criteria
226    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/// Performance sample for statistical analysis
232#[derive(Debug, Clone)]
233struct PerformanceSample {
234    duration_ms: f64,
235    memory_bytes: Option<u64>,
236    success: bool,
237}
238
239/// Performance benchmark runner
240pub struct PerformanceBenchmark {
241    config: BenchmarkConfig,
242    results: Arc<RwLock<HashMap<BenchmarkType, Vec<BenchmarkResult>>>>,
243}
244
245impl PerformanceBenchmark {
246    /// Create a new performance benchmark
247    pub fn new(config: BenchmarkConfig) -> Self {
248        Self {
249            config,
250            results: Arc::new(RwLock::new(HashMap::new())),
251        }
252    }
253
254    /// Create benchmark with default configuration
255    #[allow(clippy::should_implement_trait)]
256    pub fn default() -> Self {
257        Self::new(BenchmarkConfig::default())
258    }
259
260    /// Benchmark connection establishment
261    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        // Warmup
269        for _ in 0..self.config.warmup_iterations.min(num_connections / 10) {
270            let sample_start = Instant::now();
271            // Simulate connection
272            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        // Actual benchmark
283        for _ in 0..num_connections.min(self.config.iterations) {
284            let sample_start = Instant::now();
285            // Simulate connection establishment
286            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        // Store result
304        self.results
305            .write()
306            .entry(BenchmarkType::ConnectionEstablishment)
307            .or_default()
308            .push(result.clone());
309
310        Ok(result)
311    }
312
313    /// Benchmark DHT query performance
314    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        // Warmup
322        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        // Actual benchmark
335        for _ in 0..num_queries.min(self.config.iterations) {
336            let sample_start = Instant::now();
337            // Simulate DHT query
338            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, // 95% success rate
349            });
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    /// Benchmark message throughput
364    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        // Actual benchmark (no warmup for throughput tests)
373        for _ in 0..num_messages.min(self.config.iterations) {
374            let sample_start = Instant::now();
375            // Simulate message processing
376            let processing_time = message_size / 1000; // Simulate processing based on size
377            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    /// Run a custom benchmark
403    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        // Warmup
416        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        // Actual benchmark
429        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    /// Calculate benchmark result from samples
453    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    /// Get all benchmark results
549    pub fn results(&self) -> HashMap<BenchmarkType, Vec<BenchmarkResult>> {
550        self.results.read().clone()
551    }
552
553    /// Get results for a specific benchmark type
554    pub fn results_for(&self, benchmark_type: BenchmarkType) -> Option<Vec<BenchmarkResult>> {
555        self.results.read().get(&benchmark_type).cloned()
556    }
557
558    /// Clear all results
559    pub fn clear_results(&self) {
560        self.results.write().clear();
561    }
562
563    /// Generate a summary report
564    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}