Web/bench/real_bench_test.go

527 lines
13 KiB
Go

package bench_test
import (
"bytes"
"io"
"net"
"net/http"
"strconv"
"strings"
"sync"
"testing"
"time"
web "git.sharkk.net/Go/Web"
)
// startTestServer starts a server for benchmarking on a random port
func startTestServer(b *testing.B) (string, web.Server) {
s := web.NewServer()
// Setup routes
s.Get("/", func(ctx web.Context) error {
return ctx.String("Hello, World!")
})
s.Get("/json", func(ctx web.Context) error {
ctx.Response().SetHeader("Content-Type", "application/json")
return ctx.String(`{"message":"Hello, World!","code":200,"success":true}`)
})
s.Post("/echo", func(ctx web.Context) error {
body := ctx.Request().Body()
return ctx.Bytes(body)
})
s.Get("/users/:id/posts/:postId", func(ctx web.Context) error {
userId := ctx.Request().Param("id")
postId := ctx.Request().Param("postId")
return ctx.String(userId + ":" + postId)
})
s.Get("/middleware-test", func(ctx web.Context) error {
return ctx.String("OK")
})
s.Get("/headers", func(ctx web.Context) error {
ctx.Response().SetHeader("X-Test-1", "Value1")
ctx.Response().SetHeader("X-Test-2", "Value2")
ctx.Response().SetHeader("X-Test-3", "Value3")
ctx.Response().SetHeader("Content-Type", "text/plain")
return ctx.String("Headers set")
})
s.Post("/submit", func(ctx web.Context) error {
return ctx.String("Received " + string(ctx.Request().Body()))
})
// Add middleware for middleware test
for i := 0; i < 5; i++ {
s.Use(func(ctx web.Context) error {
return ctx.Next()
})
}
// Find a free port
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
if err != nil {
b.Fatalf("Failed to resolve TCP address: %v", err)
}
listener, err := net.ListenTCP("tcp", addr)
if err != nil {
b.Fatalf("Failed to listen on TCP: %v", err)
}
port := listener.Addr().(*net.TCPAddr).Port
listener.Close()
serverAddr := "localhost:" + strconv.Itoa(port)
// Start the server in a goroutine
var wg sync.WaitGroup
wg.Add(1)
go func() {
wg.Done()
s.Run(serverAddr)
}()
// Wait for server to start
wg.Wait()
time.Sleep(100 * time.Millisecond)
return serverAddr, s
}
// BenchmarkRealStaticGetRequest measures performance of real HTTP GET request
func BenchmarkRealStaticGetRequest(b *testing.B) {
serverAddr, _ := startTestServer(b)
url := "http://" + serverAddr + "/"
b.ResetTimer()
for i := 0; i < b.N; i++ {
resp, err := http.Get(url)
if err != nil {
b.Fatalf("Request failed: %v", err)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
b.Fatalf("Failed to read response: %v", err)
}
resp.Body.Close()
if resp.StatusCode != 200 || string(body) != "Hello, World!" {
b.Fatalf("Invalid response: status=%d, body=%s", resp.StatusCode, body)
}
}
}
// BenchmarkRealJSONResponse measures performance of real HTTP JSON response
func BenchmarkRealJSONResponse(b *testing.B) {
serverAddr, _ := startTestServer(b)
url := "http://" + serverAddr + "/json"
b.ResetTimer()
for i := 0; i < b.N; i++ {
resp, err := http.Get(url)
if err != nil {
b.Fatalf("Request failed: %v", err)
}
_, err = io.ReadAll(resp.Body)
if err != nil {
b.Fatalf("Failed to read response: %v", err)
}
resp.Body.Close()
if resp.StatusCode != 200 {
b.Fatalf("Invalid status: %d", resp.StatusCode)
}
}
}
// BenchmarkRealPostWithBody measures performance of real HTTP POST request with body
func BenchmarkRealPostWithBody(b *testing.B) {
serverAddr, _ := startTestServer(b)
url := "http://" + serverAddr + "/echo"
requestBody := strings.Repeat("Hello, World! ", 10)
b.ResetTimer()
for i := 0; i < b.N; i++ {
resp, err := http.Post(url, "text/plain", strings.NewReader(requestBody))
if err != nil {
b.Fatalf("Request failed: %v", err)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
b.Fatalf("Failed to read response: %v", err)
}
resp.Body.Close()
if resp.StatusCode != 200 || string(body) != requestBody {
b.Fatalf("Invalid response\nstatus=%d\nbody=%s", resp.StatusCode, body)
}
}
}
// BenchmarkRealRouteParams measures performance of real HTTP route parameter handling
func BenchmarkRealRouteParams(b *testing.B) {
serverAddr, _ := startTestServer(b)
url := "http://" + serverAddr + "/users/123/posts/456"
b.ResetTimer()
for i := 0; i < b.N; i++ {
resp, err := http.Get(url)
if err != nil {
b.Fatalf("Request failed: %v", err)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
b.Fatalf("Failed to read response: %v", err)
}
resp.Body.Close()
if resp.StatusCode != 200 || string(body) != "123:456" {
b.Fatalf("Invalid response: status=%d, body=%s", resp.StatusCode, body)
}
}
}
// BenchmarkRealHeaders measures real HTTP header handling performance
func BenchmarkRealHeaders(b *testing.B) {
serverAddr, _ := startTestServer(b)
url := "http://" + serverAddr + "/headers"
client := &http.Client{}
b.ResetTimer()
for i := 0; i < b.N; i++ {
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("User-Agent", "Benchmark")
req.Header.Add("Accept", "*/*")
req.Header.Add("Authorization", "Bearer token12345")
resp, err := client.Do(req)
if err != nil {
b.Fatalf("Request failed: %v", err)
}
_, err = io.ReadAll(resp.Body)
if err != nil {
b.Fatalf("Failed to read response: %v", err)
}
resp.Body.Close()
if resp.StatusCode != 200 || resp.Header.Get("X-Test-1") != "Value1" {
b.Fatalf("Invalid response: status=%d", resp.StatusCode)
}
}
}
// BenchmarkRealParallelRequests measures real HTTP performance under concurrent load
func BenchmarkRealParallelRequests(b *testing.B) {
serverAddr, _ := startTestServer(b)
baseURL := "http://" + serverAddr
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
client := &http.Client{}
counter := 0
for pb.Next() {
var resp *http.Response
var err error
switch counter % 3 {
case 0:
resp, err = client.Get(baseURL + "/")
case 1:
id := strconv.Itoa(counter & 0xff)
resp, err = client.Get(baseURL + "/users/" + id + "/posts/456")
case 2:
data := "data" + strconv.Itoa(counter&0xff)
resp, err = client.Post(baseURL+"/submit", "text/plain", bytes.NewBufferString(data))
}
if err != nil {
b.Fatalf("Request failed: %v", err)
}
_, _ = io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode != 200 {
b.Fatalf("Invalid response: status=%d", resp.StatusCode)
}
counter++
}
})
}
// startNetHTTPTestServer starts a standard net/http server for benchmarking
func startNetHTTPTestServer(b *testing.B) (string, *http.Server) {
mux := http.NewServeMux()
// Setup routes
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
w.Write([]byte("Hello, World!"))
})
mux.HandleFunc("/json", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"message":"Hello, World!","code":200,"success":true}`))
})
mux.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
body, _ := io.ReadAll(r.Body)
w.Write(body)
})
mux.HandleFunc("/users/", func(w http.ResponseWriter, r *http.Request) {
parts := strings.Split(r.URL.Path, "/")
if len(parts) != 5 || parts[1] != "users" || parts[3] != "posts" || parts[0] != "" {
http.NotFound(w, r)
return
}
userId := parts[2]
postId := parts[4]
w.Write([]byte(userId + ":" + postId))
})
mux.HandleFunc("/middleware-test", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
})
mux.HandleFunc("/headers", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Test-1", "Value1")
w.Header().Set("X-Test-2", "Value2")
w.Header().Set("X-Test-3", "Value3")
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte("Headers set"))
})
mux.HandleFunc("/submit", func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
body, _ := io.ReadAll(r.Body)
w.Write([]byte("Received " + string(body)))
})
// Find a free port
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
if err != nil {
b.Fatalf("Failed to resolve TCP address: %v", err)
}
listener, err := net.ListenTCP("tcp", addr)
if err != nil {
b.Fatalf("Failed to listen on TCP: %v", err)
}
port := listener.Addr().(*net.TCPAddr).Port
serverAddr := "localhost:" + strconv.Itoa(port)
// Start the server in a goroutine
server := &http.Server{
Addr: serverAddr,
Handler: mux,
}
var wg sync.WaitGroup
wg.Add(1)
go func() {
wg.Done()
server.Serve(listener)
}()
// Wait for server to start
wg.Wait()
time.Sleep(100 * time.Millisecond)
return serverAddr, server
}
// BenchmarkNetHTTPStaticGetRequest measures performance of net/http static GET request
func BenchmarkNetHTTPStaticGetRequest(b *testing.B) {
serverAddr, _ := startNetHTTPTestServer(b)
url := "http://" + serverAddr + "/"
b.ResetTimer()
for i := 0; i < b.N; i++ {
resp, err := http.Get(url)
if err != nil {
b.Fatalf("Request failed: %v", err)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
b.Fatalf("Failed to read response: %v", err)
}
resp.Body.Close()
if resp.StatusCode != 200 || string(body) != "Hello, World!" {
b.Fatalf("Invalid response: status=%d, body=%s", resp.StatusCode, body)
}
}
}
// BenchmarkNetHTTPJSONResponse measures performance of net/http JSON response
func BenchmarkNetHTTPJSONResponse(b *testing.B) {
serverAddr, _ := startNetHTTPTestServer(b)
url := "http://" + serverAddr + "/json"
b.ResetTimer()
for i := 0; i < b.N; i++ {
resp, err := http.Get(url)
if err != nil {
b.Fatalf("Request failed: %v", err)
}
_, err = io.ReadAll(resp.Body)
if err != nil {
b.Fatalf("Failed to read response: %v", err)
}
resp.Body.Close()
if resp.StatusCode != 200 {
b.Fatalf("Invalid status: %d", resp.StatusCode)
}
}
}
// BenchmarkNetHTTPPostWithBody measures performance of net/http POST request with body
func BenchmarkNetHTTPPostWithBody(b *testing.B) {
serverAddr, _ := startNetHTTPTestServer(b)
url := "http://" + serverAddr + "/echo"
requestBody := strings.Repeat("Hello, World! ", 10)
b.ResetTimer()
for i := 0; i < b.N; i++ {
resp, err := http.Post(url, "text/plain", strings.NewReader(requestBody))
if err != nil {
b.Fatalf("Request failed: %v", err)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
b.Fatalf("Failed to read response: %v", err)
}
resp.Body.Close()
if resp.StatusCode != 200 || string(body) != requestBody {
b.Fatalf("Invalid response\nstatus=%d\nbody=%s", resp.StatusCode, body)
}
}
}
// BenchmarkNetHTTPRouteParams measures performance of net/http route parameter handling
func BenchmarkNetHTTPRouteParams(b *testing.B) {
serverAddr, _ := startNetHTTPTestServer(b)
url := "http://" + serverAddr + "/users/123/posts/456"
b.ResetTimer()
for i := 0; i < b.N; i++ {
resp, err := http.Get(url)
if err != nil {
b.Fatalf("Request failed: %v", err)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
b.Fatalf("Failed to read response: %v", err)
}
resp.Body.Close()
if resp.StatusCode != 200 || string(body) != "123:456" {
b.Fatalf("Invalid response: status=%d, body=%s", resp.StatusCode, body)
}
}
}
// BenchmarkNetHTTPHeaders measures net/http header handling performance
func BenchmarkNetHTTPHeaders(b *testing.B) {
serverAddr, _ := startNetHTTPTestServer(b)
url := "http://" + serverAddr + "/headers"
client := &http.Client{}
b.ResetTimer()
for i := 0; i < b.N; i++ {
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("User-Agent", "Benchmark")
req.Header.Add("Accept", "*/*")
req.Header.Add("Authorization", "Bearer token12345")
resp, err := client.Do(req)
if err != nil {
b.Fatalf("Request failed: %v", err)
}
_, err = io.ReadAll(resp.Body)
if err != nil {
b.Fatalf("Failed to read response: %v", err)
}
resp.Body.Close()
if resp.StatusCode != 200 || resp.Header.Get("X-Test-1") != "Value1" {
b.Fatalf("Invalid response: status=%d", resp.StatusCode)
}
}
}
// BenchmarkNetHTTPParallelRequests measures net/http performance under concurrent load
func BenchmarkNetHTTPParallelRequests(b *testing.B) {
serverAddr, _ := startNetHTTPTestServer(b)
baseURL := "http://" + serverAddr
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
client := &http.Client{}
counter := 0
for pb.Next() {
var resp *http.Response
var err error
switch counter % 3 {
case 0:
resp, err = client.Get(baseURL + "/")
case 1:
id := strconv.Itoa(counter & 0xff)
resp, err = client.Get(baseURL + "/users/" + id + "/posts/456")
case 2:
data := "data" + strconv.Itoa(counter&0xff)
resp, err = client.Post(baseURL+"/submit", "text/plain", bytes.NewBufferString(data))
}
if err != nil {
b.Fatalf("Request failed: %v", err)
}
_, _ = io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode != 200 {
b.Fatalf("Invalid response: status=%d", resp.StatusCode)
}
counter++
}
})
}