- 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>
1385 lines
37 KiB
Go
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)
|
|
}
|