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!("  Avg:                  {}ms", format!("{:.2}", results.avg_duration_ms).cyan());
32        println!("  p95:                  {}ms", format!("{:.2}", results.p95_duration_ms).cyan());
33        println!("  p99:                  {}ms", format!("{:.2}", results.p99_duration_ms).cyan());
34
35        println!(
36            "\n  Throughput:           {} req/s",
37            format!("{:.1}", results.total_requests as f64 / duration_secs as f64).cyan()
38        );
39
40        println!("\n{}", "=".repeat(60).bright_green());
41    }
42
43    /// Print header information
44    pub fn print_header(
45        spec_file: &str,
46        target: &str,
47        num_operations: usize,
48        scenario: &str,
49        duration_secs: u64,
50    ) {
51        println!("\n{}\n", "MockForge Bench - Load Testing Mode".bright_green().bold());
52        println!("{}", "─".repeat(60).bright_black());
53
54        println!("{}: {}", "Specification".bold(), spec_file.cyan());
55        println!("{}: {}", "Target".bold(), target.cyan());
56        println!("{}: {} endpoints", "Operations".bold(), num_operations.to_string().cyan());
57        println!("{}: {}", "Scenario".bold(), scenario.cyan());
58        println!("{}: {}s", "Duration".bold(), duration_secs.to_string().cyan());
59
60        println!("{}\n", "─".repeat(60).bright_black());
61    }
62
63    /// Print progress message
64    pub fn print_progress(message: &str) {
65        println!("{} {}", "→".bright_green().bold(), message);
66    }
67
68    /// Print error message
69    pub fn print_error(message: &str) {
70        eprintln!("{} {}", "✗".bright_red().bold(), message.red());
71    }
72
73    /// Print success message
74    pub fn print_success(message: &str) {
75        println!("{} {}", "✓".bright_green().bold(), message.green());
76    }
77
78    /// Print warning message
79    pub fn print_warning(message: &str) {
80        println!("{} {}", "⚠".bright_yellow().bold(), message.yellow());
81    }
82
83    /// Print multi-target summary
84    pub fn print_multi_target_summary(results: &AggregatedResults) {
85        println!("\n{}", "=".repeat(60).bright_green());
86        println!("{}", "Multi-Target Load Test Complete! ✓".bright_green().bold());
87        println!("{}\n", "=".repeat(60).bright_green());
88
89        println!("{}", "Overall Summary:".bold());
90        println!("  Total Targets:         {}", results.total_targets.to_string().cyan());
91        println!(
92            "  Successful:           {} ({}%)",
93            results.successful_targets.to_string().green(),
94            format!(
95                "{:.1}",
96                (results.successful_targets as f64 / results.total_targets as f64) * 100.0
97            )
98            .green()
99        );
100        println!(
101            "  Failed:               {} ({}%)",
102            results.failed_targets.to_string().red(),
103            format!(
104                "{:.1}",
105                (results.failed_targets as f64 / results.total_targets as f64) * 100.0
106            )
107            .red()
108        );
109
110        println!("\n{}", "Aggregated Metrics:".bold());
111        println!(
112            "  Total Requests:       {}",
113            results.aggregated_metrics.total_requests.to_string().cyan()
114        );
115        println!(
116            "  Failed Requests:     {} ({}%)",
117            results.aggregated_metrics.total_failed_requests.to_string().red(),
118            format!("{:.2}", results.aggregated_metrics.error_rate).red()
119        );
120        println!(
121            "  Avg Response Time:    {}ms",
122            format!("{:.2}", results.aggregated_metrics.avg_duration_ms).cyan()
123        );
124        println!(
125            "  p95 Response Time:    {}ms",
126            format!("{:.2}", results.aggregated_metrics.p95_duration_ms).cyan()
127        );
128        println!(
129            "  p99 Response Time:    {}ms",
130            format!("{:.2}", results.aggregated_metrics.p99_duration_ms).cyan()
131        );
132
133        // Show per-target summary (top 10 and bottom 10 if many targets)
134        if results.total_targets <= 20 {
135            println!("\n{}", "Per-Target Results:".bold());
136            for result in &results.target_results {
137                let status = if result.success {
138                    "✓".bright_green()
139                } else {
140                    "✗".bright_red()
141                };
142                println!(
143                    "  {} {} - {} requests, {}ms avg",
144                    status,
145                    result.target_url.cyan(),
146                    result.results.total_requests,
147                    format!("{:.2}", result.results.avg_duration_ms)
148                );
149                if let Some(error) = &result.error {
150                    println!("    Error: {}", error.red());
151                }
152            }
153        } else {
154            // Show top 10 and bottom 10
155            println!("\n{}", "Top 10 Targets (by requests):".bold());
156            let mut sorted_results = results.target_results.clone();
157            sorted_results.sort_by_key(|r| r.results.total_requests);
158            sorted_results.reverse();
159
160            for result in sorted_results.iter().take(10) {
161                let status = if result.success {
162                    "✓".bright_green()
163                } else {
164                    "✗".bright_red()
165                };
166                println!(
167                    "  {} {} - {} requests, {}ms avg",
168                    status,
169                    result.target_url.cyan(),
170                    result.results.total_requests,
171                    format!("{:.2}", result.results.avg_duration_ms)
172                );
173            }
174
175            println!("\n{}", "Bottom 10 Targets:".bold());
176            for result in sorted_results.iter().rev().take(10) {
177                let status = if result.success {
178                    "✓".bright_green()
179                } else {
180                    "✗".bright_red()
181                };
182                println!(
183                    "  {} {} - {} requests, {}ms avg",
184                    status,
185                    result.target_url.cyan(),
186                    result.results.total_requests,
187                    format!("{:.2}", result.results.avg_duration_ms)
188                );
189            }
190        }
191
192        println!("\n{}", "=".repeat(60).bright_green());
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn test_terminal_reporter_creation() {
202        let _reporter = TerminalReporter;
203    }
204}