// Package tools provides performance regression detection utilities. package tools import ( "encoding/json" "fmt" "os" "time" ) // PerformanceRegressionDetector detects performance regressions in benchmark results type PerformanceRegressionDetector struct { BaselineFile string Threshold float64 } // BenchmarkResult represents a single benchmark result type BenchmarkResult struct { Name string `json:"name"` Value float64 `json:"value"` Unit string `json:"unit"` Timestamp time.Time `json:"timestamp"` } // RegressionReport contains regression analysis results type RegressionReport struct { Regressions []Regression `json:"regressions"` Improvements []Improvement `json:"improvements"` Summary string `json:"summary"` } // Regression represents a performance regression type Regression struct { Benchmark string `json:"benchmark"` CurrentValue float64 `json:"current_value"` BaselineValue float64 `json:"baseline_value"` PercentChange float64 `json:"percent_change"` Severity string `json:"severity"` } // Improvement represents a performance improvement type Improvement struct { Benchmark string `json:"benchmark"` CurrentValue float64 `json:"current_value"` BaselineValue float64 `json:"baseline_value"` PercentChange float64 `json:"percent_change"` } // NewPerformanceRegressionDetector creates a new detector instance func NewPerformanceRegressionDetector(baselineFile string, threshold float64) *PerformanceRegressionDetector { return &PerformanceRegressionDetector{ BaselineFile: baselineFile, Threshold: threshold, } } // LoadBaseline loads baseline benchmark results from file func (prd *PerformanceRegressionDetector) LoadBaseline() ([]BenchmarkResult, error) { if _, err := os.Stat(prd.BaselineFile); os.IsNotExist(err) { return nil, fmt.Errorf("baseline file not found: %s", prd.BaselineFile) } data, err := os.ReadFile(prd.BaselineFile) if err != nil { return nil, fmt.Errorf("failed to read baseline file: %w", err) } var results []BenchmarkResult if err := json.Unmarshal(data, &results); err != nil { return nil, fmt.Errorf("failed to parse baseline file: %w", err) } return results, nil } // AnalyzeResults analyzes current results against baseline func (prd *PerformanceRegressionDetector) AnalyzeResults(current []BenchmarkResult) (*RegressionReport, error) { baseline, err := prd.LoadBaseline() if err != nil { return nil, fmt.Errorf("failed to load baseline: %w", err) } report := &RegressionReport{ Regressions: []Regression{}, Improvements: []Improvement{}, } baselineMap := make(map[string]BenchmarkResult) for _, result := range baseline { baselineMap[result.Name] = result } for _, currentResult := range current { baselineResult, exists := baselineMap[currentResult.Name] if !exists { continue // Skip new benchmarks without baseline } percentChange := ((currentResult.Value - baselineResult.Value) / baselineResult.Value) * 100 if percentChange > prd.Threshold { // Performance regression detected severity := "minor" if percentChange > prd.Threshold*2 { severity = "major" } if percentChange > prd.Threshold*3 { severity = "critical" } report.Regressions = append(report.Regressions, Regression{ Benchmark: currentResult.Name, CurrentValue: currentResult.Value, BaselineValue: baselineResult.Value, PercentChange: percentChange, Severity: severity, }) } else if percentChange < -prd.Threshold { // Performance improvement detected report.Improvements = append(report.Improvements, Improvement{ Benchmark: currentResult.Name, CurrentValue: currentResult.Value, BaselineValue: baselineResult.Value, PercentChange: percentChange, }) } } // Generate summary regressionCount := len(report.Regressions) improvementCount := len(report.Improvements) if regressionCount == 0 && improvementCount == 0 { report.Summary = "No significant performance changes detected" } else { report.Summary = fmt.Sprintf("Detected %d regression(s) and %d improvement(s)", regressionCount, improvementCount) } return report, nil } // SaveBaseline saves current results as new baseline func (prd *PerformanceRegressionDetector) SaveBaseline(results []BenchmarkResult) error { data, err := json.MarshalIndent(results, "", " ") if err != nil { return fmt.Errorf("failed to marshal results: %w", err) } err = os.WriteFile(prd.BaselineFile, data, 0600) if err != nil { return fmt.Errorf("failed to write baseline file: %w", err) } return nil } // PrintReport prints a formatted regression report func (prd *PerformanceRegressionDetector) PrintReport(report *RegressionReport) { fmt.Printf("Performance Regression Analysis Report\n") fmt.Printf("=====================================\n\n") fmt.Printf("Summary: %s\n\n", report.Summary) if len(report.Regressions) > 0 { fmt.Printf("Regressions (%d):\n", len(report.Regressions)) for _, regression := range report.Regressions { fmt.Printf(" [%s] %s: %.2f -> %.2f (%.1f%% worse)\n", regression.Severity, regression.Benchmark, regression.BaselineValue, regression.CurrentValue, regression.PercentChange) } fmt.Println() } if len(report.Improvements) > 0 { fmt.Printf("Improvements (%d):\n", len(report.Improvements)) for _, improvement := range report.Improvements { fmt.Printf(" [+] %s: %.2f -> %.2f (%.1f%% better)\n", improvement.Benchmark, improvement.BaselineValue, improvement.CurrentValue, improvement.PercentChange) } fmt.Println() } }