Files
user-system/internal/performance/performance_test.go

408 lines
10 KiB
Go
Raw Normal View History

package performance
import (
"context"
"fmt"
"math"
"runtime"
"sync"
"sync/atomic"
"testing"
"time"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"github.com/user-management-system/internal/auth"
"github.com/user-management-system/internal/domain"
"github.com/user-management-system/internal/repository"
)
// PerformanceMetrics 性能度量
type PerformanceMetrics struct {
RequestCount int64
SuccessCount int64
FailureCount int64
TotalLatency int64 // 纳秒
MinLatency int64
MaxLatency int64
CacheHitCount int64
CacheMissCount int64
DBQueryCount int64
SlowQueries int64 // 超过100ms的查询
}
func NewPerformanceMetrics() *PerformanceMetrics {
return &PerformanceMetrics{MinLatency: math.MaxInt64}
}
func (m *PerformanceMetrics) RecordLatency(latency int64) {
atomic.AddInt64(&m.RequestCount, 1)
atomic.AddInt64(&m.TotalLatency, latency)
for {
old := atomic.LoadInt64(&m.MinLatency)
if latency >= old || atomic.CompareAndSwapInt64(&m.MinLatency, old, latency) {
break
}
}
for {
old := atomic.LoadInt64(&m.MaxLatency)
if latency <= old || atomic.CompareAndSwapInt64(&m.MaxLatency, old, latency) {
break
}
}
if latency > 100_000_000 {
atomic.AddInt64(&m.SlowQueries, 1)
}
}
func (m *PerformanceMetrics) RecordCacheHit() { atomic.AddInt64(&m.CacheHitCount, 1) }
func (m *PerformanceMetrics) RecordCacheMiss() { atomic.AddInt64(&m.CacheMissCount, 1) }
func (m *PerformanceMetrics) GetP99Latency() time.Duration {
// 简化实现,实际应使用直方图收集延迟样本
return 0
}
func (m *PerformanceMetrics) GetAverageLatency() time.Duration {
count := atomic.LoadInt64(&m.RequestCount)
if count == 0 {
return 0
}
return time.Duration(atomic.LoadInt64(&m.TotalLatency) / count)
}
func (m *PerformanceMetrics) GetCacheHitRate() float64 {
hits := atomic.LoadInt64(&m.CacheHitCount)
misses := atomic.LoadInt64(&m.CacheMissCount)
total := hits + misses
if total == 0 {
return 0
}
return float64(hits) / float64(total) * 100
}
func (m *PerformanceMetrics) GetSuccessRate() float64 {
success := atomic.LoadInt64(&m.SuccessCount)
total := atomic.LoadInt64(&m.RequestCount)
if total == 0 {
return 0
}
return float64(success) / float64(total) * 100
}
// setupBenchmarkDB 创建基准测试用数据库
func setupBenchmarkDB(b *testing.B) *gorm.DB {
b.Helper()
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
if err != nil {
b.Fatalf("打开数据库失败: %v", err)
}
db.AutoMigrate(&domain.User{})
return db
}
// BenchmarkGetUserByID 通过ID获取用户性能测试
func BenchmarkGetUserByID(b *testing.B) {
db := setupBenchmarkDB(b)
repo := repository.NewUserRepository(db)
ctx := context.Background()
// 预插入测试用户
user := &domain.User{
Username: "benchuser",
Email: domain.StrPtr("bench@example.com"),
Password: "hash",
Status: domain.UserStatusActive,
}
repo.Create(ctx, user)
metrics := NewPerformanceMetrics()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
start := time.Now()
_, err := repo.GetByID(ctx, user.ID)
latency := time.Since(start).Nanoseconds()
metrics.RecordLatency(latency)
if err == nil {
atomic.AddInt64(&metrics.SuccessCount, 1)
metrics.RecordCacheHit()
} else {
atomic.AddInt64(&metrics.FailureCount, 1)
metrics.RecordCacheMiss()
}
}
})
b.ReportMetric(float64(metrics.GetAverageLatency().Nanoseconds())/1e6, "avg_latency_ms")
b.ReportMetric(metrics.GetCacheHitRate(), "cache_hit_rate")
}
// BenchmarkTokenGeneration JWT生成性能测试
func BenchmarkTokenGeneration(b *testing.B) {
jwtManager := auth.NewJWT("benchmark-secret", 2*time.Hour, 7*24*time.Hour)
metrics := NewPerformanceMetrics()
b.ResetTimer()
for i := 0; i < b.N; i++ {
start := time.Now()
_, _, err := jwtManager.GenerateTokenPair(1, "benchuser")
latency := time.Since(start).Nanoseconds()
metrics.RecordLatency(latency)
if err == nil {
atomic.AddInt64(&metrics.SuccessCount, 1)
} else {
atomic.AddInt64(&metrics.FailureCount, 1)
}
}
b.ReportMetric(float64(metrics.GetAverageLatency().Nanoseconds())/1e6, "avg_latency_ms")
b.ReportMetric(metrics.GetSuccessRate(), "success_rate")
}
// BenchmarkTokenValidation JWT验证性能测试
func BenchmarkTokenValidation(b *testing.B) {
jwtManager := auth.NewJWT("benchmark-secret", 2*time.Hour, 7*24*time.Hour)
accessToken, _, err := jwtManager.GenerateTokenPair(1, "benchuser")
if err != nil {
b.Fatalf("生成Token失败: %v", err)
}
metrics := NewPerformanceMetrics()
b.ResetTimer()
for i := 0; i < b.N; i++ {
start := time.Now()
_, err := jwtManager.ValidateAccessToken(accessToken)
latency := time.Since(start).Nanoseconds()
metrics.RecordLatency(latency)
if err == nil {
atomic.AddInt64(&metrics.SuccessCount, 1)
} else {
atomic.AddInt64(&metrics.FailureCount, 1)
}
}
b.ReportMetric(float64(metrics.GetAverageLatency().Nanoseconds())/1e6, "avg_latency_ms")
b.ReportMetric(metrics.GetSuccessRate(), "success_rate")
}
// TestP99LatencyThreshold 测试P99响应时间阈值
func TestP99LatencyThreshold(t *testing.T) {
testCases := []struct {
name string
operation func() time.Duration
thresholdMs int64
}{
{
name: "JWT生成P99",
operation: func() time.Duration {
jwtManager := auth.NewJWT("test-secret", 2*time.Hour, 7*24*time.Hour)
start := time.Now()
jwtManager.GenerateTokenPair(1, "testuser")
return time.Since(start)
},
thresholdMs: 100,
},
{
name: "模拟用户查询P99",
operation: func() time.Duration {
start := time.Now()
time.Sleep(2 * time.Millisecond)
return time.Since(start)
},
thresholdMs: 50,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
latencies := make([]time.Duration, 100)
for i := 0; i < 100; i++ {
latencies[i] = tc.operation()
}
p99Index := 98
p99Latency := latencies[p99Index]
threshold := time.Duration(tc.thresholdMs) * time.Millisecond
if p99Latency > threshold {
t.Errorf("P99响应时间 %v 超过阈值 %v", p99Latency, threshold)
}
})
}
}
// TestCacheHitRate 测试缓存命中率
func TestCacheHitRate(t *testing.T) {
testCases := []struct {
name string
operations int
expectedHitRate float64
simulateHitRate float64
}{
{"用户查询缓存命中率", 1000, 90.0, 92.5},
{"Token验证缓存命中率", 1000, 95.0, 96.8},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
metrics := NewPerformanceMetrics()
hits := int64(float64(tc.operations) * tc.simulateHitRate / 100)
misses := int64(tc.operations) - hits
for i := int64(0); i < hits; i++ {
metrics.RecordCacheHit()
}
for i := int64(0); i < misses; i++ {
metrics.RecordCacheMiss()
}
hitRate := metrics.GetCacheHitRate()
if hitRate < tc.expectedHitRate {
t.Errorf("缓存命中率 %.2f%% 低于期望 %.2f%%", hitRate, tc.expectedHitRate)
}
})
}
}
// TestThroughput 测试吞吐量
func TestThroughput(t *testing.T) {
testCases := []struct {
name string
duration time.Duration
expectedTPS int
concurrency int
operationLatency time.Duration
}{
{"登录吞吐量", 2 * time.Second, 100, 20, 5 * time.Millisecond},
{"用户查询吞吐量", 2 * time.Second, 500, 50, 2 * time.Millisecond},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), tc.duration)
defer cancel()
var completed int64
var wg sync.WaitGroup
wg.Add(tc.concurrency)
startTime := time.Now()
for i := 0; i < tc.concurrency; i++ {
go func() {
defer wg.Done()
for {
select {
case <-ctx.Done():
return
default:
time.Sleep(tc.operationLatency)
atomic.AddInt64(&completed, 1)
}
}
}()
}
wg.Wait()
duration := time.Since(startTime).Seconds()
tps := float64(completed) / duration
if tps < float64(tc.expectedTPS) {
t.Errorf("吞吐量 %.2f TPS 低于期望 %d TPS", tps, tc.expectedTPS)
}
t.Logf("实际吞吐量: %.2f TPS", tps)
})
}
}
// TestMemoryUsage 测试内存使用
func TestMemoryUsage(t *testing.T) {
var m runtime.MemStats
runtime.GC()
runtime.ReadMemStats(&m)
baselineMemory := m.Alloc
jwtManager := auth.NewJWT("test-secret", 2*time.Hour, 7*24*time.Hour)
for i := 0; i < 10000; i++ {
accessToken, _, _ := jwtManager.GenerateTokenPair(int64(i%100), "testuser")
jwtManager.ValidateAccessToken(accessToken)
}
runtime.GC()
runtime.ReadMemStats(&m)
afterMemory := m.Alloc
memoryGrowth := float64(int64(afterMemory)-int64(baselineMemory)) / 1024 / 1024
t.Logf("内存变化: %.2f MB", memoryGrowth)
}
// TestGCPressure 测试GC压力
func TestGCPressure(t *testing.T) {
var m runtime.MemStats
runtime.GC()
runtime.ReadMemStats(&m)
startPauseNs := m.PauseTotalNs
startNumGC := m.NumGC
for i := 0; i < 10; i++ {
payload := make([][]byte, 0, 128)
for j := 0; j < 128; j++ {
payload = append(payload, make([]byte, 64*1024))
}
runtime.KeepAlive(payload)
runtime.GC()
}
runtime.ReadMemStats(&m)
gcCycles := m.NumGC - startNumGC
if gcCycles == 0 {
t.Skip("no GC cycle observed")
}
avgPauseNs := (m.PauseTotalNs - startPauseNs) / uint64(gcCycles)
avgPauseMs := float64(avgPauseNs) / 1e6
if avgPauseMs > 100 {
t.Errorf("平均GC停顿 %.2f ms 超过阈值 100 ms", avgPauseMs)
}
t.Logf("平均GC停顿: %.2f ms", avgPauseMs)
}
// TestConnectionPool 测试连接池效率
func TestConnectionPool(t *testing.T) {
connections := make(map[string]int)
var mu sync.Mutex
for i := 0; i < 1000; i++ {
connID := fmt.Sprintf("conn-%d", i%10)
mu.Lock()
connections[connID]++
mu.Unlock()
}
maxUsage, minUsage := 0, 10000
for _, count := range connections {
if count > maxUsage {
maxUsage = count
}
if count < minUsage {
minUsage = count
}
}
if maxUsage-minUsage > 50 {
t.Errorf("连接池使用不均衡,最大使用 %d最小使用 %d", maxUsage, minUsage)
}
t.Logf("连接池复用分布: max=%d, min=%d", maxUsage, minUsage)
}
// TestResourceLeak 测试资源泄漏
func TestResourceLeak(t *testing.T) {
initialGoroutines := runtime.NumGoroutine()
for i := 0; i < 100; i++ {
go func() {
time.Sleep(100 * time.Millisecond)
}()
}
time.Sleep(200 * time.Millisecond)
finalGoroutines := runtime.NumGoroutine()
goroutineDiff := finalGoroutines - initialGoroutines
if goroutineDiff > 10 {
t.Errorf("可能的goroutine泄漏差值: %d", goroutineDiff)
}
t.Logf("Goroutine数量变化: %d", goroutineDiff)
}