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) {
19 Self::print_summary_with_mode(results, duration_secs, false);
20 }
21
22 pub fn print_summary_with_mode(results: &K6Results, duration_secs: u64, cps_mode: bool) {
31 println!("\n{}", "=".repeat(60).bright_green());
32 println!("{}", "Load Test Complete! ✓".bright_green().bold());
33 println!("{}\n", "=".repeat(60).bright_green());
34
35 println!("{}", "Summary:".bold());
36 println!(" Total Requests: {}", results.total_requests.to_string().cyan());
37 println!(
38 " Successful: {} ({}%)",
39 (results.total_requests - results.failed_requests).to_string().green(),
40 format!("{:.2}", results.success_rate()).green()
41 );
42 println!(
43 " Failed: {} ({}%)",
44 results.failed_requests.to_string().red(),
45 format!("{:.2}", results.error_rate()).red()
46 );
47
48 println!("\n{}", "Response Times:".bold());
49 println!(" Min: {}ms", format!("{:.2}", results.min_duration_ms).cyan());
50 println!(" Avg: {}ms", format!("{:.2}", results.avg_duration_ms).cyan());
51 println!(" Med: {}ms", format!("{:.2}", results.med_duration_ms).cyan());
52 println!(" p90: {}ms", format!("{:.2}", results.p90_duration_ms).cyan());
53 println!(" p95: {}ms", format!("{:.2}", results.p95_duration_ms).cyan());
54 println!(" p99: {}ms", format!("{:.2}", results.p99_duration_ms).cyan());
55 println!(" Max: {}ms", format!("{:.2}", results.max_duration_ms).cyan());
56
57 println!("\n{}", "Throughput:".bold());
58 if results.rps > 0.0 {
59 println!(" RPS: {} req/s", format!("{:.1}", results.rps).cyan());
60 } else {
61 println!(
62 " RPS: {} req/s",
63 format!("{:.1}", results.total_requests as f64 / duration_secs as f64).cyan()
64 );
65 }
66 if results.vus_max > 0 {
67 println!(" Max VUs: {}", results.vus_max.to_string().cyan());
68 }
69
70 if cps_mode {
75 let cps = if results.rps > 0.0 {
76 results.rps
77 } else {
78 results.total_requests as f64 / duration_secs.max(1) as f64
79 };
80 println!(" CPS: {} conn/s (--cps)", format!("{:.1}", cps).cyan());
81 println!(" Total Connections: {}", results.total_requests.to_string().cyan());
82 }
83
84 if results.tcp_connect_samples > 0 {
93 if !cps_mode {
94 println!(
97 " Connections opened: {} ({} conn/s avg)",
98 results.tcp_connect_samples.to_string().cyan(),
99 format!(
100 "{:.1}",
101 results.tcp_connect_samples as f64 / duration_secs.max(1) as f64
102 )
103 .cyan(),
104 );
105 }
106 println!(
107 " TCP connect: avg {:.2}ms, max {:.2}ms",
108 results.tcp_connect_avg_ms, results.tcp_connect_max_ms,
109 );
110 }
111 if results.tls_handshake_samples > 0 {
112 println!(
113 " TLS handshake: avg {:.2}ms, max {:.2}ms",
114 results.tls_handshake_avg_ms, results.tls_handshake_max_ms,
115 );
116 }
117 if results.vus_max > 0 && (cps_mode || results.tcp_connect_samples > 0) {
124 println!(
125 " Peak concurrent VUs: {} (max open conns from client side)",
126 results.vus_max.to_string().cyan(),
127 );
128 }
129
130 if results.server_injected_latency_samples > 0
135 || results.server_injected_jitter_samples > 0
136 || results.server_reported_faults > 0
137 {
138 println!("\n{}", "Server-Injected (chaos):".bold());
139 if results.server_injected_latency_samples > 0 {
140 println!(
141 " Latency samples: {} (avg {:.2}ms, max {:.2}ms)",
142 results.server_injected_latency_samples.to_string().cyan(),
143 results.server_injected_latency_avg_ms,
144 results.server_injected_latency_max_ms,
145 );
146 }
147 if results.server_injected_jitter_samples > 0 {
148 println!(
149 " Jitter samples: {} (avg {:.2}ms)",
150 results.server_injected_jitter_samples.to_string().cyan(),
151 results.server_injected_jitter_avg_ms,
152 );
153 }
154 if results.server_reported_faults > 0 {
155 println!(
156 " Fault-marked resps: {}",
157 results.server_reported_faults.to_string().cyan(),
158 );
159 }
160 }
161
162 println!("\n{}", "=".repeat(60).bright_green());
163 }
164
165 pub fn print_header(
167 spec_file: &str,
168 target: &str,
169 num_operations: usize,
170 scenario: &str,
171 duration_secs: u64,
172 ) {
173 println!("\n{}\n", "MockForge Bench - Load Testing Mode".bright_green().bold());
174 println!("{}", "─".repeat(60).bright_black());
175
176 println!("{}: {}", "Specification".bold(), spec_file.cyan());
177 println!("{}: {}", "Target".bold(), target.cyan());
178 println!("{}: {} endpoints", "Operations".bold(), num_operations.to_string().cyan());
179 println!("{}: {}", "Scenario".bold(), scenario.cyan());
180 println!("{}: {}s", "Duration".bold(), duration_secs.to_string().cyan());
181
182 println!("{}\n", "─".repeat(60).bright_black());
183 }
184
185 pub fn print_progress(message: &str) {
187 println!("{} {}", "→".bright_green().bold(), message);
188 }
189
190 pub fn print_error(message: &str) {
192 eprintln!("{} {}", "✗".bright_red().bold(), message.red());
193 }
194
195 pub fn print_success(message: &str) {
197 println!("{} {}", "✓".bright_green().bold(), message.green());
198 }
199
200 pub fn print_warning(message: &str) {
202 println!("{} {}", "⚠".bright_yellow().bold(), message.yellow());
203 }
204
205 pub fn print_multi_target_summary(results: &AggregatedResults) {
207 println!("\n{}", "=".repeat(60).bright_green());
208 println!("{}", "Multi-Target Load Test Complete! ✓".bright_green().bold());
209 println!("{}\n", "=".repeat(60).bright_green());
210
211 println!("{}", "Overall Summary:".bold());
212 println!(" Total Targets: {}", results.total_targets.to_string().cyan());
213 println!(
214 " Successful: {} ({}%)",
215 results.successful_targets.to_string().green(),
216 format!(
217 "{:.1}",
218 (results.successful_targets as f64 / results.total_targets as f64) * 100.0
219 )
220 .green()
221 );
222 println!(
223 " Failed: {} ({}%)",
224 results.failed_targets.to_string().red(),
225 format!(
226 "{:.1}",
227 (results.failed_targets as f64 / results.total_targets as f64) * 100.0
228 )
229 .red()
230 );
231
232 println!("\n{}", "Aggregated Metrics:".bold());
233 println!(
234 " Total Requests: {}",
235 results.aggregated_metrics.total_requests.to_string().cyan()
236 );
237 println!(
238 " Failed Requests: {} ({}%)",
239 results.aggregated_metrics.total_failed_requests.to_string().red(),
240 format!("{:.2}", results.aggregated_metrics.error_rate).red()
241 );
242 println!(
243 " Total RPS: {} req/s",
244 format!("{:.1}", results.aggregated_metrics.total_rps).cyan()
245 );
246 println!(
247 " Avg RPS/target: {} req/s",
248 format!("{:.1}", results.aggregated_metrics.avg_rps).cyan()
249 );
250 println!(
251 " Total VUs: {}",
252 results.aggregated_metrics.total_vus_max.to_string().cyan()
253 );
254 println!(
255 " Avg Response Time: {}ms",
256 format!("{:.2}", results.aggregated_metrics.avg_duration_ms).cyan()
257 );
258 println!(
259 " p95 Response Time: {}ms",
260 format!("{:.2}", results.aggregated_metrics.p95_duration_ms).cyan()
261 );
262 println!(
263 " p99 Response Time: {}ms",
264 format!("{:.2}", results.aggregated_metrics.p99_duration_ms).cyan()
265 );
266
267 let print_target = |result: &crate::parallel_executor::TargetResult| {
269 let status = if result.success {
270 "✓".bright_green()
271 } else {
272 "✗".bright_red()
273 };
274 println!(" {} {}", status, result.target_url.cyan());
275 if result.success {
276 println!(
277 " Requests: {} RPS: {} VUs: {}",
278 result.results.total_requests.to_string().white(),
279 format!("{:.1}", result.results.rps).white(),
280 result.results.vus_max.to_string().white(),
281 );
282 println!(
283 " Latency: min={:.1}ms avg={:.1}ms med={:.1}ms p90={:.1}ms p95={:.1}ms p99={:.1}ms max={:.1}ms",
284 result.results.min_duration_ms,
285 result.results.avg_duration_ms,
286 result.results.med_duration_ms,
287 result.results.p90_duration_ms,
288 result.results.p95_duration_ms,
289 result.results.p99_duration_ms,
290 result.results.max_duration_ms,
291 );
292 }
293 if let Some(error) = &result.error {
294 println!(" Error: {}", error.red());
295 }
296 };
297
298 if results.total_targets <= 20 {
299 println!("\n{}", "Per-Target Results:".bold());
300 for result in &results.target_results {
301 print_target(result);
302 }
303 } else {
304 println!("\n{}", "Top 10 Targets (by requests):".bold());
306 let mut sorted_results = results.target_results.clone();
307 sorted_results.sort_by_key(|r| r.results.total_requests);
308 sorted_results.reverse();
309
310 for result in sorted_results.iter().take(10) {
311 print_target(result);
312 }
313
314 println!("\n{}", "Bottom 10 Targets:".bold());
315 for result in sorted_results.iter().rev().take(10) {
316 print_target(result);
317 }
318 }
319
320 println!("\n{}", "=".repeat(60).bright_green());
321 }
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327
328 #[test]
329 fn test_terminal_reporter_creation() {
330 let _reporter = TerminalReporter;
331 }
332}