package reporter import ( "encoding/json" "fmt" "html/template" "os" "path/filepath" "strings" "time" "git.gostacks.org/iwasforcedtobehere/stroke/pkg/metrics" ) // Reporter interface for different output formats type Reporter interface { Generate(results *metrics.Results, outputDir string) error } // ConsoleReporter outputs results to the console type ConsoleReporter struct{} // NewConsoleReporter creates a new console reporter func NewConsoleReporter() *ConsoleReporter { return &ConsoleReporter{} } // Generate outputs results to console func (cr *ConsoleReporter) Generate(results *metrics.Results, outputDir string) error { fmt.Printf("\nšŸ“Š Stroke Test Results\n") fmt.Print(strings.Repeat("=", 50) + "\n") fmt.Printf("Duration: %v\n", results.TestDuration) fmt.Printf("Total Requests: %d\n", results.TotalRequests) fmt.Printf("Successful: %d\n", results.SuccessRequests) fmt.Printf("Failed: %d\n", results.FailedRequests) fmt.Printf("RPS: %.2f\n", results.RequestsPerSecond) fmt.Printf("\nResponse Times:\n") fmt.Printf(" Min: %v\n", results.MinResponseTime) fmt.Printf(" Max: %v\n", results.MaxResponseTime) fmt.Printf(" Avg: %v\n", results.AvgResponseTime) fmt.Printf(" p50: %v\n", results.P50) fmt.Printf(" p90: %v\n", results.P90) fmt.Printf(" p95: %v\n", results.P95) fmt.Printf(" p99: %v\n", results.P99) if len(results.StatusCodes) > 0 { fmt.Printf("\nStatus Codes:\n") for code, count := range results.StatusCodes { fmt.Printf(" %d: %d\n", code, count) } } return nil } // JSONReporter outputs results as JSON type JSONReporter struct{} // NewJSONReporter creates a new JSON reporter func NewJSONReporter() *JSONReporter { return &JSONReporter{} } // Generate outputs results as JSON file func (jr *JSONReporter) Generate(results *metrics.Results, outputDir string) error { if err := os.MkdirAll(outputDir, 0755); err != nil { return fmt.Errorf("failed to create output directory: %w", err) } timestamp := time.Now().Format("20060102_150405") filename := filepath.Join(outputDir, fmt.Sprintf("stroke_results_%s.json", timestamp)) file, err := os.Create(filename) if err != nil { return fmt.Errorf("failed to create JSON file: %w", err) } defer file.Close() encoder := json.NewEncoder(file) encoder.SetIndent("", " ") if err := encoder.Encode(results); err != nil { return fmt.Errorf("failed to encode JSON: %w", err) } fmt.Printf("šŸ“„ JSON report saved to: %s\n", filename) return nil } // HTMLReporter generates beautiful HTML reports type HTMLReporter struct{} // NewHTMLReporter creates a new HTML reporter func NewHTMLReporter() *HTMLReporter { return &HTMLReporter{} } // Generate creates an HTML report func (hr *HTMLReporter) Generate(results *metrics.Results, outputDir string) error { if err := os.MkdirAll(outputDir, 0755); err != nil { return fmt.Errorf("failed to create output directory: %w", err) } timestamp := time.Now().Format("20060102_150405") filename := filepath.Join(outputDir, fmt.Sprintf("stroke_report_%s.html", timestamp)) file, err := os.Create(filename) if err != nil { return fmt.Errorf("failed to create HTML file: %w", err) } defer file.Close() tmpl := template.Must(template.New("report").Parse(htmlTemplate)) data := struct { Results *metrics.Results Timestamp string Title string }{ Results: results, Timestamp: time.Now().Format("2006-01-02 15:04:05"), Title: "Stroke Stress Test Report", } if err := tmpl.Execute(file, data); err != nil { return fmt.Errorf("failed to execute template: %w", err) } fmt.Printf("šŸ“Š HTML report saved to: %s\n", filename) return nil } // CSVReporter outputs results as CSV type CSVReporter struct{} // NewCSVReporter creates a new CSV reporter func NewCSVReporter() *CSVReporter { return &CSVReporter{} } // Generate outputs results as CSV file func (csvr *CSVReporter) Generate(results *metrics.Results, outputDir string) error { if err := os.MkdirAll(outputDir, 0755); err != nil { return fmt.Errorf("failed to create output directory: %w", err) } timestamp := time.Now().Format("20060102_150405") filename := filepath.Join(outputDir, fmt.Sprintf("stroke_results_%s.csv", timestamp)) file, err := os.Create(filename) if err != nil { return fmt.Errorf("failed to create CSV file: %w", err) } defer file.Close() // Write CSV header fmt.Fprintf(file, "Metric,Value\n") fmt.Fprintf(file, "Total Requests,%d\n", results.TotalRequests) fmt.Fprintf(file, "Success Requests,%d\n", results.SuccessRequests) fmt.Fprintf(file, "Failed Requests,%d\n", results.FailedRequests) fmt.Fprintf(file, "Test Duration,%v\n", results.TestDuration) fmt.Fprintf(file, "Requests Per Second,%.2f\n", results.RequestsPerSecond) fmt.Fprintf(file, "Min Response Time,%v\n", results.MinResponseTime) fmt.Fprintf(file, "Max Response Time,%v\n", results.MaxResponseTime) fmt.Fprintf(file, "Avg Response Time,%v\n", results.AvgResponseTime) fmt.Fprintf(file, "P50 Response Time,%v\n", results.P50) fmt.Fprintf(file, "P90 Response Time,%v\n", results.P90) fmt.Fprintf(file, "P95 Response Time,%v\n", results.P95) fmt.Fprintf(file, "P99 Response Time,%v\n", results.P99) fmt.Printf("šŸ“ˆ CSV report saved to: %s\n", filename) return nil } // MultiReporter combines multiple reporters type MultiReporter struct { reporters []Reporter } // NewMultiReporter creates a reporter that outputs to multiple formats func NewMultiReporter(formats []string) *MultiReporter { var reporters []Reporter for _, format := range formats { switch strings.ToLower(format) { case "console": reporters = append(reporters, NewConsoleReporter()) case "json": reporters = append(reporters, NewJSONReporter()) case "html": reporters = append(reporters, NewHTMLReporter()) case "csv": reporters = append(reporters, NewCSVReporter()) } } return &MultiReporter{reporters: reporters} } // Generate runs all configured reporters func (mr *MultiReporter) Generate(results *metrics.Results, outputDir string) error { for _, reporter := range mr.reporters { if err := reporter.Generate(results, outputDir); err != nil { return fmt.Errorf("reporter failed: %w", err) } } return nil } // HTML template for the report const htmlTemplate = ` {{.Title}}

šŸš€ {{.Title}} šŸš€

Generated on {{.Timestamp}}

Total Requests
{{.Results.TotalRequests}}
Successful Requests
{{.Results.SuccessRequests}}
Failed Requests
{{.Results.FailedRequests}}
Test Duration
{{.Results.TestDuration}}
Requests Per Second
{{printf "%.2f" .Results.RequestsPerSecond}}
Average Response Time
{{.Results.AvgResponseTime}}
P50 Response Time
{{.Results.P50}}
P90 Response Time
{{.Results.P90}}
P95 Response Time
{{.Results.P95}}
P99 Response Time
{{.Results.P99}}
Min Response Time
{{.Results.MinResponseTime}}
Max Response Time
{{.Results.MaxResponseTime}}
{{if .Results.StatusCodes}}

šŸ“ˆ Status Code Distribution

{{range $code, $count := .Results.StatusCodes}}
HTTP {{$code}} {{$count}} requests
{{end}}
{{end}}
`