test: add TLS performance matrix

This commit is contained in:
2026-09-21 05:13:51 +08:00
parent 847621d218
commit e6c82a01fe
5 changed files with 133 additions and 15 deletions

View File

@@ -5,11 +5,18 @@ package main
import (
"bufio"
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"math/big"
"math/bits"
"net"
"net/http"
@@ -71,6 +78,7 @@ type report struct {
Platform string `json:"platform"`
GOMAXPROCS int `json:"gomaxprocs"`
Duration string `json:"duration"`
TLS bool `json:"tls"`
Notes []string `json:"notes"`
Results []result `json:"results"`
}
@@ -102,8 +110,38 @@ func (c *trackedConn) Close() error {
return err
}
func testTLSConfig() (*tls.Config, error) {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
}
serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 120))
if err != nil {
return nil, err
}
now := time.Now()
template := &x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{CommonName: "localhost"},
NotBefore: now.Add(-time.Minute),
NotAfter: now.Add(time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
DNSNames: []string{"localhost"},
}
der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
if err != nil {
return nil, err
}
return &tls.Config{
Certificates: []tls.Certificate{{Certificate: [][]byte{der}, PrivateKey: key}},
MinVersion: tls.VersionTLS12,
}, nil
}
func main() {
serverMode := flag.String("server", "", "internal server subprocess: nethttp or native")
tlsEnabled := flag.Bool("tls", false, "run the comparison over TLS")
duration := flag.Duration("duration", 2*time.Second, "measured duration per traffic case")
concurrencyText := flag.String("concurrency", "1,32,128", "traffic concurrency levels")
idleText := flag.String("idle", "128,1024", "established idle Keep-Alive connection levels")
@@ -112,9 +150,9 @@ func main() {
flag.Parse()
var err error
if *serverMode != "" {
err = serve(*serverMode)
err = serve(*serverMode, *tlsEnabled)
} else {
err = compare(*duration, *concurrencyText, *idleText, *runs, *output)
err = compare(*duration, *concurrencyText, *idleText, *runs, *output, *tlsEnabled)
}
if err != nil {
fmt.Fprintln(os.Stderr, err)
@@ -122,12 +160,21 @@ func main() {
}
}
func serve(adapter string) error {
func serve(adapter string, tlsEnabled bool) error {
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return err
}
tracked := &trackedListener{Listener: listener}
var serveListener net.Listener = tracked
if tlsEnabled {
config, configErr := testTLSConfig()
if configErr != nil {
listener.Close()
return configErr
}
serveListener = tls.NewListener(tracked, config)
}
application := omni.New()
application.GET("/health", func(c *omni.Context) error {
return c.OK(map[string]string{"status": "ok"})
@@ -140,7 +187,7 @@ func serve(adapter string) error {
server.MaxConnections = 8192
server.MaxConcurrentRequests = 4096
shutdown = server.Shutdown
go func() { done <- server.Serve(context.Background(), tracked) }()
go func() { done <- server.Serve(context.Background(), serveListener) }()
case "nethttp":
server := &http.Server{
Handler: application, ReadHeaderTimeout: 5 * time.Second,
@@ -149,7 +196,7 @@ func serve(adapter string) error {
}
shutdown = server.Shutdown
go func() {
err := server.Serve(tracked)
err := server.Serve(serveListener)
if errors.Is(err, http.ErrServerClosed) {
err = nil
}
@@ -204,18 +251,27 @@ type child struct {
encoder *json.Encoder
decoder *json.Decoder
address string
tls bool
cancel context.CancelFunc
closed bool
}
func startChild(adapter string) (*child, error) {
return startChildMode(adapter, false)
}
func startChildMode(adapter string, tlsEnabled bool) (*child, error) {
executable, err := os.Executable()
if err != nil {
return nil, err
}
// This bounds even a broken server or protocol read; it is not a load duration.
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
c := &child{cmd: exec.CommandContext(ctx, executable, "--server", adapter), cancel: cancel}
args := []string{"--server", adapter}
if tlsEnabled {
args = append(args, "--tls")
}
c := &child{cmd: exec.CommandContext(ctx, executable, args...), cancel: cancel, tls: tlsEnabled}
c.cmd.Stderr = os.Stderr
if c.input, err = c.cmd.StdinPipe(); err != nil {
cancel()
@@ -297,7 +353,15 @@ func (h *histogram) percentile(count uint64, percent uint64) float64 {
}
func exchange(client *http.Client, address string) error {
req, err := http.NewRequest(http.MethodGet, "http://"+address+"/health", nil)
return exchangeMode(client, address, false)
}
func exchangeMode(client *http.Client, address string, tlsEnabled bool) error {
scheme := "http"
if tlsEnabled {
scheme = "https"
}
req, err := http.NewRequest(http.MethodGet, scheme+"://"+address+"/health", nil)
if err != nil {
return err
}
@@ -316,15 +380,19 @@ func exchange(client *http.Client, address string) error {
func traffic(c *child, concurrency int, duration time.Duration) (result, error) {
r := result{Scenario: "traffic", Concurrency: concurrency}
client := &http.Client{Transport: &http.Transport{
transport := &http.Transport{
MaxIdleConns: concurrency, MaxIdleConnsPerHost: concurrency,
MaxConnsPerHost: concurrency, IdleConnTimeout: time.Minute,
}, Timeout: 5 * time.Second}
}
if c.tls {
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} // test certificate is self-signed
}
client := &http.Client{Transport: transport, Timeout: 5 * time.Second}
defer client.CloseIdleConnections()
// Warm every worker before forcing a baseline GC in the server.
warmErrors := make(chan error, concurrency)
for i := 0; i < concurrency; i++ {
go func() { warmErrors <- exchange(client, c.address) }()
go func() { warmErrors <- exchangeMode(client, c.address, c.tls) }()
}
for i := 0; i < concurrency; i++ {
if err := <-warmErrors; err != nil {
@@ -351,7 +419,7 @@ func traffic(c *child, concurrency int, duration time.Duration) (result, error)
<-start
for time.Now().Before(deadline) {
began := time.Now()
err := exchange(client, c.address)
err := exchangeMode(client, c.address, c.tls)
w.latencies.add(time.Since(began))
w.count++
if err != nil {
@@ -438,6 +506,10 @@ func idle(c *child, count int) (result, error) {
if err != nil {
return r, err
}
if c.tls {
secure := tls.Client(conn, &tls.Config{InsecureSkipVerify: true, ServerName: "localhost"}) // test certificate is self-signed
conn = secure
}
connections = append(connections, conn)
conn.SetDeadline(time.Now().Add(5 * time.Second))
if _, err := io.WriteString(conn, "GET /health HTTP/1.1\r\nHost: localhost\r\nX-Request-ID: perfCheck\r\n\r\n"); err != nil {
@@ -505,7 +577,7 @@ func levels(text string) ([]int, error) {
return values, nil
}
func compare(duration time.Duration, concurrencyText, idleText string, count int, output string) error {
func compare(duration time.Duration, concurrencyText, idleText string, count int, output string, tlsEnabled bool) error {
concurrency, err := levels(concurrencyText)
if err != nil {
return err
@@ -519,9 +591,9 @@ func compare(duration time.Duration, concurrencyText, idleText string, count int
}
report := report{
CreatedAt: time.Now().UTC().Format(time.RFC3339), GoVersion: runtime.Version(),
Platform: runtime.GOOS + "/" + runtime.GOARCH, GOMAXPROCS: runtime.GOMAXPROCS(0), Duration: duration.String(),
Platform: runtime.GOOS + "/" + runtime.GOARCH, GOMAXPROCS: runtime.GOMAXPROCS(0), Duration: duration.String(), TLS: tlsEnabled,
Notes: []string{
"Loopback HTTP/1.1 health response, fixed requestId, no TLS, access logs or database.",
"Loopback HTTP/1.1 health response, fixed requestId, optional TLS, no access logs or database.",
"Each case starts a fresh server process; load client runs in the parent process on the same machine.",
"Closed-loop concurrency; throughput is successful responses / actual elapsed time, including drain.",
"Latency percentiles include failures and are histogram upper bounds with <=6.25% rounding.",
@@ -543,7 +615,7 @@ func compare(duration time.Duration, concurrencyText, idleText string, count int
}{{"traffic", concurrency}, {"idle", idleLevels}} {
for _, level := range scenario.levels {
for _, adapter := range adapters {
c, err := startChild(adapter)
c, err := startChildMode(adapter, tlsEnabled)
if err != nil {
return err
}