1use crate::Result;
6use chrono::{DateTime, Duration, Utc};
7use colored::Colorize;
8use comfy_table::{Cell, CellAlignment, Row, Table};
9use serde::{Deserialize, Serialize};
10
11pub struct Benchmarker {
13 results: Vec<BenchmarkResult>,
15 warmup_iterations: usize,
17 iterations: usize,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct BenchmarkResult {
24 pub name: String,
26 pub iterations: usize,
28 pub total_ms: i64,
30 pub avg_ms: f64,
32 pub min_ms: i64,
34 pub max_ms: i64,
36 pub std_dev_ms: f64,
38 pub ops_per_sec: f64,
40}
41
42impl Benchmarker {
43 pub fn new() -> Self {
45 Self {
46 results: Vec::new(),
47 warmup_iterations: 3,
48 iterations: 10,
49 }
50 }
51
52 pub fn with_warmup(mut self, warmup: usize) -> Self {
54 self.warmup_iterations = warmup;
55 self
56 }
57
58 pub fn with_iterations(mut self, iterations: usize) -> Self {
60 self.iterations = iterations;
61 self
62 }
63
64 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 for _ in 0..self.warmup_iterations {
73 f()?;
74 }
75
76 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 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 pub fn results(&self) -> &[BenchmarkResult] {
128 &self.results
129 }
130
131 pub fn clear(&mut self) {
133 self.results.clear();
134 }
135
136 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 pub fn export_json(&self) -> Result<String> {
178 Ok(serde_json::to_string_pretty(&self.results)?)
179 }
180
181 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#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct Comparison {
209 pub name1: String,
211 pub name2: String,
213 pub speedup: f64,
215 pub diff_ms: f64,
217 pub faster: String,
219}
220
221impl Comparison {
222 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
243pub struct Timer {
245 name: String,
247 start: DateTime<Utc>,
249}
250
251impl Timer {
252 pub fn start(name: impl Into<String>) -> Self {
254 Self {
255 name: name.into(),
256 start: Utc::now(),
257 }
258 }
259
260 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 pub fn elapsed(&self) -> Duration {
271 Utc::now().signed_duration_since(self.start)
272 }
273}
274
275pub struct TimerResult {
277 name: String,
279 duration: Duration,
281}
282
283impl TimerResult {
284 pub fn milliseconds(&self) -> i64 {
286 self.duration.num_milliseconds()
287 }
288
289 pub fn microseconds(&self) -> i64 {
291 self.duration.num_microseconds().unwrap_or(0)
292 }
293
294 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}