1use crate::providers::VisionProvider;
7use serde::{Deserialize, Serialize};
8use std::sync::Arc;
9use std::time::Instant;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct BenchmarkResult {
14 pub provider: String,
16 pub iterations: usize,
18 pub min_ms: f64,
20 pub max_ms: f64,
22 pub mean_ms: f64,
24 pub median_ms: f64,
26 pub std_dev_ms: f64,
28 pub p95_ms: f64,
30 pub p99_ms: f64,
32 pub total_ms: f64,
34 pub ops_per_sec: f64,
36}
37
38impl BenchmarkResult {
39 pub fn from_timings(provider: String, mut timings: Vec<f64>) -> Self {
41 if timings.is_empty() {
42 return Self::empty(provider);
43 }
44
45 timings.sort_by(|a, b| a.partial_cmp(b).unwrap());
46
47 let iterations = timings.len();
48 let min_ms = timings[0];
49 let max_ms = timings[timings.len() - 1];
50 let total_ms: f64 = timings.iter().sum();
51 let mean_ms = total_ms / iterations as f64;
52
53 let median_ms = if iterations.is_multiple_of(2) {
54 (timings[iterations / 2 - 1] + timings[iterations / 2]) / 2.0
55 } else {
56 timings[iterations / 2]
57 };
58
59 let variance: f64 = timings
60 .iter()
61 .map(|t| {
62 let diff = t - mean_ms;
63 diff * diff
64 })
65 .sum::<f64>()
66 / iterations as f64;
67 let std_dev_ms = variance.sqrt();
68
69 let p95_idx = ((iterations as f64 * 0.95) as usize).min(iterations - 1);
70 let p99_idx = ((iterations as f64 * 0.99) as usize).min(iterations - 1);
71
72 let p95_ms = timings[p95_idx];
73 let p99_ms = timings[p99_idx];
74
75 let ops_per_sec = if mean_ms > 0.0 { 1000.0 / mean_ms } else { 0.0 };
76
77 Self {
78 provider,
79 iterations,
80 min_ms,
81 max_ms,
82 mean_ms,
83 median_ms,
84 std_dev_ms,
85 p95_ms,
86 p99_ms,
87 total_ms,
88 ops_per_sec,
89 }
90 }
91
92 fn empty(provider: String) -> Self {
94 Self {
95 provider,
96 iterations: 0,
97 min_ms: 0.0,
98 max_ms: 0.0,
99 mean_ms: 0.0,
100 median_ms: 0.0,
101 std_dev_ms: 0.0,
102 p95_ms: 0.0,
103 p99_ms: 0.0,
104 total_ms: 0.0,
105 ops_per_sec: 0.0,
106 }
107 }
108
109 pub fn format_row(&self) -> String {
111 format!(
112 "{:<12} {:>8} {:>8.2} {:>8.2} {:>8.2} {:>8.2} {:>8.2}",
113 self.provider,
114 self.iterations,
115 self.mean_ms,
116 self.median_ms,
117 self.p95_ms,
118 self.p99_ms,
119 self.ops_per_sec
120 )
121 }
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct ComparisonReport {
127 pub results: Vec<BenchmarkResult>,
129 pub image_size_bytes: usize,
131 pub timestamp: String,
133}
134
135impl ComparisonReport {
136 pub fn new(results: Vec<BenchmarkResult>, image_size_bytes: usize) -> Self {
138 Self {
139 results,
140 image_size_bytes,
141 timestamp: chrono::Utc::now().to_rfc3339(),
142 }
143 }
144
145 pub fn format_table(&self) -> String {
147 let mut output = String::new();
148
149 output.push('\n');
150 output.push_str("═══════════════════════════════════════════════════════════════════\n");
151 output.push_str(" PROVIDER PERFORMANCE COMPARISON\n");
152 output.push_str("═══════════════════════════════════════════════════════════════════\n");
153 output.push_str(&format!("Image Size: {} bytes\n", self.image_size_bytes));
154 output.push_str(&format!("Timestamp: {}\n", self.timestamp));
155 output.push_str("───────────────────────────────────────────────────────────────────\n");
156 output.push_str(&format!(
157 "{:<12} {:>8} {:>8} {:>8} {:>8} {:>8} {:>8}\n",
158 "Provider", "Iters", "Mean", "Median", "P95", "P99", "Ops/sec"
159 ));
160 output.push_str(&format!(
161 "{:<12} {:>8} {:>8} {:>8} {:>8} {:>8} {:>8}\n",
162 "", "", "(ms)", "(ms)", "(ms)", "(ms)", ""
163 ));
164 output.push_str("───────────────────────────────────────────────────────────────────\n");
165
166 for result in &self.results {
167 output.push_str(&result.format_row());
168 output.push('\n');
169 }
170
171 output.push_str("═══════════════════════════════════════════════════════════════════\n");
172
173 output
174 }
175
176 pub fn fastest_provider(&self) -> Option<&BenchmarkResult> {
178 self.results
179 .iter()
180 .min_by(|a, b| a.mean_ms.partial_cmp(&b.mean_ms).unwrap())
181 }
182
183 pub fn save_json(&self, path: &str) -> crate::Result<()> {
185 let json = serde_json::to_string_pretty(self).map_err(|e| {
186 crate::VisionError::config(format!("Failed to serialize report: {}", e))
187 })?;
188
189 std::fs::write(path, json)
190 .map_err(|e| crate::VisionError::config(format!("Failed to write report: {}", e)))?;
191
192 Ok(())
193 }
194}
195
196#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct MemoryProfile {
199 pub provider: String,
201 pub peak_bytes: usize,
203 pub avg_bytes: usize,
205 pub bytes_per_op: usize,
207}
208
209impl MemoryProfile {
210 pub fn format(&self) -> String {
212 format!(
213 "Provider: {}\n Peak: {} MB\n Average: {} MB\n Per Operation: {} KB",
214 self.provider,
215 self.peak_bytes / (1024 * 1024),
216 self.avg_bytes / (1024 * 1024),
217 self.bytes_per_op / 1024
218 )
219 }
220}
221
222pub struct BenchmarkRunner {
224 iterations: usize,
225 warmup_iterations: usize,
226}
227
228impl Default for BenchmarkRunner {
229 fn default() -> Self {
230 Self {
231 iterations: 10,
232 warmup_iterations: 2,
233 }
234 }
235}
236
237impl BenchmarkRunner {
238 pub fn new(iterations: usize) -> Self {
240 Self {
241 iterations,
242 warmup_iterations: iterations.min(2),
243 }
244 }
245
246 pub fn with_warmup(mut self, warmup: usize) -> Self {
248 self.warmup_iterations = warmup;
249 self
250 }
251
252 pub async fn benchmark_provider(
254 &self,
255 provider: Arc<dyn VisionProvider>,
256 image_data: &[u8],
257 ) -> crate::Result<BenchmarkResult> {
258 let provider_name = provider.provider_name().to_string();
259
260 for _ in 0..self.warmup_iterations {
262 let _ = provider.process_image(image_data).await;
263 }
264
265 let mut timings = Vec::with_capacity(self.iterations);
267
268 for _ in 0..self.iterations {
269 let start = Instant::now();
270 let _ = provider.process_image(image_data).await?;
271 let elapsed = start.elapsed();
272 timings.push(elapsed.as_secs_f64() * 1000.0);
273 }
274
275 Ok(BenchmarkResult::from_timings(provider_name, timings))
276 }
277
278 pub async fn compare_providers(
280 &self,
281 providers: Vec<Arc<dyn VisionProvider>>,
282 image_data: &[u8],
283 ) -> crate::Result<ComparisonReport> {
284 let mut results = Vec::new();
285
286 for provider in providers {
287 let result = self.benchmark_provider(provider, image_data).await?;
288 results.push(result);
289 }
290
291 Ok(ComparisonReport::new(results, image_data.len()))
292 }
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298
299 #[test]
300 fn test_benchmark_result_from_timings() {
301 let timings = vec![10.0, 20.0, 15.0, 25.0, 12.0];
302 let result = BenchmarkResult::from_timings("test".to_string(), timings);
303
304 assert_eq!(result.provider, "test");
305 assert_eq!(result.iterations, 5);
306 assert_eq!(result.min_ms, 10.0);
307 assert_eq!(result.max_ms, 25.0);
308 assert!((result.mean_ms - 16.4).abs() < 0.1);
309 }
310
311 #[test]
312 fn test_benchmark_result_empty() {
313 let result = BenchmarkResult::from_timings("test".to_string(), vec![]);
314 assert_eq!(result.iterations, 0);
315 assert_eq!(result.mean_ms, 0.0);
316 }
317
318 #[test]
319 fn test_comparison_report() {
320 let results = vec![
321 BenchmarkResult::from_timings("provider1".to_string(), vec![10.0, 12.0, 11.0]),
322 BenchmarkResult::from_timings("provider2".to_string(), vec![20.0, 22.0, 21.0]),
323 ];
324
325 let report = ComparisonReport::new(results, 1024);
326 assert_eq!(report.results.len(), 2);
327 assert_eq!(report.image_size_bytes, 1024);
328
329 let fastest = report.fastest_provider().unwrap();
330 assert_eq!(fastest.provider, "provider1");
331 }
332
333 #[test]
334 fn test_benchmark_result_format_row() {
335 let result = BenchmarkResult::from_timings("test".to_string(), vec![10.0, 20.0, 15.0]);
336 let row = result.format_row();
337 assert!(row.contains("test"));
338 assert!(row.contains("3")); }
340
341 #[test]
342 fn test_comparison_report_format_table() {
343 let results = vec![BenchmarkResult::from_timings(
344 "mock".to_string(),
345 vec![5.0, 6.0, 5.5],
346 )];
347
348 let report = ComparisonReport::new(results, 1024);
349 let table = report.format_table();
350 assert!(table.contains("PROVIDER PERFORMANCE COMPARISON"));
351 assert!(table.contains("mock"));
352 }
353
354 #[test]
355 fn test_benchmark_runner_creation() {
356 let runner = BenchmarkRunner::new(20);
357 assert_eq!(runner.iterations, 20);
358
359 let runner = runner.with_warmup(5);
360 assert_eq!(runner.warmup_iterations, 5);
361 }
362
363 #[test]
364 fn test_memory_profile_format() {
365 let profile = MemoryProfile {
366 provider: "test".to_string(),
367 peak_bytes: 100 * 1024 * 1024,
368 avg_bytes: 80 * 1024 * 1024,
369 bytes_per_op: 512 * 1024,
370 };
371
372 let formatted = profile.format();
373 assert!(formatted.contains("test"));
374 assert!(formatted.contains("100 MB"));
375 assert!(formatted.contains("80 MB"));
376 assert!(formatted.contains("512 KB"));
377 }
378}