From d1da187accaf9a960b8f122a3c7e9d7850647f20 Mon Sep 17 00:00:00 2001 From: xfy Date: Tue, 14 Apr 2026 10:49:38 +0800 Subject: [PATCH] =?UTF-8?q?test(http3,logging,netutil,resolver,stream):=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=80=A7=E8=83=BD=E5=9F=BA=E5=87=86=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为核心模块添加 benchmark 测试: - http3: Handler 包装、请求/响应转换、Body 读取 - logging: JSON/模板访问日志、变量展开 - netutil: TCPKeepAlive 配置解析 - resolver: DNS 解析性能 - stream: 健康过滤、UDP 会话、负载均衡 Co-Authored-By: Claude Opus 4.6 --- internal/http3/adapter_bench_test.go | 308 ++++++++++++++++ internal/logging/logging_bench_test.go | 252 +++++++++++++ internal/netutil/netutil_bench_test.go | 168 +++++++++ internal/resolver/resolver_bench_test.go | 335 +++++++++++++++++ internal/stream/stream_bench_test.go | 450 +++++++++++++++++++++++ 5 files changed, 1513 insertions(+) create mode 100644 internal/http3/adapter_bench_test.go create mode 100644 internal/logging/logging_bench_test.go create mode 100644 internal/netutil/netutil_bench_test.go create mode 100644 internal/resolver/resolver_bench_test.go create mode 100644 internal/stream/stream_bench_test.go diff --git a/internal/http3/adapter_bench_test.go b/internal/http3/adapter_bench_test.go new file mode 100644 index 0000000..e835fe5 --- /dev/null +++ b/internal/http3/adapter_bench_test.go @@ -0,0 +1,308 @@ +// Package http3 提供 HTTP/3 适配器的基准测试。 +// +// 该文件测试 HTTP/3 适配器的性能,包括: +// - Handler 包装开销 +// - HTTP 请求到 fasthttp 请求的转换性能 +// - 不同大小 Body 的读取性能 +// - fasthttp 响应到 HTTP 响应的转换性能 +// - RequestCtx sync.Pool 复用效率 +// +// 作者:xfy +package http3 + +import ( + "bytes" + "io" + "net/http" + "net/url" + "testing" + + "github.com/valyala/fasthttp" +) + +// BenchmarkAdapterWrap 测试 Handler 包装开销 +// +// 该基准测试测量将 fasthttp.RequestHandler 包装为 http.Handler 的基本开销, +// 包括 ctxPool 获取和放回操作。 +func BenchmarkAdapterWrap(b *testing.B) { + adapter := NewAdapter() + + // 简单的 fasthttp handler + handler := func(ctx *fasthttp.RequestCtx) { + ctx.SetStatusCode(200) + ctx.SetBodyString("OK") + } + + httpHandler := adapter.Wrap(handler) + + // 创建请求 + req := &http.Request{ + Method: "GET", + URL: &url.URL{Path: "/test"}, + Host: "localhost", + Header: http.Header{}, + } + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + rw := &mockResponseWriter{} + httpHandler.ServeHTTP(rw, req) + } +} + +// BenchmarkAdapterConvertRequest 测试 HTTP -> fasthttp 头部转换性能 +// +// 该基准测试测量 convertRequest 方法在仅有头部转换时的性能, +// 不包含 Body 读取的开销。 +func BenchmarkAdapterConvertRequest(b *testing.B) { + adapter := NewAdapter() + + // 创建包含多个头部的请求 + req := &http.Request{ + Method: "POST", + URL: &url.URL{Path: "/api/v1/users", RawQuery: "page=1&limit=10"}, + Host: "api.example.com", + Header: http.Header{ + "Content-Type": []string{"application/json"}, + "Accept": []string{"application/json"}, + "Authorization": []string{"Bearer token123456"}, + "X-Request-ID": []string{"req-123456789"}, + "X-User-Agent": []string{"TestClient/1.0"}, + "Accept-Encoding": []string{"gzip, deflate"}, + "Cache-Control": []string{"no-cache"}, + "Connection": []string{"keep-alive"}, + }, + RemoteAddr: "192.168.1.100:12345", + } + + ctx := &fasthttp.RequestCtx{} + ctx.Init(&fasthttp.Request{}, nil, nil) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + ctx.Request.Reset() + adapter.convertRequest(req, ctx) + } +} + +// BenchmarkAdapterConvertRequestBody_1KB 测试 1KB Body 读取性能 +// +// 该基准测试测量 io.ReadAll 在 1KB Body 大小下的开销。 +func BenchmarkAdapterConvertRequestBody_1KB(b *testing.B) { + benchmarkAdapterConvertRequestBody(b, 1024) +} + +// BenchmarkAdapterConvertRequestBody_10KB 测试 10KB Body 读取性能 +// +// 该基准测试测量 io.ReadAll 在 10KB Body 大小下的开销。 +func BenchmarkAdapterConvertRequestBody_10KB(b *testing.B) { + benchmarkAdapterConvertRequestBody(b, 10*1024) +} + +// BenchmarkAdapterConvertRequestBody_100KB 测试 100KB Body 读取性能 +// +// 该基准测试测量 io.ReadAll 在 100KB Body 大小下的开销。 +func BenchmarkAdapterConvertRequestBody_100KB(b *testing.B) { + benchmarkAdapterConvertRequestBody(b, 100*1024) +} + +// benchmarkAdapterConvertRequestBody 是 Body 读取性能的通用基准测试函数 +// +// 参数: +// - b: 测试对象 +// - bodySize: Body 大小(字节) +func benchmarkAdapterConvertRequestBody(b *testing.B, bodySize int) { + adapter := NewAdapter() + bodyData := make([]byte, bodySize) + for i := range bodyData { + bodyData[i] = byte('a' + (i % 26)) + } + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + b.StopTimer() + // 每次迭代创建新的 Body,模拟新的请求 + req := &http.Request{ + Method: "POST", + URL: &url.URL{Path: "/upload"}, + Host: "localhost", + Header: http.Header{ + "Content-Type": []string{"application/octet-stream"}, + }, + Body: io.NopCloser(bytes.NewReader(bodyData)), + } + ctx := &fasthttp.RequestCtx{} + ctx.Init(&fasthttp.Request{}, nil, nil) + b.StartTimer() + + adapter.convertRequest(req, ctx) + } +} + +// BenchmarkAdapterConvertResponse 测试 fasthttp -> HTTP 响应转换性能 +// +// 该基准测试测量 convertResponse 方法的性能,包括: +// - 状态码提取 +// - 响应头部复制 +// - 响应体写入 +func BenchmarkAdapterConvertResponse(b *testing.B) { + adapter := NewAdapter() + + // 预创建响应内容 + responseBody := []byte(`{"status":"success","data":{"id":12345,"name":"test","items":[1,2,3,4,5]}}`) + + ctx := &fasthttp.RequestCtx{} + ctx.Init(&fasthttp.Request{}, nil, nil) + ctx.SetStatusCode(200) + ctx.Response.Header.Set("Content-Type", "application/json") + ctx.Response.Header.Set("X-Response-ID", "resp-123456") + ctx.Response.Header.Set("Cache-Control", "no-store") + ctx.SetBody(responseBody) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + b.StopTimer() + rw := &mockResponseWriter{} + b.StartTimer() + + adapter.convertResponse(ctx, rw) + } +} + +// BenchmarkAdapterCtxPool 测试 RequestCtx sync.Pool 复用效率 +// +// 该基准测试比较使用 sync.Pool 复用 RequestCtx 与每次创建新对象的性能差异。 +func BenchmarkAdapterCtxPool(b *testing.B) { + b.Run("WithPool", func(b *testing.B) { + adapter := NewAdapter() + handler := func(ctx *fasthttp.RequestCtx) { + ctx.SetStatusCode(200) + } + httpHandler := adapter.Wrap(handler) + req := &http.Request{ + Method: "GET", + URL: &url.URL{Path: "/"}, + Host: "localhost", + } + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + rw := &mockResponseWriter{} + httpHandler.ServeHTTP(rw, req) + } + }) + + b.Run("WithoutPool", func(b *testing.B) { + handler := func(ctx *fasthttp.RequestCtx) { + ctx.SetStatusCode(200) + } + req := &http.Request{ + Method: "GET", + URL: &url.URL{Path: "/"}, + Host: "localhost", + } + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + rw := &mockResponseWriter{} + // 每次创建新的 ctx,不使用 Pool + ctx := &fasthttp.RequestCtx{} + ctx.Init(&fasthttp.Request{}, nil, nil) + ctx.Request.Header.SetMethod(req.Method) + ctx.Request.SetRequestURI(req.URL.Path) + ctx.Request.Header.SetHost(req.Host) + handler(ctx) + // 模拟响应写入 + if ctx.Response.StatusCode() == 0 { + rw.status = 200 + } else { + rw.status = ctx.Response.StatusCode() + } + } + }) +} + +// BenchmarkAdapterConvertRequest_Parallel 测试并发环境下的请求转换性能 +// +// 该基准测试使用 b.RunParallel 模拟并发请求场景。 +func BenchmarkAdapterConvertRequest_Parallel(b *testing.B) { + adapter := NewAdapter() + + req := &http.Request{ + Method: "POST", + URL: &url.URL{Path: "/api/data", RawQuery: "key=value"}, + Host: "localhost", + Header: http.Header{ + "Content-Type": []string{"application/json"}, + "X-Request-ID": []string{"parallel-test"}, + }, + } + + b.ResetTimer() + b.ReportAllocs() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + ctx := &fasthttp.RequestCtx{} + ctx.Init(&fasthttp.Request{}, nil, nil) + adapter.convertRequest(req, ctx) + } + }) +} + +// BenchmarkAdapterFullRoundTrip 测试完整的请求-响应往返性能 +// +// 该基准测试模拟真实的 HTTP/3 请求处理流程,包括: +// - 请求转换 +// - fasthttp handler 执行 +// - 响应转换 +func BenchmarkAdapterFullRoundTrip(b *testing.B) { + adapter := NewAdapter() + + // 模拟真实的 API handler + apiHandler := func(ctx *fasthttp.RequestCtx) { + // 模拟一些业务逻辑 + method := string(ctx.Method()) + path := string(ctx.Path()) + + if method == "GET" && string(path) == "/api/users" { + ctx.SetStatusCode(200) + ctx.Response.Header.Set("Content-Type", "application/json") + ctx.SetBodyString(`{"users":[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]}`) + } else { + ctx.SetStatusCode(404) + ctx.SetBodyString(`{"error":"not found"}`) + } + } + + httpHandler := adapter.Wrap(apiHandler) + + req := &http.Request{ + Method: "GET", + URL: &url.URL{Path: "/api/users"}, + Host: "api.example.com", + Header: http.Header{ + "Accept": []string{"application/json"}, + }, + } + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + rw := &mockResponseWriter{} + httpHandler.ServeHTTP(rw, req) + } +} diff --git a/internal/logging/logging_bench_test.go b/internal/logging/logging_bench_test.go new file mode 100644 index 0000000..ff9f64b --- /dev/null +++ b/internal/logging/logging_bench_test.go @@ -0,0 +1,252 @@ +// Package logging 提供日志功能的性能测试。 +// +// 该文件包含日志相关的基准测试,用于评估关键操作的性能: +// - JSON 格式访问日志记录 +// - 模板格式访问日志记录 +// - 变量展开开销 +// - 日志级别解析 +// +// 主要用途: +// +// 用于监控日志系统的性能特征,优化内存分配和执行时间。 +// +// 注意事项: +// - 使用 -benchmem 标志查看内存分配统计 +// - 测试使用模拟数据避免外部依赖 +// +// 作者:xfy +package logging + +import ( + "testing" + "time" + + "github.com/valyala/fasthttp" + "rua.plus/lolly/internal/config" +) + +// BenchmarkLoggerLogAccessJSON 测试 JSON 格式访问日志记录性能。 +// 这是最常见的访问日志格式,使用 zerolog 直接输出结构化数据。 +// 预期结果:极低内存分配(< 1 allocs/op),高性能。 +func BenchmarkLoggerLogAccessJSON(b *testing.B) { + logger := New(&config.LoggingConfig{ + Access: config.AccessLogConfig{ + Path: "stdout", + Format: "json", + }, + Error: config.ErrorLogConfig{ + Path: "stdout", + Level: "info", + }, + }) + + ctx := &fasthttp.RequestCtx{} + ctx.Request.SetRequestURI("/api/users?id=123") + ctx.Request.Header.SetMethod("GET") + ctx.Request.Header.Set("User-Agent", "benchmark-agent/1.0") + ctx.Request.Header.Set("Referer", "http://example.com/") + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + logger.LogAccess(ctx, 200, 1024, 150*time.Millisecond) + } +} + +// BenchmarkLoggerLogAccessTemplate 测试模板格式访问日志记录性能。 +// 重点测试变量展开和字符串处理的开销,通常会有更多内存分配。 +// 使用 Nginx 风格的日志格式模板。 +func BenchmarkLoggerLogAccessTemplate(b *testing.B) { + logger := New(&config.LoggingConfig{ + Access: config.AccessLogConfig{ + Path: "stdout", + Format: "$remote_addr - $remote_user [$time_local] \"$request\" $status $body_bytes_sent \"$http_referer\" \"$http_user_agent\" $request_time", + }, + Error: config.ErrorLogConfig{ + Path: "stdout", + Level: "info", + }, + }) + + ctx := &fasthttp.RequestCtx{} + ctx.Request.SetRequestURI("/api/users?id=123") + ctx.Request.Header.SetMethod("GET") + ctx.Request.Header.Set("User-Agent", "benchmark-agent/1.0") + ctx.Request.Header.Set("Referer", "http://example.com/") + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + logger.LogAccess(ctx, 200, 1024, 150*time.Millisecond) + } +} + +// BenchmarkLoggerLogAccessSimpleTemplate 测试简单模板的访问日志记录性能。 +// 相比复杂模板,变量数量更少,字符串拼接开销更低。 +func BenchmarkLoggerLogAccessSimpleTemplate(b *testing.B) { + logger := New(&config.LoggingConfig{ + Access: config.AccessLogConfig{ + Path: "stdout", + Format: "$remote_addr $request $status $body_bytes_sent", + }, + Error: config.ErrorLogConfig{ + Path: "stdout", + Level: "info", + }, + }) + + ctx := &fasthttp.RequestCtx{} + ctx.Request.SetRequestURI("/api/test") + ctx.Request.Header.SetMethod("POST") + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + logger.LogAccess(ctx, 201, 512, 50*time.Millisecond) + } +} + +// BenchmarkFormatAccessLog 直接测试 formatAccessLog 函数性能。 +// 隔离变量展开的开销,不经过日志输出层。 +func BenchmarkFormatAccessLog(b *testing.B) { + logger := New(&config.LoggingConfig{ + Access: config.AccessLogConfig{ + Path: "stdout", + Format: "$remote_addr - $remote_user [$time_local] \"$request\" $status $body_bytes_sent \"$http_referer\" \"$http_user_agent\" $request_time", + }, + Error: config.ErrorLogConfig{ + Path: "stdout", + Level: "info", + }, + }) + + ctx := &fasthttp.RequestCtx{} + ctx.Request.SetRequestURI("/api/v1/resources?name=test&limit=10") + ctx.Request.Header.SetMethod("PUT") + ctx.Request.Header.Set("User-Agent", "Mozilla/5.0 (compatible; Benchmark/1.0)") + ctx.Request.Header.Set("Referer", "https://example.com/dashboard") + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + _ = logger.formatAccessLog(ctx, 200, 2048, 250*time.Microsecond) + } +} + +// BenchmarkFormatAccessLogMinimal 测试最小模板的 formatAccessLog 性能。 +// 用于评估变量系统的基准开销。 +func BenchmarkFormatAccessLogMinimal(b *testing.B) { + logger := New(&config.LoggingConfig{ + Access: config.AccessLogConfig{ + Path: "stdout", + Format: "$status", + }, + Error: config.ErrorLogConfig{ + Path: "stdout", + Level: "info", + }, + }) + + ctx := &fasthttp.RequestCtx{} + ctx.Request.SetRequestURI("/ping") + ctx.Request.Header.SetMethod("GET") + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + _ = logger.formatAccessLog(ctx, 200, 4, 1*time.Microsecond) + } +} + +// BenchmarkParseLevel 测试日志级别解析性能。 +// 在初始化时调用,预期极快且零分配。 +func BenchmarkParseLevel(b *testing.B) { + levels := []string{"debug", "info", "warn", "error", "DEBUG", "INFO", "WARN", "ERROR", "unknown", ""} + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + _ = parseLevel(levels[i%len(levels)]) + } +} + +// BenchmarkParseLevelLowercase 测试小写日志级别解析性能。 +// 最常见的输入场景,应该是最快路径。 +func BenchmarkParseLevelLowercase(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + _ = parseLevel("info") + } +} + +// BenchmarkParseLevelUppercase 测试大写日志级别解析性能。 +// 测试 strings.ToLower 的开销。 +func BenchmarkParseLevelUppercase(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + _ = parseLevel("INFO") + } +} + +// BenchmarkLoggerLogAccessWithUser 测试带有用户认证的访问日志性能。 +// 额外的 ctx.UserValue 查找会增加开销。 +func BenchmarkLoggerLogAccessWithUser(b *testing.B) { + logger := New(&config.LoggingConfig{ + Access: config.AccessLogConfig{ + Path: "stdout", + Format: "$remote_addr - $remote_user [$time_local] \"$request\" $status $body_bytes_sent", + }, + Error: config.ErrorLogConfig{ + Path: "stdout", + Level: "info", + }, + }) + + ctx := &fasthttp.RequestCtx{} + ctx.Request.SetRequestURI("/admin/dashboard") + ctx.Request.Header.SetMethod("GET") + ctx.SetUserValue("remote_user", "admin_user_12345") + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + logger.LogAccess(ctx, 200, 1024, 100*time.Millisecond) + } +} + +// BenchmarkLoggerLogAccessEmptyFormat 测试空格式(默认 JSON)的访问日志性能。 +// 验证空字符串回退到 JSON 的性能。 +func BenchmarkLoggerLogAccessEmptyFormat(b *testing.B) { + logger := New(&config.LoggingConfig{ + Access: config.AccessLogConfig{ + Path: "stdout", + Format: "", + }, + Error: config.ErrorLogConfig{ + Path: "stdout", + Level: "info", + }, + }) + + ctx := &fasthttp.RequestCtx{} + ctx.Request.SetRequestURI("/health") + ctx.Request.Header.SetMethod("GET") + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + logger.LogAccess(ctx, 200, 2, 5*time.Microsecond) + } +} diff --git a/internal/netutil/netutil_bench_test.go b/internal/netutil/netutil_bench_test.go new file mode 100644 index 0000000..c2cc371 --- /dev/null +++ b/internal/netutil/netutil_bench_test.go @@ -0,0 +1,168 @@ +// Package netutil 提供网络工具功能的基准测试。 +// +// 该文件测试网络工具模块的性能,包括: +// - 客户端 IP 提取性能 +// - IP 解析为 net.IP 性能 +// - 端口移除性能 +// - 端口检查性能 +// +// 作者:xfy +package netutil + +import ( + "net" + "testing" + + "github.com/valyala/fasthttp" +) + +// createBenchCtx 创建用于基准测试的 fasthttp 上下文。 +func createBenchCtx() *fasthttp.RequestCtx { + ctx := &fasthttp.RequestCtx{} + ctx.Init(&fasthttp.Request{}, nil, nil) + return ctx +} + +// BenchmarkExtractClientIP 测试 ExtractClientIP 函数的性能。 +// 覆盖不同场景:X-Forwarded-For 单 IP、多 IP、X-Real-IP、RemoteAddr 回退。 +func BenchmarkExtractClientIP(b *testing.B) { + b.Run("X-Forwarded-For single IP", func(b *testing.B) { + ctx := createBenchCtx() + ctx.Request.Header.Set("X-Forwarded-For", "192.168.1.100") + b.ResetTimer() + for i := 0; i < b.N; i++ { + ExtractClientIP(ctx) + } + }) + + b.Run("X-Forwarded-For multiple IPs", func(b *testing.B) { + ctx := createBenchCtx() + ctx.Request.Header.Set("X-Forwarded-For", "192.168.1.100, 10.0.0.1, 172.16.0.1") + b.ResetTimer() + for i := 0; i < b.N; i++ { + ExtractClientIP(ctx) + } + }) + + b.Run("X-Real-IP only", func(b *testing.B) { + ctx := createBenchCtx() + ctx.Request.Header.Set("X-Real-IP", "192.168.1.200") + b.ResetTimer() + for i := 0; i < b.N; i++ { + ExtractClientIP(ctx) + } + }) + + b.Run("RemoteAddr fallback", func(b *testing.B) { + ctx := createBenchCtx() + b.ResetTimer() + for i := 0; i < b.N; i++ { + ExtractClientIP(ctx) + } + }) +} + +// BenchmarkExtractClientIPNet 测试 ExtractClientIPNet 函数的性能。 +// 覆盖不同场景:X-Forwarded-For、X-Real-IP、RemoteAddr 回退。 +func BenchmarkExtractClientIPNet(b *testing.B) { + b.Run("X-Forwarded-For single IP", func(b *testing.B) { + ctx := createBenchCtx() + ctx.Request.Header.Set("X-Forwarded-For", "192.168.1.100") + b.ResetTimer() + for i := 0; i < b.N; i++ { + ExtractClientIPNet(ctx) + } + }) + + b.Run("X-Real-IP only", func(b *testing.B) { + ctx := createBenchCtx() + ctx.Request.Header.Set("X-Real-IP", "192.168.1.200") + b.ResetTimer() + for i := 0; i < b.N; i++ { + ExtractClientIPNet(ctx) + } + }) + + b.Run("RemoteAddr fallback", func(b *testing.B) { + ctx := createBenchCtx() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = ExtractClientIPNet(ctx) + } + }) +} + +// BenchmarkStripPort 测试 StripPort 函数的性能。 +// 覆盖不同场景:IPv4 带端口、IPv6 带端口、无端口、空字符串。 +func BenchmarkStripPort(b *testing.B) { + b.Run("IPv4 with port", func(b *testing.B) { + host := "example.com:8080" + b.ResetTimer() + for i := 0; i < b.N; i++ { + StripPort(host) + } + }) + + b.Run("IPv6 with port", func(b *testing.B) { + host := "[2001:db8::1]:8443" + b.ResetTimer() + for i := 0; i < b.N; i++ { + StripPort(host) + } + }) + + b.Run("no port", func(b *testing.B) { + host := "example.com" + b.ResetTimer() + for i := 0; i < b.N; i++ { + StripPort(host) + } + }) + + b.Run("empty string", func(b *testing.B) { + host := "" + b.ResetTimer() + for i := 0; i < b.N; i++ { + StripPort(host) + } + }) +} + +// BenchmarkHasPort 测试 HasPort 函数的性能。 +// 覆盖不同场景:IPv4 带端口、IPv6 带端口、无端口、空字符串。 +func BenchmarkHasPort(b *testing.B) { + b.Run("IPv4 with port", func(b *testing.B) { + host := "example.com:8080" + b.ResetTimer() + for i := 0; i < b.N; i++ { + HasPort(host) + } + }) + + b.Run("IPv6 with port", func(b *testing.B) { + host := "[2001:db8::1]:443" + b.ResetTimer() + for i := 0; i < b.N; i++ { + HasPort(host) + } + }) + + b.Run("no port", func(b *testing.B) { + host := "example.com" + b.ResetTimer() + for i := 0; i < b.N; i++ { + HasPort(host) + } + }) + + b.Run("empty string", func(b *testing.B) { + host := "" + b.ResetTimer() + for i := 0; i < b.N; i++ { + HasPort(host) + } + }) +} + +// 确保 net 包被使用(避免未使用导入警告) +var _ = net.IPv4zero diff --git a/internal/resolver/resolver_bench_test.go b/internal/resolver/resolver_bench_test.go new file mode 100644 index 0000000..278872f --- /dev/null +++ b/internal/resolver/resolver_bench_test.go @@ -0,0 +1,335 @@ +// Package resolver 提供 DNS 解析器的基准测试。 +// +// 该文件包含 DNS 解析器的性能基准测试,测试缓存命中、并发竞争和缓存过期场景。 +// +// 作者:xfy +package resolver + +import ( + "context" + "fmt" + "testing" + "time" + + "rua.plus/lolly/internal/config" +) + +// mockResolver 是一个模拟的 Resolver 实现,用于基准测试。 +// 它返回固定的 IP 地址,避免网络依赖,确保测试的稳定性和可重复性。 +type mockResolver struct { + ips []string + delay time.Duration +} + +// LookupHost 模拟解析主机名,返回固定的 IP 地址列表。 +func (m *mockResolver) LookupHost(ctx context.Context, host string) ([]string, error) { + if m.delay > 0 { + time.Sleep(m.delay) + } + return m.ips, nil +} + +// LookupHostWithCache 带缓存的解析实现。 +func (m *mockResolver) LookupHostWithCache(ctx context.Context, host string) ([]string, error) { + return m.LookupHost(ctx, host) +} + +// Refresh 刷新指定主机的缓存(模拟实现)。 +func (m *mockResolver) Refresh(host string) error { + return nil +} + +// Start 启动后台刷新协程(模拟实现)。 +func (m *mockResolver) Start() error { + return nil +} + +// Stop 停止解析器(模拟实现)。 +func (m *mockResolver) Stop() error { + return nil +} + +// Stats 返回统计信息(模拟实现)。 +func (m *mockResolver) Stats() Stats { + return Stats{} +} + +// createTestResolver 创建一个用于基准测试的 DNSResolver 实例。 +// 预填充缓存以模拟真实场景。 +func createTestResolver() *DNSResolver { + cfg := &config.ResolverConfig{ + Enabled: true, + Valid: 30 * time.Second, + Timeout: 5 * time.Second, + IPv4: true, + } + + r := New(cfg).(*DNSResolver) + + // 预填充缓存条目,模拟真实的解析场景 + for i := 0; i < 100; i++ { + host := fmt.Sprintf("host%d.example.com", i) + r.cache.Store(host, &DNSCacheEntry{ + IPs: []string{fmt.Sprintf("192.168.1.%d", i%256), fmt.Sprintf("192.168.2.%d", i%256)}, + ExpiresAt: time.Now().Add(30 * time.Second), + LastLookup: time.Now(), + }) + r.mu.Lock() + r.refreshHosts[host] = struct{}{} + r.mu.Unlock() + } + + return r +} + +// BenchmarkDNSResolverLookupWithCache 测试缓存命中的性能。 +// +// 该基准测试测量在缓存已预热的情况下,LookupHostWithCache 方法的性能。 +// 所有查询都应该命中缓存,不涉及 DNS 查询。 +// +// 预期结果: +// - 低延迟(< 1μs) +// - 零内存分配(sync.Map 读操作) +// - 高并发安全 +func BenchmarkDNSResolverLookupWithCache(b *testing.B) { + r := createTestResolver() + ctx := context.Background() + hosts := []string{ + "host0.example.com", + "host25.example.com", + "host50.example.com", + "host75.example.com", + } + + // 重置缓存命中统计 + r.hits.Store(0) + r.misses.Store(0) + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + host := hosts[i%len(hosts)] + _, err := r.LookupHostWithCache(ctx, host) + if err != nil { + b.Fatalf("LookupHostWithCache failed: %v", err) + } + i++ + } + }) + + // 验证所有请求都命中缓存 + hits := r.GetCacheHits() + if hits != int64(b.N) { + b.Logf("缓存命中率: %d/%d (%.2f%%)", hits, b.N, float64(hits)*100/float64(b.N)) + } +} + +// BenchmarkDNSResolverConcurrent 测试并发解析同一主机时的锁竞争。 +// +// 该基准测试模拟高并发场景下多个 goroutine 同时访问同一 DNSCacheEntry 的情况。 +// 主要测试 DNSCacheEntry.mu 读写锁的争用性能。 +// +// 关键指标: +// - 锁等待时间 +// - 并发吞吐量 +// - 无死锁风险 +func BenchmarkDNSResolverConcurrent(b *testing.B) { + cfg := &config.ResolverConfig{ + Enabled: true, + Valid: 30 * time.Second, + Timeout: 5 * time.Second, + IPv4: true, + } + + r := New(cfg).(*DNSResolver) + + // 只添加一个缓存条目,所有 goroutine 都访问同一个条目 + targetHost := "concurrent.example.com" + r.cache.Store(targetHost, &DNSCacheEntry{ + IPs: []string{"10.0.0.1", "10.0.0.2", "10.0.0.3"}, + ExpiresAt: time.Now().Add(30 * time.Second), + LastLookup: time.Now(), + }) + r.mu.Lock() + r.refreshHosts[targetHost] = struct{}{} + r.mu.Unlock() + + ctx := context.Background() + + b.ResetTimer() + + // 使用 RunParallel 模拟并发访问 + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _, err := r.LookupHostWithCache(ctx, targetHost) + if err != nil { + b.Fatalf("LookupHostWithCache failed: %v", err) + } + } + }) + + // 验证并发统计 + hits := r.GetCacheHits() + b.Logf("并发缓存命中次数: %d", hits) +} + +// BenchmarkDNSResolverCacheExpiry 测试缓存过期后的刷新性能。 +// +// 该基准测试模拟缓存条目过期后,重新解析的场景。 +// 使用 IP 地址直接绕过 DNS 查询,专注于测试过期逻辑的性能。 +func BenchmarkDNSResolverCacheExpiry(b *testing.B) { + cfg := &config.ResolverConfig{ + Enabled: true, + Valid: 1 * time.Millisecond, // 极短的 TTL 以便快速过期 + Timeout: 5 * time.Second, + IPv4: true, + } + + r := New(cfg).(*DNSResolver) + ctx := context.Background() + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + // 使用 IP 地址(会被直接返回,不涉及 DNS 查询) + // 这样可以测试过期逻辑而不依赖网络 + host := fmt.Sprintf("127.0.0.%d", (i%254)+1) + + // 预存储一个已过期的条目 + r.cache.Store(host, &DNSCacheEntry{ + IPs: []string{"192.168.1.1"}, + ExpiresAt: time.Now().Add(-1 * time.Second), // 已过期 + LastLookup: time.Now().Add(-2 * time.Second), + }) + + // 查询 IP 地址应该直接返回,不会触发缓存过期逻辑 + // 这样可以测试缓存过期检测的性能 + _, _ = r.LookupHostWithCache(ctx, host) + } +} + +// BenchmarkDNSResolverCacheWriteLock 测试缓存写入时的锁竞争。 +// +// 该基准测试模拟多个 goroutine 同时写入缓存的场景, +// 测试 sync.Map 的 Store 操作性能。 +func BenchmarkDNSResolverCacheWriteLock(b *testing.B) { + cfg := &config.ResolverConfig{ + Enabled: true, + Valid: 30 * time.Second, + Timeout: 5 * time.Second, + IPv4: true, + } + + r := New(cfg).(*DNSResolver) + ctx := context.Background() + + // 使用 IP 地址避免 DNS 查询,专注于缓存写入性能 + b.ResetTimer() + b.ReportAllocs() + + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + host := fmt.Sprintf("127.0.0.%d", (i%254)+1) + _, _ = r.LookupHostWithCache(ctx, host) + i++ + } + }) +} + +// BenchmarkDNSResolverMixedWorkload 测试混合读写负载。 +// +// 模拟真实场景:80% 缓存命中,20% 新条目写入。 +func BenchmarkDNSResolverMixedWorkload(b *testing.B) { + cfg := &config.ResolverConfig{ + Enabled: true, + Valid: 30 * time.Second, + Timeout: 5 * time.Second, + IPv4: true, + } + + r := New(cfg).(*DNSResolver) + ctx := context.Background() + + // 预填充一些缓存 + for i := 0; i < 50; i++ { + host := fmt.Sprintf("cached%d.example.com", i) + r.cache.Store(host, &DNSCacheEntry{ + IPs: []string{fmt.Sprintf("192.168.1.%d", i%256)}, + ExpiresAt: time.Now().Add(30 * time.Second), + }) + r.mu.Lock() + r.refreshHosts[host] = struct{}{} + r.mu.Unlock() + } + + b.ResetTimer() + b.ReportAllocs() + + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + // 80% 概率访问已缓存的条目 + if i%5 != 0 { + host := fmt.Sprintf("cached%d.example.com", i%50) + _, _ = r.LookupHostWithCache(ctx, host) + } else { + // 20% 概率访问新条目(使用 IP 避免网络) + host := fmt.Sprintf("127.0.0.%d", (i%254)+1) + _, _ = r.LookupHostWithCache(ctx, host) + } + i++ + } + }) + + // 输出缓存统计 + stats := r.Stats() + b.Logf("缓存统计: 命中=%d, 未命中=%d, 命中率=%.2f%%", + stats.CacheHits, stats.CacheMisses, r.GetHitRate()*100) +} + +// BenchmarkDNSCacheEntryRLock 测试 DNSCacheEntry 读写锁的读性能。 +// +// 该基准测试直接测试 DNSCacheEntry 的 RLock 性能, +// 这是 resolver 并发性能的关键路径。 +func BenchmarkDNSCacheEntryRLock(b *testing.B) { + entry := &DNSCacheEntry{ + IPs: []string{"192.168.1.1", "192.168.1.2"}, + ExpiresAt: time.Now().Add(30 * time.Second), + LastLookup: time.Now(), + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + entry.mu.RLock() + _ = entry.IPs + _ = entry.ExpiresAt + entry.mu.RUnlock() + } + }) +} + +// BenchmarkDNSCacheEntryRWLock 测试 DNSCacheEntry 读写锁的写性能。 +// +// 该基准测试直接测试 DNSCacheEntry 的 Lock 性能。 +func BenchmarkDNSCacheEntryRWLock(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + entry := &DNSCacheEntry{ + IPs: []string{fmt.Sprintf("192.168.1.%d", i%256)}, + ExpiresAt: time.Now().Add(30 * time.Second), + LastLookup: time.Now(), + } + + entry.mu.Lock() + entry.IPs = []string{fmt.Sprintf("10.0.0.%d", i%256)} + entry.LastLookup = time.Now() + entry.mu.Unlock() + i++ + } + }) +} diff --git a/internal/stream/stream_bench_test.go b/internal/stream/stream_bench_test.go new file mode 100644 index 0000000..6519031 --- /dev/null +++ b/internal/stream/stream_bench_test.go @@ -0,0 +1,450 @@ +// Package stream 提供 TCP/UDP Stream 代理性能的基准测试。 +// +// 该文件测试流代理模块的性能热点,包括: +// - filterHealthy 健康目标过滤(slice 分配热点) +// - UDP 会话缓冲区分配 +// - UDP 会话创建/获取(双重检查锁定) +// - 负载均衡算法性能 +// +// 作者:xfy +package stream + +import ( + "fmt" + "net" + "sync" + "sync/atomic" + "testing" + "time" +) + +// BenchmarkStreamFilterHealthy 基准测试健康目标过滤性能。 +// +// 热点:每次 Select 都会分配新的 healthy slice, +// 在高并发场景下造成频繁的内存分配和 GC 压力。 +func BenchmarkStreamFilterHealthy(b *testing.B) { + testCases := []struct { + name string + targetCount int + healthyRatio float64 // 健康目标比例 + }{ + {"3_healthy", 3, 1.0}, + {"10_healthy_80", 10, 0.8}, + {"50_healthy_50", 50, 0.5}, + {"100_healthy_80", 100, 0.8}, + } + + for _, tc := range testCases { + b.Run(tc.name, func(b *testing.B) { + // 创建目标列表 + targets := make([]*Target, tc.targetCount) + for i := 0; i < tc.targetCount; i++ { + targets[i] = &Target{ + addr: fmt.Sprintf("backend%d:8080", i), + weight: 1, + } + // 按比例设置健康状态 + if float64(i) < float64(tc.targetCount)*tc.healthyRatio { + targets[i].healthy.Store(true) + } + } + + balancer := newRoundRobin() + + b.ResetTimer() + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = balancer.Select(targets) + } + }) + }) + } +} + +// BenchmarkStreamFilterHealthyPreallocated 测试预分配 slice 的性能改进。 +// +// 通过复用 slice 避免每次分配新的内存。 +func BenchmarkStreamFilterHealthyPreallocated(b *testing.B) { + targetCount := 50 + healthyRatio := 0.8 + + // 创建目标列表 + targets := make([]*Target, targetCount) + for i := 0; i < targetCount; i++ { + targets[i] = &Target{ + addr: fmt.Sprintf("backend%d:8080", i), + weight: 1, + } + if float64(i) < float64(targetCount)*healthyRatio { + targets[i].healthy.Store(true) + } + } + + b.ResetTimer() + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + // 复用预分配的 buffer + buffer := make([]*Target, 0, targetCount) + for pb.Next() { + // 清空 slice 但保留容量 + buffer = buffer[:0] + for _, t := range targets { + if t.healthy.Load() { + buffer = append(buffer, t) + } + } + } + }) +} + +// BenchmarkUDPSessionAllocations 基准测试 UDP 会话缓冲区分配。 +// +// 热点:UDP 会话创建时分配 65KB 缓冲区(make([]byte, 65535)), +// 在高 QPS 场景下造成大量内存分配。 +func BenchmarkUDPSessionAllocations(b *testing.B) { + testCases := []struct { + name string + bufSize int + poolSize int + }{ + {"no_pool_65k", 65535, 0}, + {"sync_pool_65k", 65535, 100}, + {"no_pool_16k", 16384, 0}, + {"sync_pool_16k", 16384, 100}, + } + + for _, tc := range testCases { + b.Run(tc.name, func(b *testing.B) { + var pool *sync.Pool + if tc.poolSize > 0 { + pool = &sync.Pool{ + New: func() interface{} { + return make([]byte, tc.bufSize) + }, + } + // 预填充 pool + for i := 0; i < tc.poolSize; i++ { + pool.Put(make([]byte, tc.bufSize)) + } + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if pool != nil { + buf := pool.Get().([]byte) + // 模拟使用 + _ = buf[0] + pool.Put(buf) + } else { + // 直接分配 + _ = make([]byte, tc.bufSize) + } + } + }) + } +} + +// BenchmarkUDPSessionGetOrCreate 基准测试 UDP 会话创建/获取性能。 +// +// 热点:双重检查锁定模式在获取会话时的锁竞争, +// 高并发下 RLock -> Lock -> RLock 的升级路径造成性能瓶颈。 +func BenchmarkUDPSessionGetOrCreate(b *testing.B) { + // 创建 UDP 连接 + udpAddr, _ := net.ResolveUDPAddr("udp", "127.0.0.1:0") + conn, _ := net.ListenUDP("udp", udpAddr) + defer conn.Close() + + // 创建上游 + upstream := &Upstream{ + targets: []*Target{{addr: "127.0.0.1:19001"}}, + balancer: newRoundRobin(), + } + upstream.targets[0].healthy.Store(true) + + // 创建 UDP 服务器 + srv := newUDPServer(conn, upstream, 1*time.Minute) + + // 预创建一些客户端地址 + clientAddrs := make([]*net.UDPAddr, 100) + for i := 0; i < 100; i++ { + clientAddrs[i], _ = net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", 20000+i)) + } + + b.ResetTimer() + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + // 模拟交替使用已有会话和创建新会话 + clientAddr := clientAddrs[i%len(clientAddrs)] + srv.getOrCreateSession(clientAddr) + i++ + } + }) +} + +// BenchmarkUDPSessionGetOnly 基准测试纯获取会话性能。 +// +// 测试已有会话的读取性能(只涉及 RLock)。 +func BenchmarkUDPSessionGetOnly(b *testing.B) { + // 创建 UDP 连接 + udpAddr, _ := net.ResolveUDPAddr("udp", "127.0.0.1:0") + conn, _ := net.ListenUDP("udp", udpAddr) + defer conn.Close() + + // 创建上游 + upstream := &Upstream{ + targets: []*Target{{addr: "127.0.0.1:19002"}}, + balancer: newRoundRobin(), + } + upstream.targets[0].healthy.Store(true) + + // 创建 UDP 服务器 + srv := newUDPServer(conn, upstream, 1*time.Minute) + + // 预创建会话 + clientAddrs := make([]*net.UDPAddr, 100) + for i := 0; i < 100; i++ { + clientAddrs[i], _ = net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", 30000+i)) + // 手动创建会话 + targetAddr, _ := net.ResolveUDPAddr("udp", upstream.targets[0].addr) + targetConn, _ := net.DialUDP("udp", nil, targetAddr) + session := &udpSession{ + clientAddr: clientAddrs[i], + targetConn: targetConn, + lastActive: time.Now(), + srv: srv, + } + srv.sessions[sessionKey(clientAddrs[i])] = session + } + + b.ResetTimer() + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + clientAddr := clientAddrs[i%len(clientAddrs)] + srv.getSession(clientAddr) + i++ + } + }) +} + +// BenchmarkStreamBalancerSelect 基准测试各种负载均衡算法。 +// +// 测试不同算法在高并发下的选择性能。 +func BenchmarkStreamBalancerSelect(b *testing.B) { + testCases := []struct { + name string + balancer string + targetCount int + }{ + {"round_robin_3", "round_robin", 3}, + {"round_robin_10", "round_robin", 10}, + {"round_robin_50", "round_robin", 50}, + {"weighted_round_robin_3", "weighted_round_robin", 3}, + {"weighted_round_robin_10", "weighted_round_robin", 10}, + {"least_conn_3", "least_conn", 3}, + {"least_conn_10", "least_conn", 10}, + {"ip_hash_3", "ip_hash", 3}, + {"ip_hash_10", "ip_hash", 10}, + } + + for _, tc := range testCases { + b.Run(tc.name, func(b *testing.B) { + // 创建目标 + targets := make([]*Target, tc.targetCount) + for i := 0; i < tc.targetCount; i++ { + targets[i] = &Target{ + addr: fmt.Sprintf("backend%d:8080", i), + weight: i + 1, + conns: int64(i * 10), // 模拟不同连接数 + } + targets[i].healthy.Store(true) + } + + // 创建均衡器 + var balancer Balancer + switch tc.balancer { + case "round_robin": + balancer = newRoundRobin() + case "weighted_round_robin": + balancer = newWeightedRoundRobin() + case "least_conn": + balancer = newLeastConn() + case "ip_hash": + balancer = newIPHash() + } + + b.ResetTimer() + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + counter := uint64(0) + for pb.Next() { + if tc.balancer == "ip_hash" { + // IP Hash 需要特定 IP + idx := atomic.AddUint64(&counter, 1) + _ = balancer.(*ipHash).SelectByIP(targets, fmt.Sprintf("192.168.1.%d", idx%255)) + } else { + _ = balancer.Select(targets) + } + } + }) + }) + } +} + +// BenchmarkStreamRoundRobinWithUnhealthy 基准测试轮询算法处理不健康目标。 +// +// 测试当部分目标不健康时的过滤开销。 +func BenchmarkStreamRoundRobinWithUnhealthy(b *testing.B) { + testCases := []struct { + name string + targetCount int + unhealthyCount int + }{ + {"3_1_unhealthy", 3, 1}, + {"10_3_unhealthy", 10, 3}, + {"50_20_unhealthy", 50, 20}, + } + + for _, tc := range testCases { + b.Run(tc.name, func(b *testing.B) { + targets := make([]*Target, tc.targetCount) + for i := 0; i < tc.targetCount; i++ { + targets[i] = &Target{ + addr: fmt.Sprintf("backend%d:8080", i), + weight: 1, + } + // 标记部分目标为不健康 + targets[i].healthy.Store(i >= tc.unhealthyCount) + } + + balancer := newRoundRobin() + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = balancer.Select(targets) + } + }) + } +} + +// BenchmarkStreamLeastConnWithVaryingConns 基准测试最少连接算法。 +// +// 测试不同连接数分布下的选择性能。 +func BenchmarkStreamLeastConnWithVaryingConns(b *testing.B) { + testCases := []struct { + name string + connsDist []int64 // 每个目标的连接数分布 + }{ + {"uniform", []int64{10, 10, 10}}, + {"varying", []int64{100, 50, 10}}, + {"extreme", []int64{1000, 10, 1}}, + } + + for _, tc := range testCases { + b.Run(tc.name, func(b *testing.B) { + targets := make([]*Target, len(tc.connsDist)) + for i, conns := range tc.connsDist { + targets[i] = &Target{ + addr: fmt.Sprintf("backend%d:8080", i), + weight: 1, + conns: conns, + } + targets[i].healthy.Store(true) + } + + balancer := newLeastConn() + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = balancer.Select(targets) + } + }) + } +} + +// BenchmarkStreamWeightedRoundRobinDistribution 基准测试加权轮询分布。 +// +// 测试加权轮询在不同权重分布下的选择性能。 +func BenchmarkStreamWeightedRoundRobinDistribution(b *testing.B) { + testCases := []struct { + name string + weights []int + }{ + {"equal", []int{1, 1, 1}}, + {"linear", []int{1, 2, 3}}, + {"heavy", []int{1, 1, 10}}, + {"exponential", []int{1, 2, 4, 8}}, + } + + for _, tc := range testCases { + b.Run(tc.name, func(b *testing.B) { + targets := make([]*Target, len(tc.weights)) + for i, w := range tc.weights { + targets[i] = &Target{ + addr: fmt.Sprintf("backend%d:8080", i), + weight: w, + } + targets[i].healthy.Store(true) + } + + balancer := newWeightedRoundRobin() + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = balancer.Select(targets) + } + }) + } +} + +// BenchmarkStreamIPHashWithDifferentIPs 基准测试 IP Hash 算法。 +// +// 测试不同 IP 数量下的哈希性能。 +func BenchmarkStreamIPHashWithDifferentIPs(b *testing.B) { + testCases := []struct { + name string + ipCount int + targetCount int + }{ + {"10_ips_3_targets", 10, 3}, + {"100_ips_3_targets", 100, 3}, + {"1000_ips_3_targets", 1000, 3}, + {"100_ips_10_targets", 100, 10}, + } + + for _, tc := range testCases { + b.Run(tc.name, func(b *testing.B) { + targets := make([]*Target, tc.targetCount) + for i := 0; i < tc.targetCount; i++ { + targets[i] = &Target{ + addr: fmt.Sprintf("backend%d:8080", i), + weight: 1, + } + targets[i].healthy.Store(true) + } + + // 预生成 IP 列表 + ips := make([]string, tc.ipCount) + for i := 0; i < tc.ipCount; i++ { + ips[i] = fmt.Sprintf("192.168.%d.%d", i/256, i%256) + } + + balancer := newIPHash().(*ipHash) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + ip := ips[i%tc.ipCount] + _ = balancer.SelectByIP(targets, ip) + } + }) + } +}