test(benchmark): 新增组件级基准测试套件

- 新增 benchmark_context.go 标准化测试上下文构造器
- 新增静态文件处理器基准测试(缓存命中/未命中、try_files)
- 新增访问日志中间件基准测试
- 新增压缩中间件基准测试(gzip/brotli、Pool 复用)
- 新增限流器基准测试(令牌桶、滑动窗口、多客户端并发)
- 新增变量展开基准测试(模板展开、Pool 操作)
This commit is contained in:
xfy 2026-04-08 18:25:38 +08:00
parent f46b0dee07
commit 25bdba4e01
7 changed files with 1502 additions and 0 deletions

View File

@ -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:
// 池已满,丢弃
}
}

View File

@ -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("<html><body>Index</body></html>"),
"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)
}
}

View File

@ -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)
}
})
}

View File

@ -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)
}
})
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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)
}
}