package security import ( "encoding/json" "runtime" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/fraktal/mev-beta/internal/logger" ) func TestNewPerformanceProfiler(t *testing.T) { testLogger := logger.New("info", "text", "/tmp/test.log") // Test with default config profiler := NewPerformanceProfiler(testLogger, nil) assert.NotNil(t, profiler) assert.NotNil(t, profiler.config) assert.Equal(t, time.Second, profiler.config.SamplingInterval) assert.Equal(t, 24*time.Hour, profiler.config.RetentionPeriod) // Test with custom config customConfig := &ProfilerConfig{ SamplingInterval: 500 * time.Millisecond, RetentionPeriod: 12 * time.Hour, MaxOperations: 500, MaxMemoryUsage: 512 * 1024 * 1024, MaxGoroutines: 500, MaxResponseTime: 500 * time.Millisecond, MinThroughput: 50, EnableGCMetrics: false, EnableCPUProfiling: false, EnableMemProfiling: false, ReportInterval: 30 * time.Minute, AutoOptimize: true, } profiler2 := NewPerformanceProfiler(testLogger, customConfig) assert.NotNil(t, profiler2) assert.Equal(t, 500*time.Millisecond, profiler2.config.SamplingInterval) assert.Equal(t, 12*time.Hour, profiler2.config.RetentionPeriod) assert.True(t, profiler2.config.AutoOptimize) // Cleanup profiler.Stop() profiler2.Stop() } func TestOperationTracking(t *testing.T) { testLogger := logger.New("info", "text", "/tmp/test.log") profiler := NewPerformanceProfiler(testLogger, nil) defer profiler.Stop() // Test basic operation tracking tracker := profiler.StartOperation("test_operation") time.Sleep(10 * time.Millisecond) // Simulate work tracker.End() // Verify operation was recorded profiler.mutex.RLock() profile, exists := profiler.operations["test_operation"] profiler.mutex.RUnlock() assert.True(t, exists) assert.Equal(t, "test_operation", profile.Operation) assert.Equal(t, int64(1), profile.TotalCalls) assert.Greater(t, profile.TotalDuration, time.Duration(0)) assert.Greater(t, profile.AverageTime, time.Duration(0)) assert.Equal(t, 0.0, profile.ErrorRate) assert.NotEmpty(t, profile.PerformanceClass) } func TestOperationTrackingWithError(t *testing.T) { testLogger := logger.New("info", "text", "/tmp/test.log") profiler := NewPerformanceProfiler(testLogger, nil) defer profiler.Stop() // Test operation tracking with error tracker := profiler.StartOperation("error_operation") time.Sleep(5 * time.Millisecond) tracker.EndWithError(assert.AnError) // Verify error was recorded profiler.mutex.RLock() profile, exists := profiler.operations["error_operation"] profiler.mutex.RUnlock() assert.True(t, exists) assert.Equal(t, int64(1), profile.ErrorCount) assert.Equal(t, 100.0, profile.ErrorRate) assert.Equal(t, assert.AnError.Error(), profile.LastError) assert.False(t, profile.LastErrorTime.IsZero()) } func TestPerformanceClassification(t *testing.T) { testLogger := logger.New("info", "text", "/tmp/test.log") profiler := NewPerformanceProfiler(testLogger, nil) defer profiler.Stop() testCases := []struct { name string sleepDuration time.Duration expectedClass string }{ {"excellent", 1 * time.Millisecond, "excellent"}, {"good", 20 * time.Millisecond, "good"}, {"average", 100 * time.Millisecond, "average"}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { tracker := profiler.StartOperation(tc.name) time.Sleep(tc.sleepDuration) tracker.End() profiler.mutex.RLock() profile := profiler.operations[tc.name] profiler.mutex.RUnlock() assert.Equal(t, tc.expectedClass, profile.PerformanceClass) }) } } func TestSystemMetricsCollection(t *testing.T) { testLogger := logger.New("info", "text", "/tmp/test.log") config := &ProfilerConfig{ SamplingInterval: 100 * time.Millisecond, RetentionPeriod: time.Hour, } profiler := NewPerformanceProfiler(testLogger, config) defer profiler.Stop() // Wait for metrics collection time.Sleep(200 * time.Millisecond) profiler.mutex.RLock() metrics := profiler.metrics resourceUsage := profiler.resourceUsage profiler.mutex.RUnlock() // Verify system metrics were collected assert.NotNil(t, metrics["heap_alloc"]) assert.NotNil(t, metrics["heap_sys"]) assert.NotNil(t, metrics["goroutines"]) assert.NotNil(t, metrics["gc_cycles"]) // Verify resource usage was updated assert.Greater(t, resourceUsage.HeapUsed, uint64(0)) assert.GreaterOrEqual(t, resourceUsage.GCCycles, uint32(0)) assert.False(t, resourceUsage.Timestamp.IsZero()) } func TestPerformanceAlerts(t *testing.T) { testLogger := logger.New("info", "text", "/tmp/test.log") config := &ProfilerConfig{ SamplingInterval: time.Second, MaxResponseTime: 10 * time.Millisecond, // Very low threshold for testing } profiler := NewPerformanceProfiler(testLogger, config) defer profiler.Stop() // Trigger a slow operation to generate alert tracker := profiler.StartOperation("slow_operation") time.Sleep(50 * time.Millisecond) // Exceeds threshold tracker.End() // Check if alert was generated profiler.mutex.RLock() alerts := profiler.alerts profiler.mutex.RUnlock() assert.NotEmpty(t, alerts) foundAlert := false for _, alert := range alerts { if alert.Operation == "slow_operation" && alert.Type == "response_time" { foundAlert = true assert.Contains(t, []string{"warning", "critical"}, alert.Severity) assert.Greater(t, alert.Value, 10.0) // Should exceed 10ms threshold break } } assert.True(t, foundAlert, "Expected to find response time alert for slow operation") } func TestReportGeneration(t *testing.T) { testLogger := logger.New("info", "text", "/tmp/test.log") profiler := NewPerformanceProfiler(testLogger, nil) defer profiler.Stop() // Generate some test data tracker1 := profiler.StartOperation("fast_op") time.Sleep(1 * time.Millisecond) tracker1.End() tracker2 := profiler.StartOperation("slow_op") time.Sleep(50 * time.Millisecond) tracker2.End() // Generate report report, err := profiler.GenerateReport() require.NoError(t, err) assert.NotNil(t, report) // Verify report structure assert.NotEmpty(t, report.ID) assert.False(t, report.Timestamp.IsZero()) assert.NotEmpty(t, report.OverallHealth) assert.GreaterOrEqual(t, report.HealthScore, 0.0) assert.LessOrEqual(t, report.HealthScore, 100.0) // Verify operations are included assert.NotEmpty(t, report.TopOperations) assert.NotNil(t, report.ResourceSummary) assert.NotNil(t, report.TrendAnalysis) assert.NotNil(t, report.OptimizationPlan) // Verify resource summary assert.GreaterOrEqual(t, report.ResourceSummary.MemoryEfficiency, 0.0) assert.LessOrEqual(t, report.ResourceSummary.MemoryEfficiency, 100.0) assert.GreaterOrEqual(t, report.ResourceSummary.CPUEfficiency, 0.0) assert.LessOrEqual(t, report.ResourceSummary.CPUEfficiency, 100.0) } func TestBottleneckAnalysis(t *testing.T) { testLogger := logger.New("info", "text", "/tmp/test.log") profiler := NewPerformanceProfiler(testLogger, nil) defer profiler.Stop() // Create operations with different performance characteristics tracker1 := profiler.StartOperation("critical_op") time.Sleep(200 * time.Millisecond) // This should be classified as poor/critical tracker1.End() tracker2 := profiler.StartOperation("good_op") time.Sleep(1 * time.Millisecond) // This should be excellent tracker2.End() // Generate report to trigger bottleneck analysis report, err := profiler.GenerateReport() require.NoError(t, err) // Should detect performance bottleneck for critical_op assert.NotEmpty(t, report.Bottlenecks) foundBottleneck := false for _, bottleneck := range report.Bottlenecks { if bottleneck.Operation == "critical_op" || bottleneck.Type == "performance" { foundBottleneck = true assert.Contains(t, []string{"medium", "high"}, bottleneck.Severity) assert.Greater(t, bottleneck.Impact, 0.0) break } } // Note: May not always find bottleneck due to classification thresholds if !foundBottleneck { t.Log("Bottleneck not detected - this may be due to classification thresholds") } } func TestImprovementSuggestions(t *testing.T) { testLogger := logger.New("info", "text", "/tmp/test.log") profiler := NewPerformanceProfiler(testLogger, nil) defer profiler.Stop() // Simulate memory pressure by allocating memory largeData := make([]byte, 100*1024*1024) // 100MB _ = largeData // Force GC to update memory stats runtime.GC() time.Sleep(100 * time.Millisecond) // Create a slow operation tracker := profiler.StartOperation("slow_operation") time.Sleep(300 * time.Millisecond) // Should be classified as poor/critical tracker.End() // Generate report report, err := profiler.GenerateReport() require.NoError(t, err) // Should have improvement suggestions assert.NotNil(t, report.Improvements) // Look for memory or performance improvements hasMemoryImprovement := false hasPerformanceImprovement := false for _, suggestion := range report.Improvements { if suggestion.Area == "memory" { hasMemoryImprovement = true } if suggestion.Area == "operation_slow_operation" { hasPerformanceImprovement = true } } // At least one type of improvement should be suggested assert.True(t, hasMemoryImprovement || hasPerformanceImprovement, "Expected memory or performance improvement suggestions") } func TestMetricsExport(t *testing.T) { testLogger := logger.New("info", "text", "/tmp/test.log") profiler := NewPerformanceProfiler(testLogger, nil) defer profiler.Stop() // Wait for some metrics to be collected time.Sleep(100 * time.Millisecond) // Test JSON export jsonData, err := profiler.ExportMetrics("json") require.NoError(t, err) assert.NotEmpty(t, jsonData) // Verify it's valid JSON var metrics map[string]*PerformanceMetric err = json.Unmarshal(jsonData, &metrics) require.NoError(t, err) assert.NotEmpty(t, metrics) // Test Prometheus export promData, err := profiler.ExportMetrics("prometheus") require.NoError(t, err) assert.NotEmpty(t, promData) assert.Contains(t, string(promData), "# HELP") assert.Contains(t, string(promData), "# TYPE") assert.Contains(t, string(promData), "mev_bot_") // Test unsupported format _, err = profiler.ExportMetrics("unsupported") assert.Error(t, err) assert.Contains(t, err.Error(), "unsupported export format") } func TestThresholdConfiguration(t *testing.T) { testLogger := logger.New("info", "text", "/tmp/test.log") profiler := NewPerformanceProfiler(testLogger, nil) defer profiler.Stop() // Verify default thresholds were set profiler.mutex.RLock() thresholds := profiler.thresholds profiler.mutex.RUnlock() assert.NotEmpty(t, thresholds) assert.Contains(t, thresholds, "memory_usage") assert.Contains(t, thresholds, "goroutine_count") assert.Contains(t, thresholds, "response_time") assert.Contains(t, thresholds, "error_rate") // Verify threshold structure memThreshold := thresholds["memory_usage"] assert.Equal(t, "memory_usage", memThreshold.Metric) assert.Greater(t, memThreshold.Warning, 0.0) assert.Greater(t, memThreshold.Critical, memThreshold.Warning) assert.Equal(t, "gt", memThreshold.Operator) } func TestResourceEfficiencyCalculation(t *testing.T) { testLogger := logger.New("info", "text", "/tmp/test.log") profiler := NewPerformanceProfiler(testLogger, nil) defer profiler.Stop() // Create operations with different performance classes tracker1 := profiler.StartOperation("excellent_op") time.Sleep(1 * time.Millisecond) tracker1.End() tracker2 := profiler.StartOperation("good_op") time.Sleep(20 * time.Millisecond) tracker2.End() // Calculate efficiencies memEfficiency := profiler.calculateMemoryEfficiency() cpuEfficiency := profiler.calculateCPUEfficiency() gcEfficiency := profiler.calculateGCEfficiency() throughputScore := profiler.calculateThroughputScore() // All efficiency scores should be between 0 and 100 assert.GreaterOrEqual(t, memEfficiency, 0.0) assert.LessOrEqual(t, memEfficiency, 100.0) assert.GreaterOrEqual(t, cpuEfficiency, 0.0) assert.LessOrEqual(t, cpuEfficiency, 100.0) assert.GreaterOrEqual(t, gcEfficiency, 0.0) assert.LessOrEqual(t, gcEfficiency, 100.0) assert.GreaterOrEqual(t, throughputScore, 0.0) assert.LessOrEqual(t, throughputScore, 100.0) // CPU efficiency should be high since we have good operations assert.Greater(t, cpuEfficiency, 50.0) } func TestCleanupOldData(t *testing.T) { testLogger := logger.New("info", "text", "/tmp/test.log") config := &ProfilerConfig{ RetentionPeriod: 100 * time.Millisecond, // Very short for testing } profiler := NewPerformanceProfiler(testLogger, config) defer profiler.Stop() // Create some alerts profiler.mutex.Lock() oldAlert := PerformanceAlert{ ID: "old_alert", Timestamp: time.Now().Add(-200 * time.Millisecond), // Older than retention } newAlert := PerformanceAlert{ ID: "new_alert", Timestamp: time.Now(), } profiler.alerts = []PerformanceAlert{oldAlert, newAlert} profiler.mutex.Unlock() // Trigger cleanup profiler.cleanupOldData() // Verify old data was removed profiler.mutex.RLock() alerts := profiler.alerts profiler.mutex.RUnlock() assert.Len(t, alerts, 1) assert.Equal(t, "new_alert", alerts[0].ID) } func TestOptimizationPlanGeneration(t *testing.T) { testLogger := logger.New("info", "text", "/tmp/test.log") profiler := NewPerformanceProfiler(testLogger, nil) defer profiler.Stop() // Create test recommendations recommendations := []PerformanceRecommendation{ { Type: "immediate", Priority: "high", Category: "memory", Title: "Fix Memory Leak", ExpectedGain: 25.0, }, { Type: "short_term", Priority: "medium", Category: "algorithm", Title: "Optimize Algorithm", ExpectedGain: 40.0, }, { Type: "long_term", Priority: "low", Category: "architecture", Title: "Refactor Architecture", ExpectedGain: 15.0, }, } // Generate optimization plan plan := profiler.createOptimizationPlan(recommendations) assert.NotNil(t, plan) assert.Equal(t, 80.0, plan.TotalGain) // 25 + 40 + 15 assert.Greater(t, plan.Timeline, time.Duration(0)) // Verify phase categorization assert.Len(t, plan.Phase1, 1) // immediate assert.Len(t, plan.Phase2, 1) // short_term assert.Len(t, plan.Phase3, 1) // long_term assert.Equal(t, "Fix Memory Leak", plan.Phase1[0].Title) assert.Equal(t, "Optimize Algorithm", plan.Phase2[0].Title) assert.Equal(t, "Refactor Architecture", plan.Phase3[0].Title) } func TestConcurrentOperationTracking(t *testing.T) { testLogger := logger.New("info", "text", "/tmp/test.log") profiler := NewPerformanceProfiler(testLogger, nil) defer profiler.Stop() // Run multiple operations concurrently numOperations := 100 done := make(chan bool, numOperations) for i := 0; i < numOperations; i++ { go func(id int) { defer func() { done <- true }() tracker := profiler.StartOperation("concurrent_op") time.Sleep(1 * time.Millisecond) tracker.End() }(i) } // Wait for all operations to complete for i := 0; i < numOperations; i++ { <-done } // Verify all operations were tracked profiler.mutex.RLock() profile := profiler.operations["concurrent_op"] profiler.mutex.RUnlock() assert.NotNil(t, profile) assert.Equal(t, int64(numOperations), profile.TotalCalls) assert.Greater(t, profile.TotalDuration, time.Duration(0)) assert.Equal(t, 0.0, profile.ErrorRate) // No errors expected } func BenchmarkOperationTracking(b *testing.B) { testLogger := logger.New("error", "text", "/tmp/test.log") // Reduce logging noise profiler := NewPerformanceProfiler(testLogger, nil) defer profiler.Stop() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { tracker := profiler.StartOperation("benchmark_op") // Simulate minimal work runtime.Gosched() tracker.End() } }) } func BenchmarkReportGeneration(b *testing.B) { testLogger := logger.New("error", "text", "/tmp/test.log") profiler := NewPerformanceProfiler(testLogger, nil) defer profiler.Stop() // Create some sample data for i := 0; i < 10; i++ { tracker := profiler.StartOperation("sample_op") time.Sleep(time.Microsecond) tracker.End() } b.ResetTimer() for i := 0; i < b.N; i++ { _, err := profiler.GenerateReport() if err != nil { b.Fatal(err) } } } func TestHealthScoreCalculation(t *testing.T) { testLogger := logger.New("info", "text", "/tmp/test.log") profiler := NewPerformanceProfiler(testLogger, nil) defer profiler.Stop() // Test with clean system (should have high health score) health, score := profiler.calculateOverallHealth() assert.NotEmpty(t, health) assert.GreaterOrEqual(t, score, 0.0) assert.LessOrEqual(t, score, 100.0) assert.Equal(t, "excellent", health) // Should be excellent with no issues // Add some performance issues profiler.mutex.Lock() profiler.operations["poor_op"] = &OperationProfile{ Operation: "poor_op", PerformanceClass: "poor", } profiler.operations["critical_op"] = &OperationProfile{ Operation: "critical_op", PerformanceClass: "critical", } profiler.alerts = append(profiler.alerts, PerformanceAlert{ Severity: "warning", }) profiler.alerts = append(profiler.alerts, PerformanceAlert{ Severity: "critical", }) profiler.mutex.Unlock() // Recalculate health health2, score2 := profiler.calculateOverallHealth() assert.Less(t, score2, score) // Score should be lower with issues assert.NotEqual(t, "excellent", health2) // Should not be excellent anymore }