Skip to main content

mockforge_bench/
reporter.rs

1//! Result reporting and formatting
2
3use crate::executor::K6Results;
4use crate::parallel_executor::AggregatedResults;
5use colored::*;
6
7/// Terminal reporter for bench results
8pub struct TerminalReporter;
9
10impl TerminalReporter {
11    /// Print a summary of the bench results
12    pub fn print_summary(results: &K6Results, duration_secs: u64) {
13        println!("\n{}", "=".repeat(60).bright_green());
14        println!("{}", "Load Test Complete! ✓".bright_green().bold());
15        println!("{}\n", "=".repeat(60).bright_green());
16
17        println!("{}", "Summary:".bold());
18        println!("  Total Requests:       {}", results.total_requests.to_string().cyan());
19        println!(
20            "  Successful:           {} ({}%)",
21            (results.total_requests - results.failed_requests).to_string().green(),
22            format!("{:.2}", results.success_rate()).green()
23        );
24        println!(
25            "  Failed:               {} ({}%)",
26            results.failed_requests.to_string().red(),
27            format!("{:.2}", results.error_rate()).red()
28        );
29
30        println!("\n{}", "Response Times:".bold());
31        println!("  Min:                  {}ms", format!("{:.2}", results.min_duration_ms).cyan());
32        println!("  Avg:                  {}ms", format!("{:.2}", results.avg_duration_ms).cyan());
33        println!("  Med:                  {}ms", format!("{:.2}", results.med_duration_ms).cyan());
34        println!("  p90:                  {}ms", format!("{:.2}", results.p90_duration_ms).cyan());
35        println!("  p95:                  {}ms", format!("{:.2}", results.p95_duration_ms).cyan());
36        println!("  p99:                  {}ms", format!("{:.2}", results.p99_duration_ms).cyan());
37        println!("  Max:                  {}ms", format!("{:.2}", results.max_duration_ms).cyan());
38
39        println!("\n{}", "Throughput:".bold());
40        if results.rps > 0.0 {
41            println!("  RPS:                  {} req/s", format!("{:.1}", results.rps).cyan());
42        } else {
43            println!(
44                "  RPS:                  {} req/s",
45                format!("{:.1}", results.total_requests as f64 / duration_secs as f64).cyan()
46            );
47        }
48        if results.vus_max > 0 {
49            println!("  Max VUs:              {}", results.vus_max.to_string().cyan());
50        }
51
52        println!("\n{}", "=".repeat(60).bright_green());
53    }
54
55    /// Print header information
56    pub fn print_header(
57        spec_file: &str,
58        target: &str,
59        num_operations: usize,
60        scenario: &str,
61        duration_secs: u64,
62    ) {
63        println!("\n{}\n", "MockForge Bench - Load Testing Mode".bright_green().bold());
64        println!("{}", "─".repeat(60).bright_black());
65
66        println!("{}: {}", "Specification".bold(), spec_file.cyan());
67        println!("{}: {}", "Target".bold(), target.cyan());
68        println!("{}: {} endpoints", "Operations".bold(), num_operations.to_string().cyan());
69        println!("{}: {}", "Scenario".bold(), scenario.cyan());
70        println!("{}: {}s", "Duration".bold(), duration_secs.to_string().cyan());
71
72        println!("{}\n", "─".repeat(60).bright_black());
73    }
74
75    /// Print progress message
76    pub fn print_progress(message: &str) {
77        println!("{} {}", "→".bright_green().bold(), message);
78    }
79
80    /// Print error message
81    pub fn print_error(message: &str) {
82        eprintln!("{} {}", "✗".bright_red().bold(), message.red());
83    }
84
85    /// Print success message
86    pub fn print_success(message: &str) {
87        println!("{} {}", "✓".bright_green().bold(), message.green());
88    }
89
90    /// Print warning message
91    pub fn print_warning(message: &str) {
92        println!("{} {}", "⚠".bright_yellow().bold(), message.yellow());
93    }
94
95    /// Print multi-target summary
96    pub fn print_multi_target_summary(results: &AggregatedResults) {
97        println!("\n{}", "=".repeat(60).bright_green());
98        println!("{}", "Multi-Target Load Test Complete! ✓".bright_green().bold());
99        println!("{}\n", "=".repeat(60).bright_green());
100
101        println!("{}", "Overall Summary:".bold());
102        println!("  Total Targets:        {}", results.total_targets.to_string().cyan());
103        println!(
104            "  Successful:           {} ({}%)",
105            results.successful_targets.to_string().green(),
106            format!(
107                "{:.1}",
108                (results.successful_targets as f64 / results.total_targets as f64) * 100.0
109            )
110            .green()
111        );
112        println!(
113            "  Failed:               {} ({}%)",
114            results.failed_targets.to_string().red(),
115            format!(
116                "{:.1}",
117                (results.failed_targets as f64 / results.total_targets as f64) * 100.0
118            )
119            .red()
120        );
121
122        println!("\n{}", "Aggregated Metrics:".bold());
123        println!(
124            "  Total Requests:       {}",
125            results.aggregated_metrics.total_requests.to_string().cyan()
126        );
127        println!(
128            "  Failed Requests:      {} ({}%)",
129            results.aggregated_metrics.total_failed_requests.to_string().red(),
130            format!("{:.2}", results.aggregated_metrics.error_rate).red()
131        );
132        println!(
133            "  Total RPS:            {} req/s",
134            format!("{:.1}", results.aggregated_metrics.total_rps).cyan()
135        );
136        println!(
137            "  Avg RPS/target:       {} req/s",
138            format!("{:.1}", results.aggregated_metrics.avg_rps).cyan()
139        );
140        println!(
141            "  Total VUs:            {}",
142            results.aggregated_metrics.total_vus_max.to_string().cyan()
143        );
144        println!(
145            "  Avg Response Time:    {}ms",
146            format!("{:.2}", results.aggregated_metrics.avg_duration_ms).cyan()
147        );
148        println!(
149            "  p95 Response Time:    {}ms",
150            format!("{:.2}", results.aggregated_metrics.p95_duration_ms).cyan()
151        );
152        println!(
153            "  p99 Response Time:    {}ms",
154            format!("{:.2}", results.aggregated_metrics.p99_duration_ms).cyan()
155        );
156
157        // Show per-target summary
158        let print_target = |result: &crate::parallel_executor::TargetResult| {
159            let status = if result.success {
160                "✓".bright_green()
161            } else {
162                "✗".bright_red()
163            };
164            println!("  {} {}", status, result.target_url.cyan());
165            if result.success {
166                println!(
167                    "      Requests: {}  RPS: {}  VUs: {}",
168                    result.results.total_requests.to_string().white(),
169                    format!("{:.1}", result.results.rps).white(),
170                    result.results.vus_max.to_string().white(),
171                );
172                println!(
173                    "      Latency: min={}ms avg={}ms med={}ms p90={}ms p95={}ms p99={}ms max={}ms",
174                    format!("{:.1}", result.results.min_duration_ms),
175                    format!("{:.1}", result.results.avg_duration_ms),
176                    format!("{:.1}", result.results.med_duration_ms),
177                    format!("{:.1}", result.results.p90_duration_ms),
178                    format!("{:.1}", result.results.p95_duration_ms),
179                    format!("{:.1}", result.results.p99_duration_ms),
180                    format!("{:.1}", result.results.max_duration_ms),
181                );
182            }
183            if let Some(error) = &result.error {
184                println!("      Error: {}", error.red());
185            }
186        };
187
188        if results.total_targets <= 20 {
189            println!("\n{}", "Per-Target Results:".bold());
190            for result in &results.target_results {
191                print_target(result);
192            }
193        } else {
194            // Show top 10 and bottom 10
195            println!("\n{}", "Top 10 Targets (by requests):".bold());
196            let mut sorted_results = results.target_results.clone();
197            sorted_results.sort_by_key(|r| r.results.total_requests);
198            sorted_results.reverse();
199
200            for result in sorted_results.iter().take(10) {
201                print_target(result);
202            }
203
204            println!("\n{}", "Bottom 10 Targets:".bold());
205            for result in sorted_results.iter().rev().take(10) {
206                print_target(result);
207            }
208        }
209
210        println!("\n{}", "=".repeat(60).bright_green());
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217
218    #[test]
219    fn test_terminal_reporter_creation() {
220        let _reporter = TerminalReporter;
221    }
222}