test(benchmark): 新增组件级基准测试套件
- 新增 benchmark_context.go 标准化测试上下文构造器 - 新增静态文件处理器基准测试(缓存命中/未命中、try_files) - 新增访问日志中间件基准测试 - 新增压缩中间件基准测试(gzip/brotli、Pool 复用) - 新增限流器基准测试(令牌桶、滑动窗口、多客户端并发) - 新增变量展开基准测试(模板展开、Pool 操作)
This commit is contained in:
parent
f46b0dee07
commit
25bdba4e01
359
internal/benchmark/tools/benchmark_context.go
Normal file
359
internal/benchmark/tools/benchmark_context.go
Normal 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:
|
||||||
|
// 池已满,丢弃
|
||||||
|
}
|
||||||
|
}
|
||||||
213
internal/handler/static_bench_test.go
Normal file
213
internal/handler/static_bench_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
70
internal/middleware/accesslog/accesslog_bench_test.go
Normal file
70
internal/middleware/accesslog/accesslog_bench_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
278
internal/middleware/compression/compression_bench_test.go
Normal file
278
internal/middleware/compression/compression_bench_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
228
internal/middleware/security/ratelimit_bench_test.go
Normal file
228
internal/middleware/security/ratelimit_bench_test.go
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
142
internal/middleware/security/sliding_window_bench_test.go
Normal file
142
internal/middleware/security/sliding_window_bench_test.go
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
212
internal/variable/variable_bench_test.go
Normal file
212
internal/variable/variable_bench_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user