1use std::collections::HashMap;
2use std::fmt;
3
4use colored::Colorize;
5
6#[derive(Debug, Clone)]
8pub struct WatcherResult {
9 pub startup_time_ms: f64,
10 pub memory_usage_kb: f64,
11 pub change_detection_ms: f64,
12 pub idle_cpu_percent: f64,
13}
14
15impl WatcherResult {
16 pub fn new(
17 startup_time_ms: f64,
18 memory_usage_kb: f64,
19 change_detection_ms: f64,
20 idle_cpu_percent: f64,
21 ) -> Self {
22 Self {
23 startup_time_ms,
24 memory_usage_kb,
25 change_detection_ms,
26 idle_cpu_percent,
27 }
28 }
29}
30
31pub struct BenchResults {
33 results: HashMap<String, WatcherResult>,
34}
35
36impl Default for BenchResults {
37 fn default() -> Self {
38 Self::new()
39 }
40}
41
42impl BenchResults {
43 #[allow(dead_code)]
44 pub fn new() -> Self {
45 Self {
46 results: HashMap::new(),
47 }
48 }
49
50 pub fn with_sample_data() -> Self {
52 let mut results = HashMap::new();
53
54 results.insert(
56 "flash".to_string(),
57 WatcherResult::new(25.6, 5400.0, 32.1, 0.12),
58 );
59
60 results.insert(
62 "nodemon".to_string(),
63 WatcherResult::new(156.2, 42800.0, 122.8, 0.85),
64 );
65
66 results.insert(
68 "watchexec".to_string(),
69 WatcherResult::new(52.4, 8700.0, 58.4, 0.31),
70 );
71
72 results.insert(
74 "cargo-watch".to_string(),
75 WatcherResult::new(175.5, 21400.0, 85.2, 0.42),
76 );
77
78 Self { results }
79 }
80
81 #[allow(dead_code)]
82 pub fn add_result(&mut self, name: &str, result: WatcherResult) {
83 self.results.insert(name.to_string(), result);
84 }
85
86 #[allow(dead_code)]
88 pub fn best_performer(&self, metric: BenchMetric) -> Option<(&String, f64)> {
89 self.results
90 .iter()
91 .map(|(name, result)| {
92 let value = match metric {
93 BenchMetric::StartupTime => result.startup_time_ms,
94 BenchMetric::MemoryUsage => result.memory_usage_kb,
95 BenchMetric::ChangeDetection => result.change_detection_ms,
96 BenchMetric::CpuUsage => result.idle_cpu_percent,
97 };
98 (name, value)
99 })
100 .min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
101 }
102
103 pub fn flash_improvement(&self) -> HashMap<BenchMetric, f64> {
105 let mut improvements = HashMap::new();
106 let flash = match self.results.get("flash") {
107 Some(r) => r,
108 None => return improvements,
109 };
110
111 let metrics = vec![
112 (BenchMetric::StartupTime, flash.startup_time_ms),
113 (BenchMetric::MemoryUsage, flash.memory_usage_kb),
114 (BenchMetric::ChangeDetection, flash.change_detection_ms),
115 (BenchMetric::CpuUsage, flash.idle_cpu_percent),
116 ];
117
118 for (metric, flash_value) in metrics {
119 let others: Vec<_> = self
120 .results
121 .iter()
122 .filter(|(name, _)| *name != "flash")
123 .map(|(_, result)| match metric {
124 BenchMetric::StartupTime => result.startup_time_ms,
125 BenchMetric::MemoryUsage => result.memory_usage_kb,
126 BenchMetric::ChangeDetection => result.change_detection_ms,
127 BenchMetric::CpuUsage => result.idle_cpu_percent,
128 })
129 .collect();
130
131 if !others.is_empty() {
132 let avg: f64 = others.iter().sum::<f64>() / others.len() as f64;
133 let improvement = avg / flash_value;
134 improvements.insert(metric, improvement);
135 }
136 }
137
138 improvements
139 }
140
141 pub fn print_chart(&self, metric: BenchMetric) {
143 let title = match metric {
144 BenchMetric::StartupTime => "Startup Time (ms) - lower is better",
145 BenchMetric::MemoryUsage => "Memory Usage (KB) - lower is better",
146 BenchMetric::ChangeDetection => "Change Detection (ms) - lower is better",
147 BenchMetric::CpuUsage => "CPU Usage (%) - lower is better",
148 };
149
150 println!("\n{}", title.bright_green().bold());
151 println!("{}", "─".repeat(60).bright_blue());
152
153 let max_name_len = self.results.keys().map(|k| k.len()).max().unwrap_or(10);
154
155 let mut entries: Vec<_> = self
157 .results
158 .iter()
159 .map(|(name, result)| {
160 let value = match metric {
161 BenchMetric::StartupTime => result.startup_time_ms,
162 BenchMetric::MemoryUsage => result.memory_usage_kb,
163 BenchMetric::ChangeDetection => result.change_detection_ms,
164 BenchMetric::CpuUsage => result.idle_cpu_percent,
165 };
166 (name, value)
167 })
168 .collect();
169
170 entries.sort_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap());
172
173 let max_value = entries.iter().map(|(_, v)| *v).fold(0.0, f64::max);
175 let scale_factor = 40.0 / max_value;
176
177 for (name, value) in entries {
179 let bar_length = (value * scale_factor).round() as usize;
180 let bar = "█".repeat(bar_length);
181
182 let formatted_name = format!("{:width$}", name, width = max_name_len);
183 let formatted_value = match metric {
184 BenchMetric::StartupTime => format!("{:.1} ms", value),
185 BenchMetric::MemoryUsage => format!("{:.0} KB", value),
186 BenchMetric::ChangeDetection => format!("{:.1} ms", value),
187 BenchMetric::CpuUsage => format!("{:.2} %", value),
188 };
189
190 let color = if name == "flash" {
191 bar.bright_green()
192 } else {
193 bar.bright_blue()
194 };
195
196 println!(
197 "{} {} {}",
198 formatted_name.bright_yellow(),
199 color,
200 formatted_value.bright_white()
201 );
202 }
203
204 println!("{}", "─".repeat(60).bright_blue());
205 }
206
207 pub fn print_report(&self) {
209 println!("\n{}", "📊 Flash Benchmark Results".bright_green().bold());
210 println!("{}", "══════════════════════════════════════".bright_blue());
211
212 for metric in [
213 BenchMetric::StartupTime,
214 BenchMetric::MemoryUsage,
215 BenchMetric::ChangeDetection,
216 BenchMetric::CpuUsage,
217 ] {
218 self.print_chart(metric);
219 }
220
221 println!(
223 "\n{}",
224 "Flash Performance Improvement".bright_green().bold()
225 );
226 println!("{}", "──────────────────────────────".bright_blue());
227
228 let improvements = self.flash_improvement();
229 for (metric, factor) in improvements {
230 let metric_name = match metric {
231 BenchMetric::StartupTime => "Startup Speed",
232 BenchMetric::MemoryUsage => "Memory Efficiency",
233 BenchMetric::ChangeDetection => "Detection Speed",
234 BenchMetric::CpuUsage => "CPU Efficiency",
235 };
236
237 println!(
238 "{}: {} {}x faster than average",
239 metric_name.bright_yellow(),
240 format!("{:.1}", factor).bright_green(),
241 if factor >= 2.0 { "🔥" } else { "" }
242 );
243 }
244
245 println!("{}", "══════════════════════════════════════".bright_blue());
246 }
247}
248
249#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
250pub enum BenchMetric {
251 StartupTime,
252 MemoryUsage,
253 ChangeDetection,
254 CpuUsage,
255}
256
257impl fmt::Display for BenchMetric {
258 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259 match self {
260 BenchMetric::StartupTime => write!(f, "Startup Time"),
261 BenchMetric::MemoryUsage => write!(f, "Memory Usage"),
262 BenchMetric::ChangeDetection => write!(f, "Change Detection"),
263 BenchMetric::CpuUsage => write!(f, "CPU Usage"),
264 }
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271
272 #[test]
273 fn test_watcher_result_new() {
274 let result = WatcherResult::new(25.5, 1024.0, 50.0, 0.5);
275 assert_eq!(result.startup_time_ms, 25.5);
276 assert_eq!(result.memory_usage_kb, 1024.0);
277 assert_eq!(result.change_detection_ms, 50.0);
278 assert_eq!(result.idle_cpu_percent, 0.5);
279 }
280
281 #[test]
282 fn test_bench_results_new() {
283 let results = BenchResults::new();
284 assert!(results.results.is_empty());
285 }
286
287 #[test]
288 fn test_bench_results_with_sample_data() {
289 let results = BenchResults::with_sample_data();
290 assert!(results.results.contains_key("flash"));
291 assert!(results.results.contains_key("nodemon"));
292 assert!(results.results.contains_key("watchexec"));
293 assert!(results.results.contains_key("cargo-watch"));
294 assert_eq!(results.results.len(), 4);
295 }
296
297 #[test]
298 fn test_add_result() {
299 let mut results = BenchResults::new();
300 let watcher_result = WatcherResult::new(30.0, 2048.0, 60.0, 1.0);
301
302 results.add_result("test-watcher", watcher_result.clone());
303 assert!(results.results.contains_key("test-watcher"));
304
305 let stored = results.results.get("test-watcher").unwrap();
306 assert_eq!(stored.startup_time_ms, 30.0);
307 assert_eq!(stored.memory_usage_kb, 2048.0);
308 }
309
310 #[test]
311 fn test_best_performer() {
312 let mut results = BenchResults::new();
313 results.add_result("fast", WatcherResult::new(10.0, 1000.0, 20.0, 0.1));
314 results.add_result("slow", WatcherResult::new(50.0, 5000.0, 100.0, 0.5));
315
316 let best_startup = results.best_performer(BenchMetric::StartupTime);
317 assert!(best_startup.is_some());
318 let (name, value) = best_startup.unwrap();
319 assert_eq!(name, "fast");
320 assert_eq!(value, 10.0);
321
322 let best_memory = results.best_performer(BenchMetric::MemoryUsage);
323 assert!(best_memory.is_some());
324 let (name, value) = best_memory.unwrap();
325 assert_eq!(name, "fast");
326 assert_eq!(value, 1000.0);
327 }
328
329 #[test]
330 fn test_best_performer_empty() {
331 let results = BenchResults::new();
332 assert!(results.best_performer(BenchMetric::StartupTime).is_none());
333 }
334
335 #[test]
336 fn test_flash_improvement() {
337 let mut results = BenchResults::new();
338 results.add_result("flash", WatcherResult::new(10.0, 1000.0, 20.0, 0.1));
339 results.add_result("other1", WatcherResult::new(20.0, 2000.0, 40.0, 0.2));
340 results.add_result("other2", WatcherResult::new(30.0, 3000.0, 60.0, 0.3));
341
342 let improvements = results.flash_improvement();
343
344 assert!(improvements.contains_key(&BenchMetric::StartupTime));
349 assert_eq!(improvements[&BenchMetric::StartupTime], 2.5);
350 assert_eq!(improvements[&BenchMetric::MemoryUsage], 2.5);
351 assert_eq!(improvements[&BenchMetric::ChangeDetection], 2.5);
352 assert_eq!(improvements[&BenchMetric::CpuUsage], 2.5);
353 }
354
355 #[test]
356 fn test_flash_improvement_missing_flash() {
357 let mut results = BenchResults::new();
358 results.add_result("other", WatcherResult::new(20.0, 2000.0, 40.0, 0.2));
359
360 let improvements = results.flash_improvement();
361 assert!(improvements.is_empty());
362 }
363
364 #[test]
365 fn test_flash_improvement_only_flash() {
366 let mut results = BenchResults::new();
367 results.add_result("flash", WatcherResult::new(10.0, 1000.0, 20.0, 0.1));
368
369 let improvements = results.flash_improvement();
370 assert!(improvements.is_empty());
371 }
372
373 #[test]
374 fn test_bench_metric_display() {
375 assert_eq!(format!("{}", BenchMetric::StartupTime), "Startup Time");
376 assert_eq!(format!("{}", BenchMetric::MemoryUsage), "Memory Usage");
377 assert_eq!(
378 format!("{}", BenchMetric::ChangeDetection),
379 "Change Detection"
380 );
381 assert_eq!(format!("{}", BenchMetric::CpuUsage), "CPU Usage");
382 }
383}