Skip to main content

oxigdal_dev_tools/
benchmarker.rs

1//! Quick benchmarking utilities
2//!
3//! This module provides simple benchmarking tools for OxiGDAL operations.
4
5use crate::Result;
6use chrono::{DateTime, Duration, Utc};
7use colored::Colorize;
8use comfy_table::{Cell, CellAlignment, Row, Table};
9use serde::{Deserialize, Serialize};
10
11/// Quick benchmarker
12pub struct Benchmarker {
13    /// Benchmark results
14    results: Vec<BenchmarkResult>,
15    /// Warmup iterations
16    warmup_iterations: usize,
17    /// Benchmark iterations
18    iterations: usize,
19}
20
21/// Benchmark result
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct BenchmarkResult {
24    /// Benchmark name
25    pub name: String,
26    /// Number of iterations
27    pub iterations: usize,
28    /// Total duration in milliseconds
29    pub total_ms: i64,
30    /// Average duration in milliseconds
31    pub avg_ms: f64,
32    /// Minimum duration in milliseconds
33    pub min_ms: i64,
34    /// Maximum duration in milliseconds
35    pub max_ms: i64,
36    /// Standard deviation in milliseconds
37    pub std_dev_ms: f64,
38    /// Operations per second
39    pub ops_per_sec: f64,
40}
41
42impl Benchmarker {
43    /// Create a new benchmarker
44    pub fn new() -> Self {
45        Self {
46            results: Vec::new(),
47            warmup_iterations: 3,
48            iterations: 10,
49        }
50    }
51
52    /// Set warmup iterations
53    pub fn with_warmup(mut self, warmup: usize) -> Self {
54        self.warmup_iterations = warmup;
55        self
56    }
57
58    /// Set benchmark iterations
59    pub fn with_iterations(mut self, iterations: usize) -> Self {
60        self.iterations = iterations;
61        self
62    }
63
64    /// Benchmark a function
65    pub fn bench<F>(&mut self, name: impl Into<String>, mut f: F) -> Result<BenchmarkResult>
66    where
67        F: FnMut() -> Result<()>,
68    {
69        let name = name.into();
70
71        // Warmup
72        for _ in 0..self.warmup_iterations {
73            f()?;
74        }
75
76        // Benchmark
77        let mut durations = Vec::with_capacity(self.iterations);
78
79        for _ in 0..self.iterations {
80            let start = Utc::now();
81            f()?;
82            let end = Utc::now();
83
84            let duration = end.signed_duration_since(start);
85            durations.push(duration.num_milliseconds());
86        }
87
88        // Calculate statistics
89        let total_ms: i64 = durations.iter().sum();
90        let avg_ms = total_ms as f64 / self.iterations as f64;
91        let min_ms = *durations.iter().min().unwrap_or(&0);
92        let max_ms = *durations.iter().max().unwrap_or(&0);
93
94        let variance = durations
95            .iter()
96            .map(|&d| {
97                let diff = d as f64 - avg_ms;
98                diff * diff
99            })
100            .sum::<f64>()
101            / self.iterations as f64;
102        let std_dev_ms = variance.sqrt();
103
104        let ops_per_sec = if avg_ms > 0.0 {
105            1000.0 / avg_ms
106        } else {
107            f64::INFINITY
108        };
109
110        let result = BenchmarkResult {
111            name: name.clone(),
112            iterations: self.iterations,
113            total_ms,
114            avg_ms,
115            min_ms,
116            max_ms,
117            std_dev_ms,
118            ops_per_sec,
119        };
120
121        self.results.push(result.clone());
122
123        Ok(result)
124    }
125
126    /// Get all results
127    pub fn results(&self) -> &[BenchmarkResult] {
128        &self.results
129    }
130
131    /// Clear results
132    pub fn clear(&mut self) {
133        self.results.clear();
134    }
135
136    /// Generate report
137    pub fn report(&self) -> String {
138        let mut report = String::new();
139        report.push_str(&format!("\n{}\n", "Benchmark Report".bold()));
140        report.push_str(&format!("{}\n\n", "=".repeat(80)));
141
142        if self.results.is_empty() {
143            report.push_str("No benchmark results\n");
144            return report;
145        }
146
147        let mut table = Table::new();
148        table.set_header(Row::from(vec![
149            Cell::new("Benchmark").set_alignment(CellAlignment::Left),
150            Cell::new("Iterations").set_alignment(CellAlignment::Right),
151            Cell::new("Avg (ms)").set_alignment(CellAlignment::Right),
152            Cell::new("Min (ms)").set_alignment(CellAlignment::Right),
153            Cell::new("Max (ms)").set_alignment(CellAlignment::Right),
154            Cell::new("Std Dev").set_alignment(CellAlignment::Right),
155            Cell::new("Ops/sec").set_alignment(CellAlignment::Right),
156        ]));
157
158        for result in &self.results {
159            table.add_row(Row::from(vec![
160                Cell::new(&result.name),
161                Cell::new(format!("{}", result.iterations)),
162                Cell::new(format!("{:.3}", result.avg_ms)),
163                Cell::new(format!("{}", result.min_ms)),
164                Cell::new(format!("{}", result.max_ms)),
165                Cell::new(format!("{:.3}", result.std_dev_ms)),
166                Cell::new(format!("{:.2}", result.ops_per_sec)),
167            ]));
168        }
169
170        report.push_str(&table.to_string());
171        report.push('\n');
172
173        report
174    }
175
176    /// Export results as JSON
177    pub fn export_json(&self) -> Result<String> {
178        Ok(serde_json::to_string_pretty(&self.results)?)
179    }
180
181    /// Compare two benchmarks
182    pub fn compare(&self, name1: &str, name2: &str) -> Option<Comparison> {
183        let result1 = self.results.iter().find(|r| r.name == name1)?;
184        let result2 = self.results.iter().find(|r| r.name == name2)?;
185
186        Some(Comparison {
187            name1: result1.name.clone(),
188            name2: result2.name.clone(),
189            speedup: result2.avg_ms / result1.avg_ms,
190            diff_ms: result1.avg_ms - result2.avg_ms,
191            faster: if result1.avg_ms < result2.avg_ms {
192                result1.name.clone()
193            } else {
194                result2.name.clone()
195            },
196        })
197    }
198}
199
200impl Default for Benchmarker {
201    fn default() -> Self {
202        Self::new()
203    }
204}
205
206/// Benchmark comparison
207#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct Comparison {
209    /// First benchmark name
210    pub name1: String,
211    /// Second benchmark name
212    pub name2: String,
213    /// Speedup factor
214    pub speedup: f64,
215    /// Difference in milliseconds
216    pub diff_ms: f64,
217    /// Which is faster
218    pub faster: String,
219}
220
221impl Comparison {
222    /// Format comparison
223    pub fn format(&self) -> String {
224        format!(
225            "{} is {:.2}x {} than {} ({:.3} ms difference)",
226            self.faster,
227            self.speedup.abs(),
228            if self.speedup > 1.0 {
229                "faster"
230            } else {
231                "slower"
232            },
233            if self.faster == self.name1 {
234                &self.name2
235            } else {
236                &self.name1
237            },
238            self.diff_ms.abs()
239        )
240    }
241}
242
243/// Simple timer for quick measurements
244pub struct Timer {
245    /// Timer name
246    name: String,
247    /// Start time
248    start: DateTime<Utc>,
249}
250
251impl Timer {
252    /// Create and start a new timer
253    pub fn start(name: impl Into<String>) -> Self {
254        Self {
255            name: name.into(),
256            start: Utc::now(),
257        }
258    }
259
260    /// Stop timer and return elapsed duration
261    pub fn stop(self) -> TimerResult {
262        let elapsed = Utc::now().signed_duration_since(self.start);
263        TimerResult {
264            name: self.name,
265            duration: elapsed,
266        }
267    }
268
269    /// Get elapsed duration without stopping
270    pub fn elapsed(&self) -> Duration {
271        Utc::now().signed_duration_since(self.start)
272    }
273}
274
275/// Timer result
276pub struct TimerResult {
277    /// Timer name
278    name: String,
279    /// Duration
280    duration: Duration,
281}
282
283impl TimerResult {
284    /// Get duration in milliseconds
285    pub fn milliseconds(&self) -> i64 {
286        self.duration.num_milliseconds()
287    }
288
289    /// Get duration in microseconds
290    pub fn microseconds(&self) -> i64 {
291        self.duration.num_microseconds().unwrap_or(0)
292    }
293
294    /// Format result
295    pub fn format(&self) -> String {
296        let ms = self.milliseconds();
297        if ms > 1000 {
298            format!("{}: {:.2} s", self.name, ms as f64 / 1000.0)
299        } else if ms > 0 {
300            format!("{}: {} ms", self.name, ms)
301        } else {
302            format!("{}: {} µs", self.name, self.microseconds())
303        }
304    }
305}
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310    use std::thread;
311    use std::time::Duration as StdDuration;
312
313    #[test]
314    fn test_benchmarker_creation() {
315        let bench = Benchmarker::new();
316        assert_eq!(bench.iterations, 10);
317        assert_eq!(bench.warmup_iterations, 3);
318    }
319
320    #[test]
321    fn test_benchmarker_config() {
322        let bench = Benchmarker::new().with_warmup(5).with_iterations(20);
323        assert_eq!(bench.warmup_iterations, 5);
324        assert_eq!(bench.iterations, 20);
325    }
326
327    #[test]
328    fn test_benchmark_simple() -> Result<()> {
329        let mut bench = Benchmarker::new().with_warmup(1).with_iterations(5);
330
331        let result = bench.bench("test", || {
332            thread::sleep(StdDuration::from_millis(10));
333            Ok(())
334        })?;
335
336        assert_eq!(result.name, "test");
337        assert_eq!(result.iterations, 5);
338        assert!(result.avg_ms >= 10.0);
339
340        Ok(())
341    }
342
343    #[test]
344    fn test_benchmarker_results() -> Result<()> {
345        let mut bench = Benchmarker::new().with_iterations(5);
346
347        bench.bench("test1", || Ok(()))?;
348        bench.bench("test2", || Ok(()))?;
349
350        assert_eq!(bench.results().len(), 2);
351
352        Ok(())
353    }
354
355    #[test]
356    fn test_timer() {
357        let timer = Timer::start("test");
358        thread::sleep(StdDuration::from_millis(50));
359        let result = timer.stop();
360
361        assert!(result.milliseconds() >= 50);
362    }
363
364    #[test]
365    fn test_timer_elapsed() {
366        let timer = Timer::start("test");
367        thread::sleep(StdDuration::from_millis(10));
368        let elapsed = timer.elapsed();
369
370        assert!(elapsed.num_milliseconds() >= 10);
371    }
372
373    #[test]
374    fn test_comparison() -> Result<()> {
375        let mut bench = Benchmarker::new().with_warmup(1).with_iterations(3);
376
377        bench.bench("fast", || {
378            thread::sleep(StdDuration::from_millis(10));
379            Ok(())
380        })?;
381
382        bench.bench("slow", || {
383            thread::sleep(StdDuration::from_millis(20));
384            Ok(())
385        })?;
386
387        let comp = bench.compare("fast", "slow");
388        assert!(comp.is_some());
389
390        if let Some(c) = comp {
391            assert_eq!(c.faster, "fast");
392        }
393
394        Ok(())
395    }
396
397    #[test]
398    fn test_export_json() -> Result<()> {
399        let mut bench = Benchmarker::new().with_iterations(2);
400        bench.bench("test", || Ok(()))?;
401
402        let json = bench.export_json()?;
403        assert!(json.contains("test"));
404        assert!(json.contains("iterations"));
405
406        Ok(())
407    }
408}