flash_watcher/
bench_results.rs

1use std::collections::HashMap;
2use std::fmt;
3
4use colored::Colorize;
5
6/// Represents a benchmark result for a specific watcher
7#[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
31/// Stores benchmark results for multiple file watchers
32pub 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    /// Add pre-populated sample benchmark results for demonstration purposes
51    pub fn with_sample_data() -> Self {
52        let mut results = HashMap::new();
53
54        // Flash results (simulating best performance)
55        results.insert(
56            "flash".to_string(),
57            WatcherResult::new(25.6, 5400.0, 32.1, 0.12),
58        );
59
60        // nodemon results
61        results.insert(
62            "nodemon".to_string(),
63            WatcherResult::new(156.2, 42800.0, 122.8, 0.85),
64        );
65
66        // watchexec results
67        results.insert(
68            "watchexec".to_string(),
69            WatcherResult::new(52.4, 8700.0, 58.4, 0.31),
70        );
71
72        // cargo-watch results
73        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    /// Get the best performer for a specific metric
87    #[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    /// Calculate how much faster/better Flash is compared to the average
104    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    /// Print a comparison bar chart for a specific metric
142    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        // Get values for this metric
156        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        // Sort by value (best first)
171        entries.sort_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap());
172
173        // Find the maximum value for scaling
174        let max_value = entries.iter().map(|(_, v)| *v).fold(0.0, f64::max);
175        let scale_factor = 40.0 / max_value;
176
177        // Print bars
178        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    /// Print a summary report of all benchmark results
208    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        // Print Flash improvement stats
222        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        // Average of others: startup=25.0, memory=2500.0, detection=50.0, cpu=0.25
345        // Flash values: startup=10.0, memory=1000.0, detection=20.0, cpu=0.1
346        // Improvements: 25/10=2.5, 2500/1000=2.5, 50/20=2.5, 0.25/0.1=2.5
347
348        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}