xfy f145a8770e refactor: modernize code with Go 1.22+ features
Apply modern Go patterns across the codebase:
- Replace `interface{}` with `any` (Go 1.18+)
- Use `for range n` instead of `for i := 0; i < n; i++` (Go 1.22+)
- Replace `sort.Slice` with `slices.Sort` from slices package
- Simplify sync.WaitGroup patterns with errgroup where appropriate
- Add Makefile targets for modernize analyzer

Total: 84 files updated, net reduction of 79 lines

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 10:37:45 +08:00

161 lines
3.8 KiB
Go

// Package tools provides testing utilities and mock infrastructure for benchmark tests.
package tools
import (
"math"
"slices"
"sync"
"testing"
"time"
"github.com/valyala/fasthttp"
)
// FasthttpLoadGenerator is a load generator using fasthttp client.
type FasthttpLoadGenerator struct {
client *fasthttp.HostClient
addr string
stats LoadGenStats
mu sync.Mutex
}
// LoadGenStats contains load generator statistics.
type LoadGenStats struct {
Latencies []time.Duration // For percentile calculation
TotalRequests int
SuccessCount int
ErrorCount int
TotalDuration time.Duration
MinLatency time.Duration
MaxLatency time.Duration
MeanLatency time.Duration
P50Latency time.Duration
P90Latency time.Duration
P99Latency time.Duration
QPS float64
}
// NewFasthttpLoadGenerator creates a new load generator for the given address.
func NewFasthttpLoadGenerator(addr string) *FasthttpLoadGenerator {
return &FasthttpLoadGenerator{
client: &fasthttp.HostClient{
Addr: addr,
MaxConns: 1000,
},
addr: addr,
stats: LoadGenStats{
MinLatency: time.Duration(math.MaxInt64),
},
}
}
// Run executes a load test with n requests using the specified concurrency.
// Returns collected statistics.
func (lg *FasthttpLoadGenerator) Run(n int, concurrency int) *LoadGenStats {
var wg sync.WaitGroup
requestsPerWorker := n / concurrency
// Channels for collecting metrics
latencyChan := make(chan time.Duration, n)
errorChan := make(chan error, n)
start := time.Now()
for range concurrency {
wg.Go(func() {
req := fasthttp.AcquireRequest()
resp := fasthttp.AcquireResponse()
defer fasthttp.ReleaseRequest(req)
defer fasthttp.ReleaseResponse(resp)
for range requestsPerWorker {
req.SetRequestURI("http://" + lg.addr + "/")
req.Header.SetMethod("GET")
reqStart := time.Now()
err := lg.client.Do(req, resp)
latency := time.Since(reqStart)
latencyChan <- latency
if err != nil {
errorChan <- err
}
}
})
}
wg.Wait()
close(latencyChan)
close(errorChan)
totalDuration := time.Since(start)
// Collect latencies
latencies := make([]time.Duration, 0, n)
for lat := range latencyChan {
latencies = append(latencies, lat)
}
// Count errors
errorCount := 0
for err := range errorChan {
_ = err // Error recorded, used for counting
errorCount++
}
// Calculate statistics
lg.mu.Lock()
defer lg.mu.Unlock()
lg.stats.TotalRequests = n
lg.stats.ErrorCount = errorCount
lg.stats.SuccessCount = n - errorCount
lg.stats.TotalDuration = totalDuration
lg.stats.QPS = float64(n) / totalDuration.Seconds()
lg.stats.Latencies = latencies
// Calculate latency distribution
if len(latencies) > 0 {
slices.Sort(latencies)
lg.stats.MinLatency = latencies[0]
lg.stats.MaxLatency = latencies[len(latencies)-1]
// Calculate mean
var sum time.Duration
for _, l := range latencies {
sum += l
}
lg.stats.MeanLatency = sum / time.Duration(len(latencies))
// Calculate percentiles
lg.stats.P50Latency = latencies[len(latencies)*50/100]
lg.stats.P90Latency = latencies[len(latencies)*90/100]
lg.stats.P99Latency = latencies[len(latencies)*99/100]
}
return &lg.stats
}
// GetStats returns current statistics without running a test.
func (lg *FasthttpLoadGenerator) GetStats() *LoadGenStats {
lg.mu.Lock()
defer lg.mu.Unlock()
return &lg.stats
}
// RunParallel runs requests in parallel using testing.PB.
// This is designed for use with Go benchmark functions.
func (lg *FasthttpLoadGenerator) RunParallel(pb *testing.PB) {
req := fasthttp.AcquireRequest()
resp := fasthttp.AcquireResponse()
defer fasthttp.ReleaseRequest(req)
defer fasthttp.ReleaseResponse(resp)
for pb.Next() {
req.SetRequestURI("http://" + lg.addr + "/")
req.Header.SetMethod("GET")
_ = lg.client.Do(req, resp)
}
}