1use crate::executor::K6Results;
4use crate::parallel_executor::AggregatedResults;
5use colored::*;
6
7pub struct TerminalReporter;
9
10impl TerminalReporter {
11 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 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 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 pub fn print_progress(message: &str) {
109 println!("{} {}", "→".bright_green().bold(), message);
110 }
111
112 pub fn print_error(message: &str) {
114 eprintln!("{} {}", "✗".bright_red().bold(), message.red());
115 }
116
117 pub fn print_success(message: &str) {
119 println!("{} {}", "✓".bright_green().bold(), message.green());
120 }
121
122 pub fn print_warning(message: &str) {
124 println!("{} {}", "⚠".bright_yellow().bold(), message.yellow());
125 }
126
127 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 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 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}