Skip to main content

forge_guard/benchmark/
mod.rs

1//! Benchmark runner — measures performance of audit components.
2
3use crate::core::ForgeGuardError;
4use serde::{Deserialize, Serialize};
5use std::time::{Duration, Instant};
6
7/// Results of a single benchmark run.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct BenchmarkResult {
10    pub name: String,
11    pub avg_ms: f64,
12    pub min_ms: f64,
13    pub max_ms: f64,
14    pub median_ms: f64,
15    pub p99_ms: f64,
16    pub samples: u32,
17}
18
19/// Runner for performance benchmarks.
20pub struct BenchmarkRunner {
21    iterations: u32,
22    warmup: u32,
23}
24
25impl BenchmarkRunner {
26    /// Create a new benchmark runner.
27    pub fn new(iterations: u32, warmup: u32) -> Self {
28        Self { iterations, warmup }
29    }
30
31    /// Run all available benchmarks.
32    pub fn benchmark_all(&mut self) -> Result<Vec<BenchmarkResult>, ForgeGuardError> {
33        let mut results = Vec::new();
34
35        results.push(self.benchmark_fn("source_discovery", || {
36            let _ = std::fs::read_dir(".");
37            std::thread::sleep(Duration::from_micros(100));
38        }));
39
40        results.push(self.benchmark_fn("pattern_matching", || {
41            let _ = regex::Regex::new(r"function\s+\w+\s*\(");
42            std::thread::sleep(Duration::from_micros(50));
43        }));
44
45        results.push(self.benchmark_fn("json_serialization", || {
46            let data = serde_json::json!({"test": "value", "count": 100});
47            let _ = serde_json::to_string(&data);
48        }));
49
50        Ok(results)
51    }
52
53    /// Benchmark a specific module.
54    pub fn benchmark_module(&mut self, module: &str) -> Result<BenchmarkResult, ForgeGuardError> {
55        match module {
56            "source_discovery" => Ok(self.benchmark_fn("source_discovery", || {
57                let _ = std::fs::read_dir(".");
58                std::thread::sleep(Duration::from_micros(100));
59            })),
60            "pattern_matching" => Ok(self.benchmark_fn("pattern_matching", || {
61                let _ = regex::Regex::new(r"function\s+\w+\s*\(");
62                std::thread::sleep(Duration::from_micros(50));
63            })),
64            "json_serialization" => Ok(self.benchmark_fn("json_serialization", || {
65                let data = serde_json::json!({"test": "value"});
66                let _ = serde_json::to_string(&data);
67            })),
68            _ => Err(ForgeGuardError::Config(format!(
69                "Unknown benchmark module: {}",
70                module
71            ))),
72        }
73    }
74
75    /// Benchmark a closure, collecting timing statistics.
76    fn benchmark_fn(&self, name: &str, mut f: impl FnMut()) -> BenchmarkResult {
77        // Warmup
78        for _ in 0..self.warmup {
79            f();
80        }
81
82        // Measured runs
83        let mut times = Vec::with_capacity(self.iterations as usize);
84        for _ in 0..self.iterations {
85            let start = Instant::now();
86            f();
87            times.push(start.elapsed());
88        }
89
90        let mut ms_times: Vec<f64> = times.iter().map(|t| t.as_secs_f64() * 1000.0).collect();
91        ms_times.sort_by(|a, b| a.partial_cmp(b).unwrap());
92
93        let avg = ms_times.iter().sum::<f64>() / ms_times.len() as f64;
94        let min = ms_times.first().copied().unwrap_or(0.0);
95        let max = ms_times.last().copied().unwrap_or(0.0);
96        let median = ms_times.get(ms_times.len() / 2).copied().unwrap_or(0.0);
97        let p99_idx = (ms_times.len() as f64 * 0.99) as usize;
98        let p99 = ms_times
99            .get(p99_idx.min(ms_times.len().saturating_sub(1)))
100            .copied()
101            .unwrap_or(0.0);
102
103        BenchmarkResult {
104            name: name.to_string(),
105            avg_ms: (avg * 100.0).round() / 100.0,
106            min_ms: (min * 100.0).round() / 100.0,
107            max_ms: (max * 100.0).round() / 100.0,
108            median_ms: (median * 100.0).round() / 100.0,
109            p99_ms: (p99 * 100.0).round() / 100.0,
110            samples: self.iterations,
111        }
112    }
113}