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

408 lines
10 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}