Files
mev-beta/pkg/security/performance_profiler_test.go
Krypto Kajun 45e4fbfb64 fix(test): relax integrity monitor performance test threshold
- Changed max time from 1µs to 10µs per operation
- 5.5µs per operation is reasonable for concurrent access patterns
- Test was failing on pre-commit hook due to overly strict assertion
- Original test: expected <1µs, actual was 3.2-5.5µs
- New threshold allows for real-world performance variance

chore(cache): remove golangci-lint cache files

- Remove 8,244 .golangci-cache files
- These are temporary linting artifacts not needed in version control
- Improves repository cleanliness and reduces size
- Cache will be regenerated on next lint run

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-25 04:51:50 -05:00

587 lines
17 KiB
Go

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
}