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        // Issue #79 — server-injected chaos signals (latency / jitter / faults)
53        // observed from MockForge response headers. Surfaces the slice of
54        // total wire time that came from the chaos middleware vs the system
55        // under test.
56        if results.server_injected_latency_samples > 0
57            || results.server_injected_jitter_samples > 0
58            || results.server_reported_faults > 0
59        {
60            println!("\n{}", "Server-Injected (chaos):".bold());
61            if results.server_injected_latency_samples > 0 {
62                println!(
63                    "  Latency samples:      {} (avg {:.2}ms, max {:.2}ms)",
64                    results.server_injected_latency_samples.to_string().cyan(),
65                    results.server_injected_latency_avg_ms,
66                    results.server_injected_latency_max_ms,
67                );
68            }
69            if results.server_injected_jitter_samples > 0 {
70                println!(
71                    "  Jitter samples:       {} (avg {:.2}ms)",
72                    results.server_injected_jitter_samples.to_string().cyan(),
73                    results.server_injected_jitter_avg_ms,
74                );
75            }
76            if results.server_reported_faults > 0 {
77                println!(
78                    "  Fault-marked resps:   {}",
79                    results.server_reported_faults.to_string().cyan(),
80                );
81            }
82        }
83
84        println!("\n{}", "=".repeat(60).bright_green());
85    }
86
87    /// Print header information
88    pub fn print_header(
89        spec_file: &str,
90        target: &str,
91        num_operations: usize,
92        scenario: &str,
93        duration_secs: u64,
94    ) {
95        println!("\n{}\n", "MockForge Bench - Load Testing Mode".bright_green().bold());
96        println!("{}", "─".repeat(60).bright_black());
97
98        println!("{}: {}", "Specification".bold(), spec_file.cyan());
99        println!("{}: {}", "Target".bold(), target.cyan());
100        println!("{}: {} endpoints", "Operations".bold(), num_operations.to_string().cyan());
101        println!("{}: {}", "Scenario".bold(), scenario.cyan());
102        println!("{}: {}s", "Duration".bold(), duration_secs.to_string().cyan());
103
104        println!("{}\n", "─".repeat(60).bright_black());
105    }
106
107    /// Print progress message
108    pub fn print_progress(message: &str) {
109        println!("{} {}", "→".bright_green().bold(), message);
110    }
111
112    /// Print error message
113    pub fn print_error(message: &str) {
114        eprintln!("{} {}", "✗".bright_red().bold(), message.red());
115    }
116
117    /// Print success message
118    pub fn print_success(message: &str) {
119        println!("{} {}", "✓".bright_green().bold(), message.green());
120    }
121
122    /// Print warning message
123    pub fn print_warning(message: &str) {
124        println!("{} {}", "⚠".bright_yellow().bold(), message.yellow());
125    }
126
127    /// Print multi-target summary
128    pub fn print_multi_target_summary(results: &AggregatedResults) {
129        println!("\n{}", "=".repeat(60).bright_green());
130        println!("{}", "Multi-Target Load Test Complete! ✓".bright_green().bold());
131        println!("{}\n", "=".repeat(60).bright_green());
132
133        println!("{}", "Overall Summary:".bold());
134        println!("  Total Targets:        {}", results.total_targets.to_string().cyan());
135        println!(
136            "  Successful:           {} ({}%)",
137            results.successful_targets.to_string().green(),
138            format!(
139                "{:.1}",
140                (results.successful_targets as f64 / results.total_targets as f64) * 100.0
141            )
142            .green()
143        );
144        println!(
145            "  Failed:               {} ({}%)",
146            results.failed_targets.to_string().red(),
147            format!(
148                "{:.1}",
149                (results.failed_targets as f64 / results.total_targets as f64) * 100.0
150            )
151            .red()
152        );
153
154        println!("\n{}", "Aggregated Metrics:".bold());
155        println!(
156            "  Total Requests:       {}",
157            results.aggregated_metrics.total_requests.to_string().cyan()
158        );
159        println!(
160            "  Failed Requests:      {} ({}%)",
161            results.aggregated_metrics.total_failed_requests.to_string().red(),
162            format!("{:.2}", results.aggregated_metrics.error_rate).red()
163        );
164        println!(
165            "  Total RPS:            {} req/s",
166            format!("{:.1}", results.aggregated_metrics.total_rps).cyan()
167        );
168        println!(
169            "  Avg RPS/target:       {} req/s",
170            format!("{:.1}", results.aggregated_metrics.avg_rps).cyan()
171        );
172        println!(
173            "  Total VUs:            {}",
174            results.aggregated_metrics.total_vus_max.to_string().cyan()
175        );
176        println!(
177            "  Avg Response Time:    {}ms",
178            format!("{:.2}", results.aggregated_metrics.avg_duration_ms).cyan()
179        );
180        println!(
181            "  p95 Response Time:    {}ms",
182            format!("{:.2}", results.aggregated_metrics.p95_duration_ms).cyan()
183        );
184        println!(
185            "  p99 Response Time:    {}ms",
186            format!("{:.2}", results.aggregated_metrics.p99_duration_ms).cyan()
187        );
188
189        // Show per-target summary
190        let print_target = |result: &crate::parallel_executor::TargetResult| {
191            let status = if result.success {
192                "✓".bright_green()
193            } else {
194                "✗".bright_red()
195            };
196            println!("  {} {}", status, result.target_url.cyan());
197            if result.success {
198                println!(
199                    "      Requests: {}  RPS: {}  VUs: {}",
200                    result.results.total_requests.to_string().white(),
201                    format!("{:.1}", result.results.rps).white(),
202                    result.results.vus_max.to_string().white(),
203                );
204                println!(
205                    "      Latency: min={:.1}ms avg={:.1}ms med={:.1}ms p90={:.1}ms p95={:.1}ms p99={:.1}ms max={:.1}ms",
206                    result.results.min_duration_ms,
207                    result.results.avg_duration_ms,
208                    result.results.med_duration_ms,
209                    result.results.p90_duration_ms,
210                    result.results.p95_duration_ms,
211                    result.results.p99_duration_ms,
212                    result.results.max_duration_ms,
213                );
214            }
215            if let Some(error) = &result.error {
216                println!("      Error: {}", error.red());
217            }
218        };
219
220        if results.total_targets <= 20 {
221            println!("\n{}", "Per-Target Results:".bold());
222            for result in &results.target_results {
223                print_target(result);
224            }
225        } else {
226            // Show top 10 and bottom 10
227            println!("\n{}", "Top 10 Targets (by requests):".bold());
228            let mut sorted_results = results.target_results.clone();
229            sorted_results.sort_by_key(|r| r.results.total_requests);
230            sorted_results.reverse();
231
232            for result in sorted_results.iter().take(10) {
233                print_target(result);
234            }
235
236            println!("\n{}", "Bottom 10 Targets:".bold());
237            for result in sorted_results.iter().rev().take(10) {
238                print_target(result);
239            }
240        }
241
242        println!("\n{}", "=".repeat(60).bright_green());
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249
250    #[test]
251    fn test_terminal_reporter_creation() {
252        let _reporter = TerminalReporter;
253    }
254}