From 25bdba4e01feb16733ec708dcfb5cade13ef1a23 Mon Sep 17 00:00:00 2001 From: xfy Date: Wed, 8 Apr 2026 18:25:38 +0800 Subject: [PATCH] =?UTF-8?q?test(benchmark):=20=E6=96=B0=E5=A2=9E=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E7=BA=A7=E5=9F=BA=E5=87=86=E6=B5=8B=E8=AF=95=E5=A5=97?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 benchmark_context.go 标准化测试上下文构造器 - 新增静态文件处理器基准测试(缓存命中/未命中、try_files) - 新增访问日志中间件基准测试 - 新增压缩中间件基准测试(gzip/brotli、Pool 复用) - 新增限流器基准测试(令牌桶、滑动窗口、多客户端并发) - 新增变量展开基准测试(模板展开、Pool 操作) --- internal/benchmark/tools/benchmark_context.go | 359 ++++++++++++++++++ internal/handler/static_bench_test.go | 213 +++++++++++ .../accesslog/accesslog_bench_test.go | 70 ++++ .../compression/compression_bench_test.go | 278 ++++++++++++++ .../security/ratelimit_bench_test.go | 228 +++++++++++ .../security/sliding_window_bench_test.go | 142 +++++++ internal/variable/variable_bench_test.go | 212 +++++++++++ 7 files changed, 1502 insertions(+) create mode 100644 internal/benchmark/tools/benchmark_context.go create mode 100644 internal/handler/static_bench_test.go create mode 100644 internal/middleware/accesslog/accesslog_bench_test.go create mode 100644 internal/middleware/compression/compression_bench_test.go create mode 100644 internal/middleware/security/ratelimit_bench_test.go create mode 100644 internal/middleware/security/sliding_window_bench_test.go create mode 100644 internal/variable/variable_bench_test.go diff --git a/internal/benchmark/tools/benchmark_context.go b/internal/benchmark/tools/benchmark_context.go new file mode 100644 index 0000000..a6323b9 --- /dev/null +++ b/internal/benchmark/tools/benchmark_context.go @@ -0,0 +1,359 @@ +// Package tools 提供基准测试工具函数。 +// +// 该文件提供标准化的基准测试上下文构造器,用于: +// - 快速创建模拟的 fasthttp.RequestCtx +// - 标准化测试数据大小 +// - 简化组件级基准测试编写 +// +// 作者:xfy +package tools + +import ( + "bytes" + "math/rand" + "net" + "strconv" + "time" + + "github.com/valyala/fasthttp" +) + +// BenchmarkContext 提供标准化的基准测试上下文。 +// +// 包含构造模拟请求所需的所有配置。 +type BenchmarkContext struct { + // RequestSize 请求数据大小 + RequestSize TestDataSize + + // ResponseSize 响应数据大小 + ResponseSize TestDataSize + + // Concurrency 并发级别(用于并行测试) + Concurrency int + + // randSrc 随机数生成器 + randSrc *rand.Rand +} + +// NewBenchmarkContext 创建基准测试上下文。 +// +// 参数: +// - reqSize: 请求数据大小 +// - respSize: 响应数据大小 +// - concurrency: 并发级别 +// +// 返回值: +// - *BenchmarkContext: 配置好的基准测试上下文 +func NewBenchmarkContext(reqSize, respSize TestDataSize, concurrency int) *BenchmarkContext { + return &BenchmarkContext{ + RequestSize: reqSize, + ResponseSize: respSize, + Concurrency: concurrency, + randSrc: rand.New(rand.NewSource(time.Now().UnixNano())), + } +} + +// MockRequestCtx 构造模拟的 fasthttp.RequestCtx。 +// +// 创建一个可用于基准测试的请求上下文,包含: +// - 请求方法和路径 +// - 请求头 +// - 请求体 +// - 远程地址 +// +// 参数: +// - method: HTTP 方法 (GET, POST, etc.) +// - path: 请求路径 +// +// 返回值: +// - *fasthttp.RequestCtx: 模拟的请求上下文 +func (bc *BenchmarkContext) MockRequestCtx(method, path string) *fasthttp.RequestCtx { + ctx := &fasthttp.RequestCtx{} + + // 设置请求行 + ctx.Request.Header.SetMethod(method) + ctx.Request.SetRequestURI(path) + + // 设置远程地址 + ctx.Init(&fasthttp.Request{}, &net.TCPAddr{ + IP: net.ParseIP("192.168.1.100"), + Port: 12345, + }, nil) + + // 设置请求体 + if bc.RequestSize > 0 { + body := GenerateTestData(bc.RequestSize) + ctx.Request.SetBody(body) + } + + return ctx +} + +// MockRequestCtxWithHeaders 构造带自定义请求头的模拟上下文。 +// +// 参数: +// - method: HTTP 方法 +// - path: 请求路径 +// - headers: 请求头键值对 +// +// 返回值: +// - *fasthttp.RequestCtx: 模拟的请求上下文 +func (bc *BenchmarkContext) MockRequestCtxWithHeaders(method, path string, headers map[string]string) *fasthttp.RequestCtx { + ctx := bc.MockRequestCtx(method, path) + + // 设置请求头 + for key, value := range headers { + ctx.Request.Header.Set(key, value) + } + + return ctx +} + +// MockRequestCtxWithBody 构造带自定义请求体的模拟上下文。 +// +// 参数: +// - method: HTTP 方法 +// - path: 请求路径 +// - body: 请求体内容 +// +// 返回值: +// - *fasthttp.RequestCtx: 模拟的请求上下文 +func (bc *BenchmarkContext) MockRequestCtxWithBody(method, path string, body []byte) *fasthttp.RequestCtx { + ctx := bc.MockRequestCtx(method, path) + ctx.Request.SetBody(body) + return ctx +} + +// MockRequestCtxWithIP 构造带指定 IP 的模拟上下文。 +// +// 参数: +// - method: HTTP 方法 +// - path: 请求路径 +// - ip: 客户端 IP 地址 +// +// 返回值: +// - *fasthttp.RequestCtx: 模拟的请求上下文 +func (bc *BenchmarkContext) MockRequestCtxWithIP(method, path, ip string) *fasthttp.RequestCtx { + ctx := &fasthttp.RequestCtx{} + + ctx.Request.Header.SetMethod(method) + ctx.Request.SetRequestURI(path) + + ctx.Init(&fasthttp.Request{}, &net.TCPAddr{ + IP: net.ParseIP(ip), + Port: 12345, + }, nil) + + return ctx +} + +// MockResponse 构造模拟响应数据。 +// +// 根据 ResponseSize 生成响应体。 +// +// 返回值: +// - []byte: 模拟的响应数据 +func (bc *BenchmarkContext) MockResponse() []byte { + return GenerateTestData(bc.ResponseSize) +} + +// MockResponseWithContentType 构造带 Content-Type 的模拟响应。 +// +// 参数: +// - statusCode: HTTP 状态码 +// - contentType: 内容类型 +// +// 返回值: +// - []byte: 模拟的响应数据 +func (bc *BenchmarkContext) MockResponseWithContentType(statusCode int, contentType string) []byte { + // 返回响应体,调用者负责设置状态码和 Content-Type + return GenerateTestData(bc.ResponseSize) +} + +// RandomIP 生成随机 IP 地址。 +// +// 用于测试多客户端场景。 +// +// 返回值: +// - string: 随机 IP 地址 +func (bc *BenchmarkContext) RandomIP() string { + return "192.168." + strconv.Itoa(bc.randSrc.Intn(256)) + "." + strconv.Itoa(bc.randSrc.Intn(256)) +} + +// RandomPath 生成随机请求路径。 +// +// 参数: +// - prefix: 路径前缀 +// +// 返回值: +// - string: 随机路径 +func (bc *BenchmarkContext) RandomPath(prefix string) string { + return prefix + "/" + strconv.Itoa(bc.randSrc.Intn(10000)) +} + +// MockJSONRequest 构造 JSON 格式的模拟请求。 +// +// 参数: +// - path: 请求路径 +// - data: JSON 数据 +// +// 返回值: +// - *fasthttp.RequestCtx: 模拟的请求上下文 +func (bc *BenchmarkContext) MockJSONRequest(path string, data []byte) *fasthttp.RequestCtx { + ctx := bc.MockRequestCtx("POST", path) + ctx.Request.Header.SetContentType("application/json") + ctx.Request.SetBody(data) + return ctx +} + +// MockFormRequest 构造表单格式的模拟请求。 +// +// 参数: +// - path: 请求路径 +// - form: 表单数据键值对 +// +// 返回值: +// - *fasthttp.RequestCtx: 模拟的请求上下文 +func (bc *BenchmarkContext) MockFormRequest(path string, form map[string]string) *fasthttp.RequestCtx { + ctx := bc.MockRequestCtx("POST", path) + ctx.Request.Header.SetContentType("application/x-www-form-urlencoded") + + // 构造表单数据 + var buf bytes.Buffer + first := true + for key, value := range form { + if !first { + buf.WriteByte('&') + } + buf.WriteString(key) + buf.WriteByte('=') + buf.WriteString(value) + first = false + } + ctx.Request.SetBody(buf.Bytes()) + + return ctx +} + +// SetResponse 设置模拟上下文的响应。 +// +// 用于测试响应处理逻辑。 +// +// 参数: +// - ctx: 请求上下文 +// - statusCode: HTTP 状态码 +// - body: 响应体 +func (bc *BenchmarkContext) SetResponse(ctx *fasthttp.RequestCtx, statusCode int, body []byte) { + ctx.Response.SetStatusCode(statusCode) + ctx.Response.SetBody(body) +} + +// SetJSONResponse 设置 JSON 格式的模拟响应。 +// +// 参数: +// - ctx: 请求上下文 +// - statusCode: HTTP 状态码 +// - json: JSON 数据 +func (bc *BenchmarkContext) SetJSONResponse(ctx *fasthttp.RequestCtx, statusCode int, json []byte) { + ctx.Response.SetStatusCode(statusCode) + ctx.Response.Header.SetContentType("application/json") + ctx.Response.SetBody(json) +} + +// DefaultBenchmarkContext 返回默认的基准测试上下文。 +// +// 配置: +// - RequestSize: 1KB +// - ResponseSize: 1KB +// - Concurrency: 1 +// +// 返回值: +// - *BenchmarkContext: 默认配置的上下文 +func DefaultBenchmarkContext() *BenchmarkContext { + return NewBenchmarkContext(Size1KB, Size1KB, 1) +} + +// SmallRequestContext 返回小请求的基准测试上下文。 +// +// 配置: +// - RequestSize: 100B +// - ResponseSize: 100B +// +// 返回值: +// - *BenchmarkContext: 小请求配置的上下文 +func SmallRequestContext() *BenchmarkContext { + return NewBenchmarkContext(100, 100, 1) +} + +// LargeRequestContext 返回大请求的基准测试上下文。 +// +// 配置: +// - RequestSize: 100KB +// - ResponseSize: 100KB +// +// 返回值: +// - *BenchmarkContext: 大请求配置的上下文 +func LargeRequestContext() *BenchmarkContext { + return NewBenchmarkContext(Size100KB, Size100KB, 1) +} + +// HighConcurrencyContext 返回高并发基准测试上下文。 +// +// 配置: +// - Concurrency: 100 +// +// 返回值: +// - *BenchmarkContext: 高并发配置的上下文 +func HighConcurrencyContext() *BenchmarkContext { + return NewBenchmarkContext(Size1KB, Size1KB, 100) +} + +// BenchmarkContextPool 提供基准测试上下文的池。 +// +// 用于减少内存分配开销。 +type BenchmarkContextPool struct { + pool chan *BenchmarkContext +} + +// NewBenchmarkContextPool 创建基准测试上下文池。 +// +// 参数: +// - size: 池大小 +// +// 返回值: +// - *BenchmarkContextPool: 上下文池 +func NewBenchmarkContextPool(size int) *BenchmarkContextPool { + p := &BenchmarkContextPool{ + pool: make(chan *BenchmarkContext, size), + } + // 预填充池 + for i := 0; i < size; i++ { + p.pool <- DefaultBenchmarkContext() + } + return p +} + +// Get 从池中获取基准测试上下文。 +// +// 返回值: +// - *BenchmarkContext: 基准测试上下文 +func (p *BenchmarkContextPool) Get() *BenchmarkContext { + select { + case ctx := <-p.pool: + return ctx + default: + return DefaultBenchmarkContext() + } +} + +// Put 将基准测试上下文放回池中。 +// +// 参数: +// - ctx: 基准测试上下文 +func (p *BenchmarkContextPool) Put(ctx *BenchmarkContext) { + select { + case p.pool <- ctx: + default: + // 池已满,丢弃 + } +} \ No newline at end of file diff --git a/internal/handler/static_bench_test.go b/internal/handler/static_bench_test.go new file mode 100644 index 0000000..dc4c69d --- /dev/null +++ b/internal/handler/static_bench_test.go @@ -0,0 +1,213 @@ +// Package handler 提供静态文件处理器的基准测试。 +// +// 该文件测试文件查找、缓存命中/未命中、try_files 等场景的性能。 +// +// 作者:xfy +package handler + +import ( + "os" + "path/filepath" + "testing" + + "github.com/valyala/fasthttp" + "rua.plus/lolly/internal/cache" +) + +// setupStaticTestDir 创建临时测试目录。 +func setupStaticTestDir() (string, func()) { + dir, err := os.MkdirTemp("", "static_bench_*") + if err != nil { + panic(err) + } + + // 创建测试文件 + testFiles := map[string][]byte{ + "index.html": []byte("Index"), + "style.css": make([]byte, 1024), // 1KB + "large.json": make([]byte, 10*1024), // 10KB + "nested/file.js": make([]byte, 5*1024), // 5KB + } + + for path, content := range testFiles { + fullPath := filepath.Join(dir, path) + if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil { + panic(err) + } + if err := os.WriteFile(fullPath, content, 0644); err != nil { + panic(err) + } + } + + cleanup := func() { + os.RemoveAll(dir) + } + + return dir, cleanup +} + +// BenchmarkStaticFileLookup 测试文件路径查找性能。 +func BenchmarkStaticFileLookup(b *testing.B) { + dir, cleanup := setupStaticTestDir() + defer cleanup() + + handler := NewStaticHandler(dir, "/", []string{"index.html"}, false) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + ctx := &fasthttp.RequestCtx{} + ctx.Request.SetRequestURI("/style.css") + handler.Handle(ctx) + } +} + +// BenchmarkStaticFileCacheHit 测试缓存命中场景性能。 +func BenchmarkStaticFileCacheHit(b *testing.B) { + dir, cleanup := setupStaticTestDir() + defer cleanup() + + fc := cache.NewFileCache(1000, 10*1024*1024, 0) // 1000 文件或 10MB + handler := NewStaticHandler(dir, "/", []string{"index.html"}, false) + handler.SetFileCache(fc) + + // 预热缓存 + ctx := &fasthttp.RequestCtx{} + ctx.Request.SetRequestURI("/style.css") + handler.Handle(ctx) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + ctx := &fasthttp.RequestCtx{} + ctx.Request.SetRequestURI("/style.css") + handler.Handle(ctx) + } +} + +// BenchmarkStaticFileCacheMiss_1KB 测试 1KB 文件缓存未命中场景性能。 +func BenchmarkStaticFileCacheMiss_1KB(b *testing.B) { + dir, cleanup := setupStaticTestDir() + defer cleanup() + + handler := NewStaticHandler(dir, "/", []string{"index.html"}, false) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + ctx := &fasthttp.RequestCtx{} + ctx.Request.SetRequestURI("/style.css") + handler.Handle(ctx) + } +} + +// BenchmarkStaticFileCacheMiss_10KB 测试 10KB 文件缓存未命中场景性能。 +func BenchmarkStaticFileCacheMiss_10KB(b *testing.B) { + dir, cleanup := setupStaticTestDir() + defer cleanup() + + handler := NewStaticHandler(dir, "/", []string{"index.html"}, false) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + ctx := &fasthttp.RequestCtx{} + ctx.Request.SetRequestURI("/large.json") + handler.Handle(ctx) + } +} + +// BenchmarkStaticTryFiles 测试 try_files 查找性能。 +func BenchmarkStaticTryFiles(b *testing.B) { + dir, cleanup := setupStaticTestDir() + defer cleanup() + + handler := NewStaticHandler(dir, "/", []string{"index.html"}, false) + handler.SetTryFiles([]string{"$uri", "$uri/", "/index.html"}, false, nil) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + ctx := &fasthttp.RequestCtx{} + ctx.Request.SetRequestURI("/nonexistent/path") + handler.Handle(ctx) + } +} + +// BenchmarkStaticIndex 测试索引文件查找性能。 +func BenchmarkStaticIndex(b *testing.B) { + dir, cleanup := setupStaticTestDir() + defer cleanup() + + handler := NewStaticHandler(dir, "/", []string{"index.html", "index.htm"}, false) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + ctx := &fasthttp.RequestCtx{} + ctx.Request.SetRequestURI("/") + handler.Handle(ctx) + } +} + +// BenchmarkStaticNestedFile 测试嵌套文件查找性能。 +func BenchmarkStaticNestedFile(b *testing.B) { + dir, cleanup := setupStaticTestDir() + defer cleanup() + + handler := NewStaticHandler(dir, "/", []string{"index.html"}, false) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + ctx := &fasthttp.RequestCtx{} + ctx.Request.SetRequestURI("/nested/file.js") + handler.Handle(ctx) + } +} + +// BenchmarkStaticFileNotFound 测试文件未找到场景性能。 +func BenchmarkStaticFileNotFound(b *testing.B) { + dir, cleanup := setupStaticTestDir() + defer cleanup() + + handler := NewStaticHandler(dir, "/", []string{"index.html"}, false) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + ctx := &fasthttp.RequestCtx{} + ctx.Request.SetRequestURI("/nonexistent/file.txt") + handler.Handle(ctx) + } +} + +// BenchmarkStaticWithCacheParallel 测试带缓存的并发访问性能。 +func BenchmarkStaticWithCacheParallel(b *testing.B) { + dir, cleanup := setupStaticTestDir() + defer cleanup() + + fc := cache.NewFileCache(1000, 10*1024*1024, 0) + handler := NewStaticHandler(dir, "/", []string{"index.html"}, false) + handler.SetFileCache(fc) + + paths := []string{"/style.css", "/large.json", "/nested/file.js", "/index.html"} + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + ctx := &fasthttp.RequestCtx{} + ctx.Request.SetRequestURI(paths[i%len(paths)]) + handler.Handle(ctx) + i++ + } + }) +} + +// BenchmarkStaticFileLookupWithAlias 测试 alias 模式下的文件查找性能。 +func BenchmarkStaticFileLookupWithAlias(b *testing.B) { + dir, cleanup := setupStaticTestDir() + defer cleanup() + + handler := NewStaticHandlerWithAlias(dir+"/", "/static/", []string{"index.html"}, false) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + ctx := &fasthttp.RequestCtx{} + ctx.Request.SetRequestURI("/static/style.css") + handler.Handle(ctx) + } +} \ No newline at end of file diff --git a/internal/middleware/accesslog/accesslog_bench_test.go b/internal/middleware/accesslog/accesslog_bench_test.go new file mode 100644 index 0000000..13acdea --- /dev/null +++ b/internal/middleware/accesslog/accesslog_bench_test.go @@ -0,0 +1,70 @@ +// Package accesslog 提供访问日志中间件的基准测试。 +// +// 该文件测试日志记录的性能开销。 +// +// 作者:xfy +package accesslog + +import ( + "testing" + + "github.com/valyala/fasthttp" + "rua.plus/lolly/internal/config" +) + +// BenchmarkAccessLogProcess 测试访问日志中间件处理性能。 +func BenchmarkAccessLogProcess(b *testing.B) { + cfg := &config.LoggingConfig{ + Access: config.AccessLogConfig{ + Path: "/dev/null", + Format: "combined", + }, + } + al := New(cfg) + defer al.Close() + + mockHandler := func(ctx *fasthttp.RequestCtx) { + ctx.SetStatusCode(fasthttp.StatusOK) + ctx.SetBodyString("Hello, World!") + } + + handler := al.Process(mockHandler) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + ctx := &fasthttp.RequestCtx{} + ctx.Request.Header.SetMethod(fasthttp.MethodGet) + ctx.Request.SetRequestURI("/api/test") + ctx.Request.Header.SetHost("example.com") + handler(ctx) + } +} + +// BenchmarkAccessLogProcessParallel 测试并发场景下的访问日志性能。 +func BenchmarkAccessLogProcessParallel(b *testing.B) { + cfg := &config.LoggingConfig{ + Access: config.AccessLogConfig{ + Path: "/dev/null", + Format: "combined", + }, + } + al := New(cfg) + defer al.Close() + + mockHandler := func(ctx *fasthttp.RequestCtx) { + ctx.SetStatusCode(fasthttp.StatusOK) + ctx.SetBodyString("OK") + } + + handler := al.Process(mockHandler) + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + ctx := &fasthttp.RequestCtx{} + ctx.Request.Header.SetMethod(fasthttp.MethodGet) + ctx.Request.SetRequestURI("/api/test") + handler(ctx) + } + }) +} \ No newline at end of file diff --git a/internal/middleware/compression/compression_bench_test.go b/internal/middleware/compression/compression_bench_test.go new file mode 100644 index 0000000..71e3cea --- /dev/null +++ b/internal/middleware/compression/compression_bench_test.go @@ -0,0 +1,278 @@ +// Package compression 提供压缩中间件的基准测试。 +// +// 该文件测试 gzip/brotli 压缩性能、Pool 复用效率和组件级性能。 +// +// 作者:xfy +package compression + +import ( + "testing" + + "github.com/valyala/fasthttp" + "rua.plus/lolly/internal/benchmark/tools" + "rua.plus/lolly/internal/config" +) + +// BenchmarkGzipCompress_1KB 测试 gzip 压缩 1KB 数据的性能。 +func BenchmarkGzipCompress_1KB(b *testing.B) { + cfg := &config.CompressionConfig{ + Type: "gzip", + Level: 6, + MinSize: 100, + Types: []string{"text/html", "application/json"}, + } + mw, _ := New(cfg) + + data := tools.GenerateTestData(tools.Size1KB) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + mw.compressGzip(data) + } +} + +// BenchmarkGzipCompress_10KB 测试 gzip 压缩 10KB 数据的性能。 +func BenchmarkGzipCompress_10KB(b *testing.B) { + cfg := &config.CompressionConfig{ + Type: "gzip", + Level: 6, + MinSize: 100, + } + mw, _ := New(cfg) + + data := tools.GenerateTestData(tools.Size10KB) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + mw.compressGzip(data) + } +} + +// BenchmarkGzipCompress_100KB 测试 gzip 压缩 100KB 数据的性能。 +func BenchmarkGzipCompress_100KB(b *testing.B) { + cfg := &config.CompressionConfig{ + Type: "gzip", + Level: 6, + MinSize: 100, + } + mw, _ := New(cfg) + + data := tools.GenerateTestData(tools.Size100KB) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + mw.compressGzip(data) + } +} + +// BenchmarkBrotliCompress_1KB 测试 brotli 压缩 1KB 数据的性能。 +func BenchmarkBrotliCompress_1KB(b *testing.B) { + cfg := &config.CompressionConfig{ + Type: "brotli", + Level: 6, + MinSize: 100, + } + mw, _ := New(cfg) + + data := tools.GenerateTestData(tools.Size1KB) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + mw.compressBrotli(data) + } +} + +// BenchmarkBrotliCompress_10KB 测试 brotli 压缩 10KB 数据的性能。 +func BenchmarkBrotliCompress_10KB(b *testing.B) { + cfg := &config.CompressionConfig{ + Type: "brotli", + Level: 6, + MinSize: 100, + } + mw, _ := New(cfg) + + data := tools.GenerateTestData(tools.Size10KB) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + mw.compressBrotli(data) + } +} + +// BenchmarkCompressionPool 测试压缩 Pool 复用效率。 +// +// 模拟实际使用场景,反复从 Pool 获取和归还压缩器。 +func BenchmarkCompressionPool(b *testing.B) { + cfg := &config.CompressionConfig{ + Type: "gzip", + Level: 6, + MinSize: 100, + } + mw, _ := New(cfg) + + data := tools.GenerateTestData(tools.Size1KB) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + mw.compressGzip(data) + } +} + +// BenchmarkCompressionMiddleware 组件级测试:测量压缩中间件本身的开销。 +// +// 使用 mockHandler 排除下游处理的影响。 +func BenchmarkCompressionMiddleware(b *testing.B) { + cfg := &config.CompressionConfig{ + Type: "gzip", + Level: 6, + MinSize: 100, + Types: []string{ + "text/html", "text/css", "application/json", + }, + } + mw, _ := New(cfg) + + // 创建 10KB 响应数据 + responseBody := tools.GenerateTestData(tools.Size10KB) + + // Mock handler 返回固定响应 + mockHandler := func(ctx *fasthttp.RequestCtx) { + ctx.SetStatusCode(fasthttp.StatusOK) + ctx.SetContentType("application/json") + ctx.SetBody(responseBody) + } + + handler := mw.Process(mockHandler) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + ctx := &fasthttp.RequestCtx{} + ctx.Request.Header.SetMethod(fasthttp.MethodGet) + ctx.Request.SetRequestURI("/api/test") + ctx.Request.Header.Set("Accept-Encoding", "gzip") + handler(ctx) + } +} + +// BenchmarkCompressionMiddlewareNoCompress 测试无需压缩场景的性能开销。 +// +// 当客户端不支持压缩时,中间件应几乎无额外开销。 +func BenchmarkCompressionMiddlewareNoCompress(b *testing.B) { + cfg := &config.CompressionConfig{ + Type: "gzip", + Level: 6, + MinSize: 100, + } + mw, _ := New(cfg) + + responseBody := tools.GenerateTestData(tools.Size10KB) + + mockHandler := func(ctx *fasthttp.RequestCtx) { + ctx.SetStatusCode(fasthttp.StatusOK) + ctx.SetContentType("application/json") + ctx.SetBody(responseBody) + } + + handler := mw.Process(mockHandler) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + ctx := &fasthttp.RequestCtx{} + ctx.Request.Header.SetMethod(fasthttp.MethodGet) + ctx.Request.SetRequestURI("/api/test") + // 不设置 Accept-Encoding 头 + handler(ctx) + } +} + +// BenchmarkIsCompressible 测试 MIME 类型检查性能。 +func BenchmarkIsCompressible(b *testing.B) { + cfg := &config.CompressionConfig{ + Type: "gzip", + Level: 6, + Types: []string{ + "text/html", "text/css", "text/javascript", + "application/json", "application/javascript", + }, + } + mw, _ := New(cfg) + + contentTypes := []string{ + "application/json", + "text/html; charset=utf-8", + "image/png", + "application/octet-stream", + "text/css", + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, ct := range contentTypes { + mw.isCompressible(ct) + } + } +} + +// BenchmarkCompressionLevelComparison 比较不同压缩级别的性能。 +func BenchmarkCompressionLevelComparison(b *testing.B) { + data := tools.GenerateTestData(tools.Size10KB) + + b.Run("Level1", func(b *testing.B) { + cfg := &config.CompressionConfig{Type: "gzip", Level: 1} + mw, _ := New(cfg) + b.ResetTimer() + for i := 0; i < b.N; i++ { + mw.compressGzip(data) + } + }) + + b.Run("Level6", func(b *testing.B) { + cfg := &config.CompressionConfig{Type: "gzip", Level: 6} + mw, _ := New(cfg) + b.ResetTimer() + for i := 0; i < b.N; i++ { + mw.compressGzip(data) + } + }) + + b.Run("Level9", func(b *testing.B) { + cfg := &config.CompressionConfig{Type: "gzip", Level: 9} + mw, _ := New(cfg) + b.ResetTimer() + for i := 0; i < b.N; i++ { + mw.compressGzip(data) + } + }) +} + +// BenchmarkCompressionMiddlewareParallel 测试并发场景下的中间件性能。 +func BenchmarkCompressionMiddlewareParallel(b *testing.B) { + cfg := &config.CompressionConfig{ + Type: "gzip", + Level: 6, + MinSize: 100, + Types: []string{"application/json"}, + } + mw, _ := New(cfg) + + responseBody := tools.GenerateTestData(tools.Size10KB) + + mockHandler := func(ctx *fasthttp.RequestCtx) { + ctx.SetStatusCode(fasthttp.StatusOK) + ctx.SetContentType("application/json") + ctx.SetBody(responseBody) + } + + handler := mw.Process(mockHandler) + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + ctx := &fasthttp.RequestCtx{} + ctx.Request.Header.SetMethod(fasthttp.MethodGet) + ctx.Request.SetRequestURI("/api/test") + ctx.Request.Header.Set("Accept-Encoding", "gzip") + handler(ctx) + } + }) +} \ No newline at end of file diff --git a/internal/middleware/security/ratelimit_bench_test.go b/internal/middleware/security/ratelimit_bench_test.go new file mode 100644 index 0000000..2304589 --- /dev/null +++ b/internal/middleware/security/ratelimit_bench_test.go @@ -0,0 +1,228 @@ +// Package security 提供限流中间件的基准测试。 +// +// 该文件测试令牌桶限流器的性能,包括单客户端和多客户端场景。 +// +// 作者:xfy +package security + +import ( + "net" + "testing" + + "github.com/valyala/fasthttp" + "rua.plus/lolly/internal/config" +) + +// setupRateLimitRequestCtx 创建用于基准测试的 fasthttp.RequestCtx。 +func setupRateLimitRequestCtx(ip string) *fasthttp.RequestCtx { + ctx := &fasthttp.RequestCtx{} + ctx.Request.Header.SetMethod(fasthttp.MethodGet) + ctx.Request.SetRequestURI("/api/test") + ctx.Init(&fasthttp.Request{}, &net.TCPAddr{ + IP: net.ParseIP(ip), + Port: 12345, + }, nil) + return ctx +} + +// BenchmarkRateLimiterAllow 测试单客户端 Allow 性能。 +func BenchmarkRateLimiterAllow(b *testing.B) { + cfg := &config.RateLimitConfig{ + RequestRate: 1000, + Burst: 2000, + Key: "ip", + } + rl, _ := NewRateLimiter(cfg) + defer rl.(*RateLimiter).StopCleanup() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + rl.(*RateLimiter).Allow("192.168.1.100") + } +} + +// BenchmarkRateLimiterAllowParallel_10Clients 测试 10 客户端并发 Allow 性能。 +func BenchmarkRateLimiterAllowParallel_10Clients(b *testing.B) { + cfg := &config.RateLimitConfig{ + RequestRate: 10000, + Burst: 20000, + Key: "ip", + } + rl, _ := NewRateLimiter(cfg) + defer rl.(*RateLimiter).StopCleanup() + + clients := make([]string, 10) + for i := range clients { + clients[i] = "192.168.1." + string(rune('0'+i)) + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + rl.(*RateLimiter).Allow(clients[i%10]) + i++ + } + }) +} + +// BenchmarkRateLimiterAllowParallel_100Clients 测试 100 客户端并发 Allow 性能。 +func BenchmarkRateLimiterAllowParallel_100Clients(b *testing.B) { + cfg := &config.RateLimitConfig{ + RequestRate: 100000, + Burst: 200000, + Key: "ip", + } + rl, _ := NewRateLimiter(cfg) + defer rl.(*RateLimiter).StopCleanup() + + clients := make([]string, 100) + for i := range clients { + clients[i] = "10.0.0." + string(rune(i)) + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + rl.(*RateLimiter).Allow(clients[i%100]) + i++ + } + }) +} + +// BenchmarkRateLimiterAllowParallel_1000Clients 测试 1000 客户端并发 Allow 性能。 +func BenchmarkRateLimiterAllowParallel_1000Clients(b *testing.B) { + cfg := &config.RateLimitConfig{ + RequestRate: 1000000, + Burst: 2000000, + Key: "ip", + } + rl, _ := NewRateLimiter(cfg) + defer rl.(*RateLimiter).StopCleanup() + + clients := make([]string, 1000) + for i := range clients { + clients[i] = "172.16.0." + string(rune(i%256)) + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + rl.(*RateLimiter).Allow(clients[i%1000]) + i++ + } + }) +} + +// BenchmarkRateLimiterCleanup_1000Buckets 测试清理 1000 个过期桶的性能。 +func BenchmarkRateLimiterCleanup_1000Buckets(b *testing.B) { + cfg := &config.RateLimitConfig{ + RequestRate: 100, + Burst: 200, + Key: "ip", + } + mw, _ := NewRateLimiter(cfg) + rl := mw.(*RateLimiter) + defer rl.StopCleanup() + + // 预创建 1000 个桶 + for i := 0; i < 1000; i++ { + rl.Allow("192.168.0." + string(rune(i))) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + rl.Cleanup(0) // 清理所有桶 + // 重新创建桶以保持测试一致性 + for j := 0; j < 1000; j++ { + rl.Allow("192.168.0." + string(rune(j))) + } + } +} + +// BenchmarkKeyByIP 测试 IP 提取性能。 +func BenchmarkKeyByIP(b *testing.B) { + ctx := setupRateLimitRequestCtx("192.168.1.100") + + b.ResetTimer() + for i := 0; i < b.N; i++ { + keyByIP(ctx) + } +} + +// BenchmarkRateLimiterMiddleware 组件级测试:测量限流中间件本身的开销。 +func BenchmarkRateLimiterMiddleware(b *testing.B) { + cfg := &config.RateLimitConfig{ + RequestRate: 100000, + Burst: 200000, + Key: "ip", + } + rl, _ := NewRateLimiter(cfg) + defer rl.(*RateLimiter).StopCleanup() + + mockHandler := func(ctx *fasthttp.RequestCtx) { + ctx.SetStatusCode(fasthttp.StatusOK) + ctx.SetBodyString("OK") + } + + handler := rl.Process(mockHandler) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + ctx := setupRateLimitRequestCtx("192.168.1.100") + handler(ctx) + } +} + +// BenchmarkRateLimiterMiddlewareParallel 测试并发场景下的中间件性能。 +func BenchmarkRateLimiterMiddlewareParallel(b *testing.B) { + cfg := &config.RateLimitConfig{ + RequestRate: 1000000, + Burst: 2000000, + Key: "ip", + } + rl, _ := NewRateLimiter(cfg) + defer rl.(*RateLimiter).StopCleanup() + + mockHandler := func(ctx *fasthttp.RequestCtx) { + ctx.SetStatusCode(fasthttp.StatusOK) + ctx.SetBodyString("OK") + } + + handler := rl.Process(mockHandler) + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + ip := "192.168.1." + string(rune('0'+i%10)) + ctx := setupRateLimitRequestCtx(ip) + handler(ctx) + i++ + } + }) +} + +// BenchmarkRateLimiterStats 测试获取统计信息的性能。 +func BenchmarkRateLimiterStats(b *testing.B) { + cfg := &config.RateLimitConfig{ + RequestRate: 1000, + Burst: 2000, + Key: "ip", + } + mw, _ := NewRateLimiter(cfg) + rl := mw.(*RateLimiter) + defer rl.StopCleanup() + + // 预创建一些桶 + for i := 0; i < 100; i++ { + rl.Allow("192.168.0." + string(rune(i))) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + rl.GetStats() + } +} \ No newline at end of file diff --git a/internal/middleware/security/sliding_window_bench_test.go b/internal/middleware/security/sliding_window_bench_test.go new file mode 100644 index 0000000..6a1f2bf --- /dev/null +++ b/internal/middleware/security/sliding_window_bench_test.go @@ -0,0 +1,142 @@ +// Package security 提供滑动窗口限流器的基准测试。 +// +// 该文件测试近似模式和精确模式的滑动窗口限流性能。 +// +// 作者:xfy +package security + +import ( + "testing" + "time" +) + +// BenchmarkSlidingWindowAllow 测试近似模式滑动窗口 Allow 性能。 +func BenchmarkSlidingWindowAllow(b *testing.B) { + sw := NewSlidingWindowLimiter(time.Second, 10000, false) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + sw.Allow("192.168.1.100") + } +} + +// BenchmarkSlidingWindowAllowPrecise 测试精确模式滑动窗口 Allow 性能。 +func BenchmarkSlidingWindowAllowPrecise(b *testing.B) { + sw := NewSlidingWindowLimiter(time.Second, 10000, true) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + sw.Allow("192.168.1.100") + } +} + +// BenchmarkSlidingWindowAllowParallel 测试近似模式并发 Allow 性能。 +func BenchmarkSlidingWindowAllowParallel(b *testing.B) { + sw := NewSlidingWindowLimiter(time.Second, 100000, false) + + clients := make([]string, 10) + for i := range clients { + clients[i] = "192.168.1." + string(rune('0'+i)) + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + sw.Allow(clients[i%10]) + i++ + } + }) +} + +// BenchmarkSlidingWindowAllowPreciseParallel 测试精确模式并发 Allow 性能。 +func BenchmarkSlidingWindowAllowPreciseParallel(b *testing.B) { + sw := NewSlidingWindowLimiter(time.Second, 100000, true) + + clients := make([]string, 10) + for i := range clients { + clients[i] = "192.168.1." + string(rune('0'+i)) + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + sw.Allow(clients[i%10]) + i++ + } + }) +} + +// BenchmarkSlidingWindowCleanup 测试滑动窗口清理性能。 +func BenchmarkSlidingWindowCleanup(b *testing.B) { + sw := NewSlidingWindowLimiter(time.Second, 1000, false) + + // 预创建 100 个键 + for i := 0; i < 100; i++ { + sw.Allow("192.168.0." + string(rune(i))) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + sw.Cleanup(time.Hour) + } +} + +// BenchmarkSlidingWindowGetCount 测试获取计数性能。 +func BenchmarkSlidingWindowGetCount(b *testing.B) { + sw := NewSlidingWindowLimiter(time.Second, 10000, false) + key := "192.168.1.100" + + // 预先添加一些请求 + for i := 0; i < 100; i++ { + sw.Allow(key) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + sw.GetCount(key) + } +} + +// BenchmarkSlidingWindowReset 测试重置性能。 +func BenchmarkSlidingWindowReset(b *testing.B) { + sw := NewSlidingWindowLimiter(time.Second, 10000, false) + key := "192.168.1.100" + + b.ResetTimer() + for i := 0; i < b.N; i++ { + sw.Allow(key) + sw.Reset(key) + } +} + +// BenchmarkSlidingWindowMultiKey 测试多键场景性能。 +func BenchmarkSlidingWindowMultiKey(b *testing.B) { + sw := NewSlidingWindowLimiter(time.Second, 10000, false) + + keys := make([]string, 100) + for i := range keys { + keys[i] = "192.168.0." + string(rune(i)) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + sw.Allow(keys[i%100]) + } +} + +// BenchmarkSlidingWindowStats 测试获取统计信息性能。 +func BenchmarkSlidingWindowStats(b *testing.B) { + sw := NewSlidingWindowLimiter(time.Second, 10000, false) + + // 预创建一些键 + for i := 0; i < 50; i++ { + sw.Allow("192.168.0." + string(rune(i))) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + sw.GetStats() + } +} \ No newline at end of file diff --git a/internal/variable/variable_bench_test.go b/internal/variable/variable_bench_test.go new file mode 100644 index 0000000..bb5888b --- /dev/null +++ b/internal/variable/variable_bench_test.go @@ -0,0 +1,212 @@ +// Package variable 提供变量模块的基准测试。 +// +// 该文件测试变量展开和 Pool 操作的性能。 +// +// 作者:xfy +package variable + +import ( + "net" + "testing" + + "github.com/valyala/fasthttp" +) + +// setupBenchmarkRequestCtx 创建用于基准测试的 fasthttp.RequestCtx。 +func setupBenchmarkRequestCtx() *fasthttp.RequestCtx { + ctx := &fasthttp.RequestCtx{} + ctx.Request.Header.SetMethod(fasthttp.MethodGet) + ctx.Request.SetRequestURI("/test/path?foo=bar&baz=qux") + ctx.Request.Header.SetHost("example.com") + ctx.Init(&fasthttp.Request{}, &net.TCPAddr{ + IP: net.ParseIP("192.168.1.100"), + Port: 12345, + }, nil) + return ctx +} + +// BenchmarkVariableExpandSimple 测试简单模板展开性能。 +// +// 模板: "$remote_addr - $request_method" +func BenchmarkVariableExpandSimple(b *testing.B) { + ctx := setupBenchmarkRequestCtx() + vc := NewVariableContext(ctx) + defer ReleaseVariableContext(vc) + + template := "$remote_addr - $request_method" + + b.ResetTimer() + for i := 0; i < b.N; i++ { + vc.Expand(template) + } +} + +// BenchmarkVariableExpandComplex 测试复杂模板展开性能。 +// +// 模拟 Nginx combined 日志格式: +// "$remote_addr - [$time_local] \"$request_method $uri $args\" $status $body_bytes_sent" +func BenchmarkVariableExpandComplex(b *testing.B) { + ctx := setupBenchmarkRequestCtx() + vc := NewVariableContext(ctx) + vc.SetResponseInfo(200, 1024, 1000000) // status, bodySize, durationNs + defer ReleaseVariableContext(vc) + + template := "$remote_addr - [$time_local] \"$request_method $uri $args\" $status $body_bytes_sent" + + b.ResetTimer() + for i := 0; i < b.N; i++ { + vc.Expand(template) + } +} + +// BenchmarkVariableExpandMixed 测试混合 ${var} 和 $var 格式的展开性能。 +func BenchmarkVariableExpandMixed(b *testing.B) { + ctx := setupBenchmarkRequestCtx() + vc := NewVariableContext(ctx) + defer ReleaseVariableContext(vc) + + template := "${remote_addr} - $request_method ${uri}?${args}" + + b.ResetTimer() + for i := 0; i < b.N; i++ { + vc.Expand(template) + } +} + +// BenchmarkVariableExpandNoVar 测试无变量模板的性能(快速路径)。 +func BenchmarkVariableExpandNoVar(b *testing.B) { + ctx := setupBenchmarkRequestCtx() + vc := NewVariableContext(ctx) + defer ReleaseVariableContext(vc) + + template := "This is a plain string with no variables" + + b.ResetTimer() + for i := 0; i < b.N; i++ { + vc.Expand(template) + } +} + +// BenchmarkVariableContextPool 测试 Pool 获取释放性能。 +func BenchmarkVariableContextPool(b *testing.B) { + ctx := setupBenchmarkRequestCtx() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + vc := NewVariableContext(ctx) + ReleaseVariableContext(vc) + } +} + +// BenchmarkVariableContextPoolParallel 测试并发 Pool 获取释放性能。 +func BenchmarkVariableContextPoolParallel(b *testing.B) { + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + ctx := setupBenchmarkRequestCtx() + for pb.Next() { + vc := NewVariableContext(ctx) + ReleaseVariableContext(vc) + } + }) +} + +// BenchmarkVariableGetCache 测试内置变量缓存命中性能。 +// +// 首次获取变量会求值并缓存,后续获取命中缓存。 +func BenchmarkVariableGetCache(b *testing.B) { + ctx := setupBenchmarkRequestCtx() + vc := NewVariableContext(ctx) + defer ReleaseVariableContext(vc) + + // 预热缓存 + _, _ = vc.Get("remote_addr") + + b.ResetTimer() + for i := 0; i < b.N; i++ { + vc.Get("remote_addr") + } +} + +// BenchmarkVariableGetNoCache 测试内置变量首次求值性能。 +// +// 每次循环创建新的 VariableContext,模拟首次求值场景。 +func BenchmarkVariableGetNoCache(b *testing.B) { + ctx := setupBenchmarkRequestCtx() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + vc := NewVariableContext(ctx) + vc.Get("remote_addr") + ReleaseVariableContext(vc) + } +} + +// BenchmarkVariableGetMultiple 测试获取多个内置变量的性能。 +func BenchmarkVariableGetMultiple(b *testing.B) { + ctx := setupBenchmarkRequestCtx() + vc := NewVariableContext(ctx) + defer ReleaseVariableContext(vc) + + vars := []string{ + "remote_addr", "request_method", "uri", "args", + "host", "request_uri", "scheme", "time_local", + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, name := range vars { + vc.Get(name) + } + } +} + +// BenchmarkVariableSetAndGet 测试设置和获取自定义变量的性能。 +func BenchmarkVariableSetAndGet(b *testing.B) { + ctx := setupBenchmarkRequestCtx() + vc := NewVariableContext(ctx) + defer ReleaseVariableContext(vc) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + vc.Set("custom_var", "custom_value") + vc.Get("custom_var") + } +} + +// BenchmarkExpandStringStaticWithLookup 测试静态展开函数的性能(使用自定义查找函数)。 +func BenchmarkExpandStringStaticWithLookup(b *testing.B) { + template := "$remote_addr - $request_method" + lookup := func(name string) string { + switch name { + case "remote_addr": + return "192.168.1.100" + case "request_method": + return "GET" + default: + return "" + } + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + ExpandString(template, lookup) + } +} + +// BenchmarkVariableExpandLongTemplate 测试长模板展开性能。 +// +// 模拟完整访问日志格式,约 200 字符。 +func BenchmarkVariableExpandLongTemplate(b *testing.B) { + ctx := setupBenchmarkRequestCtx() + vc := NewVariableContext(ctx) + vc.SetResponseInfo(200, 4096, 15000000) + vc.SetServerName("api.example.com") + defer ReleaseVariableContext(vc) + + template := "$remote_addr - [$time_local] \"$request_method $uri?$args\" $status $body_bytes_sent \"$http_referer\" \"$http_user_agent\" $request_time $server_name" + + b.ResetTimer() + for i := 0; i < b.N; i++ { + vc.Expand(template) + } +} \ No newline at end of file