Files
mev-beta/tools/performance-audit/internal/performance_auditor.go
Krypto Kajun 850223a953 fix(multicall): resolve critical multicall parsing corruption issues
- Added comprehensive bounds checking to prevent buffer overruns in multicall parsing
- Implemented graduated validation system (Strict/Moderate/Permissive) to reduce false positives
- Added LRU caching system for address validation with 10-minute TTL
- Enhanced ABI decoder with missing Universal Router and Arbitrum-specific DEX signatures
- Fixed duplicate function declarations and import conflicts across multiple files
- Added error recovery mechanisms with multiple fallback strategies
- Updated tests to handle new validation behavior for suspicious addresses
- Fixed parser test expectations for improved validation system
- Applied gofmt formatting fixes to ensure code style compliance
- Fixed mutex copying issues in monitoring package by introducing MetricsSnapshot
- Resolved critical security vulnerabilities in heuristic address extraction
- Progress: Updated TODO audit from 10% to 35% complete

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 00:12:55 -05:00

1385 lines
37 KiB
Go

package internal
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"runtime"
"sort"
"sync"
"sync/atomic"
"time"
"github.com/fraktal/mev-beta/pkg/math"
)
type PerformanceAuditConfig struct {
TestType string
Duration time.Duration
OutputDir string
Verbose bool
Concurrent int
LoadLevel string
ProfileEnabled bool
BenchmarkMode bool
StressTest bool
TargetTPS int
MaxMemoryMB int
CPUThreshold float64
}
type PerformanceAuditor struct {
config *PerformanceAuditConfig
results *PerformanceResults
mu sync.RWMutex
}
type PerformanceResults struct {
TestType string `json:"test_type"`
Duration int64 `json:"duration_ms"`
LoadLevel string `json:"load_level"`
OverallScore float64 `json:"overall_score"`
ThroughputResults *ThroughputResults `json:"throughput_results,omitempty"`
LatencyResults *LatencyResults `json:"latency_results,omitempty"`
MemoryResults *MemoryResults `json:"memory_results,omitempty"`
CPUResults *CPUResults `json:"cpu_results,omitempty"`
StressResults *StressResults `json:"stress_results,omitempty"`
BenchmarkResults *BenchmarkResults `json:"benchmark_results,omitempty"`
SystemMetrics *SystemMetrics `json:"system_metrics"`
PerformanceIssues []PerformanceIssue `json:"performance_issues"`
Recommendations []string `json:"recommendations"`
Timestamp time.Time `json:"timestamp"`
Environment EnvironmentInfo `json:"environment"`
}
type ThroughputResults struct {
ActualTPS float64 `json:"actual_tps"`
TargetTPS float64 `json:"target_tps"`
MaxTPS float64 `json:"max_tps"`
AverageTPS float64 `json:"average_tps"`
TotalTransactions int64 `json:"total_transactions"`
SuccessfulTransactions int64 `json:"successful_transactions"`
FailedTransactions int64 `json:"failed_transactions"`
SuccessRate float64 `json:"success_rate"`
ThroughputOverTime []TPSMeasurement `json:"throughput_over_time"`
}
type TPSMeasurement struct {
Timestamp time.Time `json:"timestamp"`
TPS float64 `json:"tps"`
}
type LatencyResults struct {
AverageLatency float64 `json:"average_latency_ms"`
MedianLatency float64 `json:"median_latency_ms"`
P95Latency float64 `json:"p95_latency_ms"`
P99Latency float64 `json:"p99_latency_ms"`
MaxLatency float64 `json:"max_latency_ms"`
MinLatency float64 `json:"min_latency_ms"`
LatencyDistribution map[string]int `json:"latency_distribution"`
LatencyOverTime []LatencyMeasurement `json:"latency_over_time"`
}
type LatencyMeasurement struct {
Timestamp time.Time `json:"timestamp"`
Latency float64 `json:"latency_ms"`
}
type MemoryResults struct {
MaxMemoryUsage int64 `json:"max_memory_usage_mb"`
AverageMemoryUsage int64 `json:"average_memory_usage_mb"`
MemoryLeakDetected bool `json:"memory_leak_detected"`
GCFrequency float64 `json:"gc_frequency_per_sec"`
GCPauseTime float64 `json:"gc_pause_time_ms"`
HeapSize int64 `json:"heap_size_mb"`
StackSize int64 `json:"stack_size_mb"`
MemoryOverTime []MemoryMeasurement `json:"memory_over_time"`
}
type MemoryMeasurement struct {
Timestamp time.Time `json:"timestamp"`
HeapMB int64 `json:"heap_mb"`
StackMB int64 `json:"stack_mb"`
TotalMB int64 `json:"total_mb"`
Goroutines int `json:"goroutines"`
}
type CPUResults struct {
MaxCPUUsage float64 `json:"max_cpu_usage_percent"`
AverageCPUUsage float64 `json:"average_cpu_usage_percent"`
CPUCores int `json:"cpu_cores"`
CPUEfficiency float64 `json:"cpu_efficiency_percent"`
CPUOverTime []CPUMeasurement `json:"cpu_over_time"`
}
type CPUMeasurement struct {
Timestamp time.Time `json:"timestamp"`
Usage float64 `json:"usage_percent"`
UserTime float64 `json:"user_time_percent"`
SystemTime float64 `json:"system_time_percent"`
}
type StressResults struct {
StressLevel string `json:"stress_level"`
BreakingPoint float64 `json:"breaking_point_tps"`
RecoveryTime float64 `json:"recovery_time_ms"`
ErrorRate float64 `json:"error_rate_percent"`
StressScenarios []StressScenario `json:"stress_scenarios"`
}
type StressScenario struct {
Name string `json:"name"`
LoadMultiplier float64 `json:"load_multiplier"`
Duration int64 `json:"duration_ms"`
Success bool `json:"success"`
ErrorCount int `json:"error_count"`
Timestamp time.Time `json:"timestamp"`
}
type BenchmarkResults struct {
ArbitrageDetectionBench *BenchResult `json:"arbitrage_detection_bench"`
PriceCalculationBench *BenchResult `json:"price_calculation_bench"`
SwapSimulationBench *BenchResult `json:"swap_simulation_bench"`
MemoryAllocationBench *BenchResult `json:"memory_allocation_bench"`
}
type BenchResult struct {
Name string `json:"name"`
Iterations int64 `json:"iterations"`
NsPerOp int64 `json:"ns_per_op"`
MBPerSec float64 `json:"mb_per_sec"`
AllocsPerOp int64 `json:"allocs_per_op"`
BytesPerOp int64 `json:"bytes_per_op"`
}
type SystemMetrics struct {
StartTime time.Time `json:"start_time"`
EndTime time.Time `json:"end_time"`
TotalGoroutines int `json:"total_goroutines"`
MaxGoroutines int `json:"max_goroutines"`
AvgGoroutines float64 `json:"avg_goroutines"`
GCCycles int64 `json:"gc_cycles"`
TotalAllocations int64 `json:"total_allocations_mb"`
LiveObjects int64 `json:"live_objects"`
}
type PerformanceIssue struct {
Severity string `json:"severity"`
Category string `json:"category"`
Description string `json:"description"`
Impact string `json:"impact"`
Suggestion string `json:"suggestion"`
Timestamp time.Time `json:"timestamp"`
}
type EnvironmentInfo struct {
GOOS string `json:"goos"`
GOARCH string `json:"goarch"`
GoVersion string `json:"go_version"`
NumCPU int `json:"num_cpu"`
GOMAXPROCS int `json:"gomaxprocs"`
CGOEnabled bool `json:"cgo_enabled"`
}
func NewPerformanceAuditor(config *PerformanceAuditConfig) (*PerformanceAuditor, error) {
return &PerformanceAuditor{
config: config,
results: &PerformanceResults{
TestType: config.TestType,
LoadLevel: config.LoadLevel,
PerformanceIssues: make([]PerformanceIssue, 0),
Recommendations: make([]string, 0),
Timestamp: time.Now(),
Environment: EnvironmentInfo{
GOOS: runtime.GOOS,
GOARCH: runtime.GOARCH,
GoVersion: runtime.Version(),
NumCPU: runtime.NumCPU(),
GOMAXPROCS: runtime.GOMAXPROCS(-1),
CGOEnabled: runtime.GOEXPERIMENT == "cgocheck2",
},
},
}, nil
}
func (pa *PerformanceAuditor) RunPerformanceTests(ctx context.Context) error {
startTime := time.Now()
defer func() {
pa.results.Duration = time.Since(startTime).Milliseconds()
}()
pa.results.SystemMetrics = &SystemMetrics{
StartTime: startTime,
}
switch pa.config.TestType {
case "throughput":
return pa.runThroughputTest(ctx)
case "latency":
return pa.runLatencyTest(ctx)
case "memory":
return pa.runMemoryTest(ctx)
case "cpu":
return pa.runCPUTest(ctx)
case "stress":
return pa.runStressTest(ctx)
case "benchmark":
return pa.runBenchmarkTest(ctx)
case "all":
return pa.runAllTests(ctx)
default:
return fmt.Errorf("unsupported test type: %s", pa.config.TestType)
}
}
func (pa *PerformanceAuditor) runAllTests(ctx context.Context) error {
tests := []struct {
name string
fn func(context.Context) error
}{
{"throughput", pa.runThroughputTest},
{"latency", pa.runLatencyTest},
{"memory", pa.runMemoryTest},
{"cpu", pa.runCPUTest},
}
if pa.config.StressTest {
tests = append(tests, struct {
name string
fn func(context.Context) error
}{"stress", pa.runStressTest})
}
if pa.config.BenchmarkMode {
tests = append(tests, struct {
name string
fn func(context.Context) error
}{"benchmark", pa.runBenchmarkTest})
}
for _, test := range tests {
if pa.config.Verbose {
fmt.Printf("Running %s test...\n", test.name)
}
if err := test.fn(ctx); err != nil {
return fmt.Errorf("failed %s test: %w", test.name, err)
}
}
pa.calculateOverallScore()
pa.generateRecommendations()
return nil
}
func (pa *PerformanceAuditor) runThroughputTest(ctx context.Context) error {
if pa.config.Verbose {
fmt.Println("Starting throughput test...")
}
pa.results.ThroughputResults = &ThroughputResults{
TargetTPS: float64(pa.config.TargetTPS),
ThroughputOverTime: make([]TPSMeasurement, 0),
}
var totalTransactions int64
var successfulTransactions int64
var failedTransactions int64
// Create a ticker for measuring TPS over time
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
// Create workers for generating load
var wg sync.WaitGroup
transactionChan := make(chan bool, pa.config.Concurrent*10)
// Start workers
for i := 0; i < pa.config.Concurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
pa.throughputWorker(ctx, transactionChan, &totalTransactions, &successfulTransactions, &failedTransactions)
}()
}
// Monitoring goroutine
monitorCtx, cancel := context.WithCancel(ctx)
defer cancel()
go pa.monitorThroughput(monitorCtx, &totalTransactions, ticker)
// Generate load
loadGenerator := time.NewTicker(time.Duration(1000000000/pa.config.TargetTPS) * time.Nanosecond)
defer loadGenerator.Stop()
testDuration := pa.config.Duration
timeout := time.After(testDuration)
LoadLoop:
for {
select {
case <-ctx.Done():
break LoadLoop
case <-timeout:
break LoadLoop
case <-loadGenerator.C:
select {
case transactionChan <- true:
default:
// Channel full, count as failed
atomic.AddInt64(&failedTransactions, 1)
}
}
}
close(transactionChan)
wg.Wait()
// Calculate final results
totalTxns := atomic.LoadInt64(&totalTransactions)
successTxns := atomic.LoadInt64(&successfulTransactions)
failedTxns := atomic.LoadInt64(&failedTransactions)
pa.results.ThroughputResults.TotalTransactions = totalTxns
pa.results.ThroughputResults.SuccessfulTransactions = successTxns
pa.results.ThroughputResults.FailedTransactions = failedTxns
if totalTxns > 0 {
pa.results.ThroughputResults.SuccessRate = float64(successTxns) / float64(totalTxns) * 100.0
}
durationSeconds := testDuration.Seconds()
pa.results.ThroughputResults.ActualTPS = float64(totalTxns) / durationSeconds
pa.results.ThroughputResults.AverageTPS = pa.results.ThroughputResults.ActualTPS
// Analyze throughput performance
pa.analyzeThroughputPerformance()
return nil
}
func (pa *PerformanceAuditor) throughputWorker(ctx context.Context, transactionChan <-chan bool,
totalTxns, successTxns, failedTxns *int64) {
// Initialize arbitrage calculator for realistic work
calculator := math.NewArbitrageCalculator()
for {
select {
case <-ctx.Done():
return
case _, ok := <-transactionChan:
if !ok {
return
}
atomic.AddInt64(totalTxns, 1)
// Simulate realistic arbitrage calculation work
if pa.simulateArbitrageCalculation(calculator) {
atomic.AddInt64(successTxns, 1)
} else {
atomic.AddInt64(failedTxns, 1)
}
}
}
}
func (pa *PerformanceAuditor) simulateArbitrageCalculation(calculator *math.ArbitrageCalculator) bool {
// Simulate realistic arbitrage calculation
// This would normally involve actual price fetching and calculation
// Simple CPU-intensive operation to simulate work
sum := 0.0
for i := 0; i < 1000; i++ {
sum += float64(i) * 1.001
}
// Simulate success/failure rate based on load level
switch pa.config.LoadLevel {
case "light":
return sum > 0 // Almost always succeeds
case "normal":
return sum > 100 // Usually succeeds
case "heavy":
return sum > 200 // Sometimes fails
case "extreme":
return sum > 400 // Often fails
default:
return sum > 0
}
}
func (pa *PerformanceAuditor) monitorThroughput(ctx context.Context, totalTransactions *int64, ticker *time.Ticker) {
lastCount := int64(0)
for {
select {
case <-ctx.Done():
return
case now := <-ticker.C:
currentCount := atomic.LoadInt64(totalTransactions)
tps := float64(currentCount - lastCount)
pa.mu.Lock()
pa.results.ThroughputResults.ThroughputOverTime = append(
pa.results.ThroughputResults.ThroughputOverTime,
TPSMeasurement{
Timestamp: now,
TPS: tps,
},
)
// Update max TPS
if tps > pa.results.ThroughputResults.MaxTPS {
pa.results.ThroughputResults.MaxTPS = tps
}
pa.mu.Unlock()
lastCount = currentCount
}
}
}
func (pa *PerformanceAuditor) runLatencyTest(ctx context.Context) error {
if pa.config.Verbose {
fmt.Println("Starting latency test...")
}
pa.results.LatencyResults = &LatencyResults{
LatencyDistribution: make(map[string]int),
LatencyOverTime: make([]LatencyMeasurement, 0),
}
latencies := make([]float64, 0, 10000)
var mu sync.Mutex
// Run latency measurements
var wg sync.WaitGroup
requests := 1000 // Number of requests per worker
for i := 0; i < pa.config.Concurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
pa.latencyWorker(ctx, requests, &latencies, &mu)
}()
}
wg.Wait()
// Calculate latency statistics
if len(latencies) > 0 {
sort.Float64s(latencies)
pa.results.LatencyResults.MinLatency = latencies[0]
pa.results.LatencyResults.MaxLatency = latencies[len(latencies)-1]
// Calculate percentiles
pa.results.LatencyResults.MedianLatency = pa.percentile(latencies, 50)
pa.results.LatencyResults.P95Latency = pa.percentile(latencies, 95)
pa.results.LatencyResults.P99Latency = pa.percentile(latencies, 99)
// Calculate average
sum := 0.0
for _, lat := range latencies {
sum += lat
}
pa.results.LatencyResults.AverageLatency = sum / float64(len(latencies))
// Create distribution
pa.createLatencyDistribution(latencies)
}
pa.analyzeLatencyPerformance()
return nil
}
func (pa *PerformanceAuditor) latencyWorker(ctx context.Context, requests int,
latencies *[]float64, mu *sync.Mutex) {
calculator := math.NewArbitrageCalculator()
for i := 0; i < requests; i++ {
select {
case <-ctx.Done():
return
default:
start := time.Now()
pa.simulateArbitrageCalculation(calculator)
latency := float64(time.Since(start).Nanoseconds()) / 1000000.0 // Convert to ms
mu.Lock()
*latencies = append(*latencies, latency)
// Record over time (sample every 10th measurement)
if i%10 == 0 {
pa.results.LatencyResults.LatencyOverTime = append(
pa.results.LatencyResults.LatencyOverTime,
LatencyMeasurement{
Timestamp: time.Now(),
Latency: latency,
},
)
}
mu.Unlock()
}
}
}
func (pa *PerformanceAuditor) percentile(sortedData []float64, percentile int) float64 {
if len(sortedData) == 0 {
return 0
}
index := int(float64(len(sortedData)) * float64(percentile) / 100.0)
if index >= len(sortedData) {
index = len(sortedData) - 1
}
return sortedData[index]
}
func (pa *PerformanceAuditor) createLatencyDistribution(latencies []float64) {
// Create distribution buckets
buckets := map[string]int{
"< 1ms": 0,
"1-5ms": 0,
"5-10ms": 0,
"10-50ms": 0,
"50-100ms": 0,
"> 100ms": 0,
}
for _, lat := range latencies {
switch {
case lat < 1:
buckets["< 1ms"]++
case lat < 5:
buckets["1-5ms"]++
case lat < 10:
buckets["5-10ms"]++
case lat < 50:
buckets["10-50ms"]++
case lat < 100:
buckets["50-100ms"]++
default:
buckets["> 100ms"]++
}
}
pa.results.LatencyResults.LatencyDistribution = buckets
}
func (pa *PerformanceAuditor) runMemoryTest(ctx context.Context) error {
if pa.config.Verbose {
fmt.Println("Starting memory test...")
}
pa.results.MemoryResults = &MemoryResults{
MemoryOverTime: make([]MemoryMeasurement, 0),
}
// Monitor memory usage
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
monitorCtx, cancel := context.WithCancel(ctx)
defer cancel()
go pa.monitorMemoryUsage(monitorCtx, ticker)
// Generate memory load
return pa.generateMemoryLoad(ctx)
}
func (pa *PerformanceAuditor) monitorMemoryUsage(ctx context.Context, ticker *time.Ticker) {
var m runtime.MemStats
var maxMemory int64
var totalMemory int64
var measurements int64
for {
select {
case <-ctx.Done():
return
case now := <-ticker.C:
runtime.ReadMemStats(&m)
heapMB := int64(m.HeapInuse) / 1024 / 1024
stackMB := int64(m.StackInuse) / 1024 / 1024
totalMB := heapMB + stackMB
if totalMB > maxMemory {
maxMemory = totalMB
}
totalMemory += totalMB
measurements++
pa.mu.Lock()
pa.results.MemoryResults.MemoryOverTime = append(
pa.results.MemoryResults.MemoryOverTime,
MemoryMeasurement{
Timestamp: now,
HeapMB: heapMB,
StackMB: stackMB,
TotalMB: totalMB,
Goroutines: runtime.NumGoroutine(),
},
)
pa.results.MemoryResults.MaxMemoryUsage = maxMemory
if measurements > 0 {
pa.results.MemoryResults.AverageMemoryUsage = totalMemory / measurements
}
pa.results.MemoryResults.HeapSize = heapMB
pa.results.MemoryResults.StackSize = stackMB
pa.mu.Unlock()
}
}
}
func (pa *PerformanceAuditor) generateMemoryLoad(ctx context.Context) error {
// Generate memory pressure by creating and releasing large data structures
var wg sync.WaitGroup
for i := 0; i < pa.config.Concurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
pa.memoryWorker(ctx)
}()
}
wg.Wait()
// Detect memory leaks
pa.detectMemoryLeaks()
pa.analyzeMemoryPerformance()
return nil
}
func (pa *PerformanceAuditor) memoryWorker(ctx context.Context) {
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
// Allocate memory to simulate real workload
data := make([]byte, 1024*1024) // 1MB
// Do some work with the data
for i := range data {
data[i] = byte(i % 256)
}
// Simulate processing time
time.Sleep(10 * time.Millisecond)
// Allow garbage collection
data = nil
}
}
}
func (pa *PerformanceAuditor) detectMemoryLeaks() {
if len(pa.results.MemoryResults.MemoryOverTime) < 10 {
return
}
// Simple memory leak detection: check if memory usage is consistently increasing
measurements := pa.results.MemoryResults.MemoryOverTime
start := measurements[0].TotalMB
end := measurements[len(measurements)-1].TotalMB
// If memory usage increased by more than 50% and didn't decrease significantly
if float64(end) > float64(start)*1.5 {
pa.results.MemoryResults.MemoryLeakDetected = true
pa.addPerformanceIssue("CRITICAL", "memory",
"Potential memory leak detected: memory usage increased significantly during test",
"High memory usage may lead to system instability",
"Review memory allocation patterns and ensure proper cleanup")
}
}
func (pa *PerformanceAuditor) runCPUTest(ctx context.Context) error {
if pa.config.Verbose {
fmt.Println("Starting CPU test...")
}
pa.results.CPUResults = &CPUResults{
CPUCores: runtime.NumCPU(),
CPUOverTime: make([]CPUMeasurement, 0),
}
// Generate CPU load and monitor
return pa.generateCPULoad(ctx)
}
func (pa *PerformanceAuditor) generateCPULoad(ctx context.Context) error {
var wg sync.WaitGroup
// Start CPU workers
for i := 0; i < pa.config.Concurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
pa.cpuWorker(ctx)
}()
}
// Monitor CPU usage
go pa.monitorCPUUsage(ctx)
wg.Wait()
pa.analyzeCPUPerformance()
return nil
}
func (pa *PerformanceAuditor) cpuWorker(ctx context.Context) {
// CPU-intensive work
calculator := math.NewArbitrageCalculator()
for {
select {
case <-ctx.Done():
return
default:
// Simulate complex arbitrage calculations
for i := 0; i < 10000; i++ {
pa.simulateArbitrageCalculation(calculator)
}
}
}
}
func (pa *PerformanceAuditor) monitorCPUUsage(ctx context.Context) {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
// Simple CPU monitoring (in real implementation, would use more sophisticated methods)
var maxCPU float64
var totalCPU float64
var measurements int
for {
select {
case <-ctx.Done():
return
case now := <-ticker.C:
// Simulate CPU usage measurement (simplified)
cpuUsage := float64(runtime.NumGoroutine()) / float64(runtime.NumCPU()) * 10.0
if cpuUsage > 100.0 {
cpuUsage = 100.0
}
if cpuUsage > maxCPU {
maxCPU = cpuUsage
}
totalCPU += cpuUsage
measurements++
pa.mu.Lock()
pa.results.CPUResults.CPUOverTime = append(
pa.results.CPUResults.CPUOverTime,
CPUMeasurement{
Timestamp: now,
Usage: cpuUsage,
UserTime: cpuUsage * 0.8, // Simplified
SystemTime: cpuUsage * 0.2, // Simplified
},
)
pa.results.CPUResults.MaxCPUUsage = maxCPU
if measurements > 0 {
pa.results.CPUResults.AverageCPUUsage = totalCPU / float64(measurements)
}
pa.mu.Unlock()
}
}
}
func (pa *PerformanceAuditor) runStressTest(ctx context.Context) error {
if pa.config.Verbose {
fmt.Println("Starting stress test...")
}
pa.results.StressResults = &StressResults{
StressLevel: pa.config.LoadLevel,
StressScenarios: make([]StressScenario, 0),
}
// Define stress scenarios
scenarios := []struct {
name string
loadMultiplier float64
duration time.Duration
}{
{"baseline", 1.0, 30 * time.Second},
{"moderate_stress", 2.0, 30 * time.Second},
{"high_stress", 5.0, 30 * time.Second},
{"extreme_stress", 10.0, 30 * time.Second},
{"breaking_point", 20.0, 30 * time.Second},
}
for _, scenario := range scenarios {
if pa.config.Verbose {
fmt.Printf("Running stress scenario: %s\n", scenario.name)
}
result := pa.runStressScenario(ctx, scenario.name, scenario.loadMultiplier, scenario.duration)
pa.results.StressResults.StressScenarios = append(pa.results.StressResults.StressScenarios, result)
// If we found the breaking point, record it
if !result.Success && pa.results.StressResults.BreakingPoint == 0 {
pa.results.StressResults.BreakingPoint = scenario.loadMultiplier * float64(pa.config.TargetTPS)
}
}
pa.analyzeStressPerformance()
return nil
}
func (pa *PerformanceAuditor) runStressScenario(ctx context.Context, name string, loadMultiplier float64, duration time.Duration) StressScenario {
scenario := StressScenario{
Name: name,
LoadMultiplier: loadMultiplier,
Duration: duration.Milliseconds(),
Timestamp: time.Now(),
}
// Create scenario-specific context
scenarioCtx, cancel := context.WithTimeout(ctx, duration)
defer cancel()
// Increase load based on multiplier
targetTPS := int(float64(pa.config.TargetTPS) * loadMultiplier)
concurrent := int(float64(pa.config.Concurrent) * loadMultiplier)
var errorCount int64
var successCount int64
var wg sync.WaitGroup
// Start workers
for i := 0; i < concurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
pa.stressWorker(scenarioCtx, &successCount, &errorCount)
}()
}
wg.Wait()
total := errorCount + successCount
scenario.ErrorCount = int(errorCount)
scenario.Success = total > 0 && float64(errorCount)/float64(total) < 0.1 // Less than 10% errors
return scenario
}
func (pa *PerformanceAuditor) stressWorker(ctx context.Context, successCount, errorCount *int64) {
calculator := math.NewArbitrageCalculator()
for {
select {
case <-ctx.Done():
return
default:
if pa.simulateArbitrageCalculation(calculator) {
atomic.AddInt64(successCount, 1)
} else {
atomic.AddInt64(errorCount, 1)
}
}
}
}
func (pa *PerformanceAuditor) runBenchmarkTest(ctx context.Context) error {
if pa.config.Verbose {
fmt.Println("Starting benchmark test...")
}
pa.results.BenchmarkResults = &BenchmarkResults{}
// Run different benchmark scenarios
benchmarks := []struct {
name string
fn func() *BenchResult
}{
{"arbitrage_detection", pa.benchmarkArbitrageDetection},
{"price_calculation", pa.benchmarkPriceCalculation},
{"swap_simulation", pa.benchmarkSwapSimulation},
{"memory_allocation", pa.benchmarkMemoryAllocation},
}
for _, benchmark := range benchmarks {
if pa.config.Verbose {
fmt.Printf("Running benchmark: %s\n", benchmark.name)
}
result := benchmark.fn()
switch benchmark.name {
case "arbitrage_detection":
pa.results.BenchmarkResults.ArbitrageDetectionBench = result
case "price_calculation":
pa.results.BenchmarkResults.PriceCalculationBench = result
case "swap_simulation":
pa.results.BenchmarkResults.SwapSimulationBench = result
case "memory_allocation":
pa.results.BenchmarkResults.MemoryAllocationBench = result
}
}
pa.analyzeBenchmarkPerformance()
return nil
}
func (pa *PerformanceAuditor) benchmarkArbitrageDetection() *BenchResult {
iterations := int64(1000)
start := time.Now()
var allocs1, allocs2 runtime.MemStats
runtime.GC()
runtime.ReadMemStats(&allocs1)
calculator := math.NewArbitrageCalculator()
for i := int64(0); i < iterations; i++ {
pa.simulateArbitrageCalculation(calculator)
}
runtime.ReadMemStats(&allocs2)
duration := time.Since(start)
return &BenchResult{
Name: "arbitrage_detection",
Iterations: iterations,
NsPerOp: duration.Nanoseconds() / iterations,
AllocsPerOp: int64(allocs2.Mallocs-allocs1.Mallocs) / iterations,
BytesPerOp: int64(allocs2.TotalAlloc-allocs1.TotalAlloc) / iterations,
}
}
func (pa *PerformanceAuditor) benchmarkPriceCalculation() *BenchResult {
iterations := int64(5000)
start := time.Now()
// Simulate price calculations
for i := int64(0); i < iterations; i++ {
_ = float64(i) * 1.001 * 2000.0 / 1.0005
}
duration := time.Since(start)
return &BenchResult{
Name: "price_calculation",
Iterations: iterations,
NsPerOp: duration.Nanoseconds() / iterations,
}
}
func (pa *PerformanceAuditor) benchmarkSwapSimulation() *BenchResult {
iterations := int64(2000)
start := time.Now()
// Simulate swap calculations
for i := int64(0); i < iterations; i++ {
// Simplified swap simulation
amount := float64(i + 1)
reserve1 := 1000000.0
reserve2 := 2000000.0
_ = amount * reserve2 / (reserve1 + amount)
}
duration := time.Since(start)
return &BenchResult{
Name: "swap_simulation",
Iterations: iterations,
NsPerOp: duration.Nanoseconds() / iterations,
}
}
func (pa *PerformanceAuditor) benchmarkMemoryAllocation() *BenchResult {
iterations := int64(1000)
start := time.Now()
var allocs1, allocs2 runtime.MemStats
runtime.GC()
runtime.ReadMemStats(&allocs1)
for i := int64(0); i < iterations; i++ {
data := make([]byte, 1024)
_ = data
}
runtime.ReadMemStats(&allocs2)
duration := time.Since(start)
return &BenchResult{
Name: "memory_allocation",
Iterations: iterations,
NsPerOp: duration.Nanoseconds() / iterations,
AllocsPerOp: int64(allocs2.Mallocs-allocs1.Mallocs) / iterations,
BytesPerOp: int64(allocs2.TotalAlloc-allocs1.TotalAlloc) / iterations,
}
}
func (pa *PerformanceAuditor) calculateOverallScore() {
scores := make([]float64, 0)
// Throughput score
if pa.results.ThroughputResults != nil {
score := (pa.results.ThroughputResults.ActualTPS / pa.results.ThroughputResults.TargetTPS) * 100.0
if score > 100.0 {
score = 100.0
}
scores = append(scores, score)
}
// Latency score (lower is better, convert to score)
if pa.results.LatencyResults != nil {
// Good latency is < 10ms, acceptable is < 50ms
latencyScore := 100.0
if pa.results.LatencyResults.P95Latency > 50.0 {
latencyScore = 50.0
} else if pa.results.LatencyResults.P95Latency > 10.0 {
latencyScore = 100.0 - (pa.results.LatencyResults.P95Latency-10.0)*1.25
}
scores = append(scores, latencyScore)
}
// Memory score
if pa.results.MemoryResults != nil {
memoryScore := 100.0
if pa.results.MemoryResults.MemoryLeakDetected {
memoryScore -= 30.0
}
if pa.results.MemoryResults.MaxMemoryUsage > int64(pa.config.MaxMemoryMB) {
memoryScore -= 20.0
}
scores = append(scores, memoryScore)
}
// CPU score
if pa.results.CPUResults != nil {
cpuScore := 100.0
if pa.results.CPUResults.MaxCPUUsage > pa.config.CPUThreshold {
cpuScore = 100.0 - (pa.results.CPUResults.MaxCPUUsage-pa.config.CPUThreshold)*2.0
}
if cpuScore < 0 {
cpuScore = 0
}
scores = append(scores, cpuScore)
}
// Calculate overall score
if len(scores) > 0 {
total := 0.0
for _, score := range scores {
total += score
}
pa.results.OverallScore = total / float64(len(scores))
}
}
func (pa *PerformanceAuditor) analyzeThroughputPerformance() {
if pa.results.ThroughputResults == nil {
return
}
tr := pa.results.ThroughputResults
if tr.ActualTPS < tr.TargetTPS*0.8 {
pa.addPerformanceIssue("HIGH", "throughput",
fmt.Sprintf("Throughput %.1f TPS is significantly below target %.1f TPS", tr.ActualTPS, tr.TargetTPS),
"Reduced system capacity and potential revenue loss",
"Optimize bottlenecks in transaction processing pipeline")
}
if tr.SuccessRate < 95.0 {
pa.addPerformanceIssue("HIGH", "reliability",
fmt.Sprintf("Transaction success rate %.1f%% is below acceptable threshold", tr.SuccessRate),
"High failure rate indicates system instability",
"Investigate error patterns and improve error handling")
}
}
func (pa *PerformanceAuditor) analyzeLatencyPerformance() {
if pa.results.LatencyResults == nil {
return
}
lr := pa.results.LatencyResults
if lr.P95Latency > 100.0 {
pa.addPerformanceIssue("HIGH", "latency",
fmt.Sprintf("P95 latency %.2f ms exceeds acceptable threshold", lr.P95Latency),
"High latency reduces arbitrage opportunity capture rate",
"Optimize hot paths and reduce processing overhead")
}
if lr.P99Latency > 500.0 {
pa.addPerformanceIssue("CRITICAL", "latency",
fmt.Sprintf("P99 latency %.2f ms is extremely high", lr.P99Latency),
"Extreme latency spikes may cause missed opportunities",
"Investigate and eliminate latency spikes")
}
}
func (pa *PerformanceAuditor) analyzeMemoryPerformance() {
if pa.results.MemoryResults == nil {
return
}
mr := pa.results.MemoryResults
if mr.MaxMemoryUsage > int64(pa.config.MaxMemoryMB) {
pa.addPerformanceIssue("MEDIUM", "memory",
fmt.Sprintf("Memory usage %d MB exceeds configured limit %d MB", mr.MaxMemoryUsage, pa.config.MaxMemoryMB),
"High memory usage may lead to system instability",
"Optimize memory allocation patterns and implement memory pooling")
}
}
func (pa *PerformanceAuditor) analyzeCPUPerformance() {
if pa.results.CPUResults == nil {
return
}
cr := pa.results.CPUResults
if cr.MaxCPUUsage > pa.config.CPUThreshold {
pa.addPerformanceIssue("MEDIUM", "cpu",
fmt.Sprintf("CPU usage %.1f%% exceeds threshold %.1f%%", cr.MaxCPUUsage, pa.config.CPUThreshold),
"High CPU usage may limit system scalability",
"Optimize CPU-intensive operations and consider load balancing")
}
}
func (pa *PerformanceAuditor) analyzeStressPerformance() {
if pa.results.StressResults == nil {
return
}
sr := pa.results.StressResults
if sr.BreakingPoint > 0 && sr.BreakingPoint < float64(pa.config.TargetTPS)*2.0 {
pa.addPerformanceIssue("HIGH", "scalability",
fmt.Sprintf("System breaking point %.1f TPS is low", sr.BreakingPoint),
"Limited scalability may restrict growth potential",
"Improve system architecture for better scalability")
}
}
func (pa *PerformanceAuditor) analyzeBenchmarkPerformance() {
if pa.results.BenchmarkResults == nil {
return
}
// Analyze benchmark results for performance insights
if br := pa.results.BenchmarkResults.ArbitrageDetectionBench; br != nil {
if br.NsPerOp > 1000000 { // More than 1ms per operation
pa.addPerformanceIssue("MEDIUM", "performance",
"Arbitrage detection benchmark shows high per-operation latency",
"Slow arbitrage detection may miss time-sensitive opportunities",
"Optimize arbitrage detection algorithms")
}
}
}
func (pa *PerformanceAuditor) addPerformanceIssue(severity, category, description, impact, suggestion string) {
pa.mu.Lock()
defer pa.mu.Unlock()
pa.results.PerformanceIssues = append(pa.results.PerformanceIssues, PerformanceIssue{
Severity: severity,
Category: category,
Description: description,
Impact: impact,
Suggestion: suggestion,
Timestamp: time.Now(),
})
}
func (pa *PerformanceAuditor) generateRecommendations() {
if pa.results.OverallScore < 70.0 {
pa.results.Recommendations = append(pa.results.Recommendations,
"Overall performance score is below 70%. Consider comprehensive optimization.")
}
if pa.results.OverallScore >= 80.0 {
pa.results.Recommendations = append(pa.results.Recommendations,
"Good performance achieved. Focus on monitoring and gradual improvements.")
}
// Category-specific recommendations
if pa.results.ThroughputResults != nil && pa.results.ThroughputResults.ActualTPS < pa.results.ThroughputResults.TargetTPS*0.9 {
pa.results.Recommendations = append(pa.results.Recommendations,
"Throughput is below target. Consider parallel processing and pipeline optimization.")
}
if pa.results.LatencyResults != nil && pa.results.LatencyResults.P95Latency > 50.0 {
pa.results.Recommendations = append(pa.results.Recommendations,
"High latency detected. Optimize critical paths and consider caching strategies.")
}
if pa.results.MemoryResults != nil && pa.results.MemoryResults.MemoryLeakDetected {
pa.results.Recommendations = append(pa.results.Recommendations,
"Memory leak detected. Review memory allocation patterns and implement proper cleanup.")
}
}
func (pa *PerformanceAuditor) GenerateReport() error {
// Update system metrics
pa.results.SystemMetrics.EndTime = time.Now()
pa.results.SystemMetrics.TotalGoroutines = runtime.NumGoroutine()
// Sort performance issues by severity
sort.Slice(pa.results.PerformanceIssues, func(i, j int) bool {
severityOrder := map[string]int{"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3}
return severityOrder[pa.results.PerformanceIssues[i].Severity] < severityOrder[pa.results.PerformanceIssues[j].Severity]
})
// Generate JSON report
jsonReport, err := json.MarshalIndent(pa.results, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal results: %w", err)
}
// Save JSON report
timestamp := time.Now().Format("2006-01-02_15-04-05")
jsonPath := filepath.Join(pa.config.OutputDir, fmt.Sprintf("performance_audit_%s.json", timestamp))
if err := os.WriteFile(jsonPath, jsonReport, 0644); err != nil {
return fmt.Errorf("failed to write JSON report: %w", err)
}
// Generate summary report
summaryPath := filepath.Join(pa.config.OutputDir, fmt.Sprintf("performance_summary_%s.txt", timestamp))
if err := pa.generateSummaryReport(summaryPath); err != nil {
return fmt.Errorf("failed to generate summary report: %w", err)
}
if pa.config.Verbose {
fmt.Printf("Reports generated:\n")
fmt.Printf(" JSON: %s\n", jsonPath)
fmt.Printf(" Summary: %s\n", summaryPath)
}
return nil
}
func (pa *PerformanceAuditor) generateSummaryReport(filePath string) error {
summary := fmt.Sprintf(`Performance Audit Report
Generated: %s
Test Type: %s
Load Level: %s
Duration: %d ms
Overall Score: %.1f%%
ENVIRONMENT
===========
OS: %s
Architecture: %s
Go Version: %s
CPU Cores: %d
GOMAXPROCS: %d
`, pa.results.Timestamp.Format("2006-01-02 15:04:05"),
pa.results.TestType,
pa.results.LoadLevel,
pa.results.Duration,
pa.results.OverallScore,
pa.results.Environment.GOOS,
pa.results.Environment.GOARCH,
pa.results.Environment.GoVersion,
pa.results.Environment.NumCPU,
pa.results.Environment.GOMAXPROCS)
// Throughput results
if pa.results.ThroughputResults != nil {
tr := pa.results.ThroughputResults
summary += fmt.Sprintf(`THROUGHPUT RESULTS
==================
Target TPS: %.1f
Actual TPS: %.1f
Max TPS: %.1f
Success Rate: %.1f%%
Total Transactions: %d
`, tr.TargetTPS, tr.ActualTPS, tr.MaxTPS, tr.SuccessRate, tr.TotalTransactions)
}
// Latency results
if pa.results.LatencyResults != nil {
lr := pa.results.LatencyResults
summary += fmt.Sprintf(`LATENCY RESULTS
===============
Average: %.2f ms
Median: %.2f ms
P95: %.2f ms
P99: %.2f ms
Max: %.2f ms
`, lr.AverageLatency, lr.MedianLatency, lr.P95Latency, lr.P99Latency, lr.MaxLatency)
}
// Memory results
if pa.results.MemoryResults != nil {
mr := pa.results.MemoryResults
summary += fmt.Sprintf(`MEMORY RESULTS
==============
Max Usage: %d MB
Average Usage: %d MB
Memory Leak: %t
Heap Size: %d MB
`, mr.MaxMemoryUsage, mr.AverageMemoryUsage, mr.MemoryLeakDetected, mr.HeapSize)
}
// CPU results
if pa.results.CPUResults != nil {
cr := pa.results.CPUResults
summary += fmt.Sprintf(`CPU RESULTS
===========
Max Usage: %.1f%%
Average Usage: %.1f%%
CPU Cores: %d
`, cr.MaxCPUUsage, cr.AverageCPUUsage, cr.CPUCores)
}
// Performance issues
if len(pa.results.PerformanceIssues) > 0 {
summary += "\nPERFORMANCE ISSUES\n==================\n"
for i, issue := range pa.results.PerformanceIssues {
summary += fmt.Sprintf("%d. [%s] %s: %s\n", i+1, issue.Severity, issue.Category, issue.Description)
}
}
// Recommendations
if len(pa.results.Recommendations) > 0 {
summary += "\nRECOMMENDATIONS\n===============\n"
for i, rec := range pa.results.Recommendations {
summary += fmt.Sprintf("%d. %s\n", i+1, rec)
}
}
return os.WriteFile(filePath, []byte(summary), 0644)
}