126 lines
3.2 KiB
Go
126 lines
3.2 KiB
Go
package transport_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
omni "git.yuntuo.hk/k2safe/go-omni"
|
|
"git.yuntuo.hk/k2safe/go-omni/transport/native"
|
|
)
|
|
|
|
// Both servers use the same application, client, request validation and fixed
|
|
// concurrency. B/op includes client AND server allocations in this process;
|
|
// ns/op is aggregate throughput cost, not individual request latency.
|
|
func BenchmarkTransportTCPApplicationRequest(b *testing.B) {
|
|
for _, adapter := range []string{"nethttp", "native"} {
|
|
for _, concurrency := range []int{1, 32, 128} {
|
|
b.Run(fmt.Sprintf("%s/concurrency%d", adapter, concurrency), func(b *testing.B) {
|
|
benchmarkTCP(b, adapter, concurrency)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func benchmarkTCP(b *testing.B, adapter string, concurrency int) {
|
|
application := omni.New()
|
|
application.GET("/health", func(ctx *omni.Context) error {
|
|
return ctx.OK(map[string]string{"status": "ok"})
|
|
})
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
done := make(chan error, 1)
|
|
var shutdown func(context.Context) error
|
|
if adapter == "native" {
|
|
server := native.New(application)
|
|
server.MaxConnections = 1024
|
|
server.MaxConcurrentRequests = 1024
|
|
shutdown = server.Shutdown
|
|
go func() { done <- server.Serve(context.Background(), listener) }()
|
|
} else {
|
|
server := &http.Server{
|
|
Handler: application, ReadHeaderTimeout: 5 * time.Second,
|
|
ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second,
|
|
IdleTimeout: time.Minute, MaxHeaderBytes: 1 << 20,
|
|
}
|
|
shutdown = server.Shutdown
|
|
go func() {
|
|
err := server.Serve(listener)
|
|
if err == http.ErrServerClosed {
|
|
err = nil
|
|
}
|
|
done <- err
|
|
}()
|
|
}
|
|
client := &http.Client{Transport: &http.Transport{
|
|
MaxIdleConns: 1024,
|
|
MaxIdleConnsPerHost: 1024,
|
|
MaxConnsPerHost: concurrency,
|
|
IdleConnTimeout: time.Minute,
|
|
}, Timeout: 5 * time.Second}
|
|
b.Cleanup(func() {
|
|
client.CloseIdleConnections()
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
if err := shutdown(ctx); err != nil {
|
|
b.Error(err)
|
|
}
|
|
select {
|
|
case err := <-done:
|
|
if err != nil {
|
|
b.Error(err)
|
|
}
|
|
case <-ctx.Done():
|
|
b.Error("server did not stop")
|
|
}
|
|
})
|
|
endpoint := "http://" + listener.Addr().String() + "/health"
|
|
request := func() error {
|
|
response, err := client.Get(endpoint)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, readErr := io.Copy(io.Discard, response.Body)
|
|
closeErr := response.Body.Close()
|
|
if response.StatusCode != http.StatusOK || response.Header.Get("X-Request-ID") == "" {
|
|
return fmt.Errorf("unexpected response: status=%d", response.StatusCode)
|
|
}
|
|
if readErr != nil {
|
|
return readErr
|
|
}
|
|
return closeErr
|
|
}
|
|
if err := request(); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
errors := make(chan error, concurrency)
|
|
var workers sync.WaitGroup
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for worker := 0; worker < concurrency; worker++ {
|
|
workers.Add(1)
|
|
go func(worker int) {
|
|
defer workers.Done()
|
|
for index := worker; index < b.N; index += concurrency {
|
|
if err := request(); err != nil {
|
|
errors <- err
|
|
return
|
|
}
|
|
}
|
|
}(worker)
|
|
}
|
|
workers.Wait()
|
|
b.StopTimer()
|
|
close(errors)
|
|
for err := range errors {
|
|
b.Error(err)
|
|
}
|
|
}
|