#!/usr/bin/env python3 """ Load Test Report Generator This script generates HTML reports from Locust JSON output files. It provides a comprehensive summary of load test results with metrics and endpoint breakdowns. Usage: python generate_load_report.py --input results.json --output report.html Reference: Phase 209 Plan 05 Task 3 """ import argparse import json import sys from datetime import datetime from pathlib import Path def load_results(filepath: str) -> dict: """ Load Locust JSON results from file. Args: filepath: Path to Locust JSON output file Returns: Parsed JSON data as dictionary Raises: FileNotFoundError: If file doesn't exist json.JSONDecodeError: If file is not valid JSON """ path = Path(filepath) if not path.exists(): raise FileNotFoundError(f"Results file not found: {filepath}") with open(path, 'r') as f: try: data = json.load(f) except json.JSONDecodeError as e: raise json.JSONDecodeError(f"Invalid JSON in {filepath}: {e}", e.doc, e.pos) return data def format_response_time(ms: float) -> str: """Format response time in milliseconds.""" return f"{ms:.0f}ms" def format_percentage(value: float) -> str: """Format percentage with 2 decimal places.""" return f"{value:.2f}%" def generate_color_class(failure_rate: float) -> str: """Generate CSS class based on failure rate.""" if failure_rate == 0: return "success" elif failure_rate < 1: return "warning" else: return "error" def generate_html_report(data: dict) -> str: """ Generate HTML report from Locust JSON data. Args: data: Parsed Locust JSON output Returns: HTML string """ # Extract test metadata start_time = datetime.fromtimestamp(data.get('start_time', 0)).strftime('%Y-%m-%d %H:%M:%S') duration_seconds = data.get('state', {}).get('run_time', 0) duration = f"{duration_seconds // 60}m {duration_seconds % 60}s" # Calculate summary metrics stats = data.get('stats', []) total_requests = sum(s.get('num_requests', 0) for s in stats) total_failures = sum(s.get('num_failures', 0) for s in stats) failure_rate = (total_failures / total_requests * 100) if total_requests > 0 else 0 # Calculate response times avg_response_time = sum( s.get('avg_response_time', 0) * s.get('num_requests', 0) for s in stats ) / total_requests if total_requests > 0 else 0 # Calculate RPS (requests per second) rps = total_requests / (duration_seconds / 1000) if duration_seconds > 0 else 0 # Generate CSS css = """ """ # Generate HTML html = f""" Load Test Report {css}

Load Test Report

Total Requests

{total_requests:,}

Requests/Second

{rps:.1f}

Failure Rate

{format_percentage(failure_rate)}

Avg Response Time

{format_response_time(avg_response_time)}

Endpoint Breakdown

""" # Add table rows for each endpoint for stat in sorted(stats, key=lambda s: s.get('num_requests', 0), reverse=True): name = stat.get('name', 'N/A') num_requests = stat.get('num_requests', 0) num_failures = stat.get('num_failures', 0) failure_rate = (num_failures / num_requests * 100) if num_requests > 0 else 0 avg_response = stat.get('avg_response_time', 0) response_times = stat.get('response_times', {}) p50 = response_times.get('0.5', 0) p95 = response_times.get('0.95', 0) p99 = response_times.get('0.99', 0) color_class = generate_color_class(failure_rate) html += f""" """ html += """
Endpoint Requests Failures Avg P50 P95 P99
{name} {num_requests:,} {num_failures} ({format_percentage(failure_rate)}) {format_response_time(avg_response)} {format_response_time(p50)} {format_response_time(p95)} {format_response_time(p99)}
""" return html def main(): """Main entry point for report generation.""" parser = argparse.ArgumentParser( description='Generate HTML report from Locust JSON output' ) parser.add_argument( '--input', '-i', required=True, help='Path to Locust JSON output file' ) parser.add_argument( '--output', '-o', required=True, help='Path to output HTML report file' ) args = parser.parse_args() try: # Load results print(f"Loading results from {args.input}...") data = load_results(args.input) # Generate HTML print("Generating HTML report...") html = generate_html_report(data) # Save report output_path = Path(args.output) output_path.parent.mkdir(parents=True, exist_ok=True) with open(output_path, 'w') as f: f.write(html) print(f"✅ Report generated: {output_path}") return 0 except FileNotFoundError as e: print(f"❌ Error: {e}", file=sys.stderr) return 1 except json.JSONDecodeError as e: print(f"❌ Error: Invalid JSON - {e}", file=sys.stderr) return 1 except Exception as e: print(f"❌ Error: {e}", file=sys.stderr) return 1 if __name__ == '__main__': sys.exit(main())