527 lines
13 KiB
Go
527 lines
13 KiB
Go
package bench
|
|
|
|
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++
|
|
}
|
|
})
|
|
}
|