release: prepare v0.6.0-preview

This commit is contained in:
2026-09-22 01:38:42 +08:00
parent 9c9c8ab59a
commit c5e415ca52
18 changed files with 797 additions and 62 deletions

View File

@@ -36,40 +36,42 @@ import (
const expectedBody = `{"code":"OK","message":"success","data":{"status":"ok"},"requestId":"perfCheck"}`
type snapshot struct {
Address string `json:"address,omitempty"`
HeapAlloc uint64 `json:"heapAlloc"`
HeapInuse uint64 `json:"heapInuse"`
StackInuse uint64 `json:"stackInuse"`
TotalAlloc uint64 `json:"totalAlloc"`
Mallocs uint64 `json:"mallocs"`
NumGC uint32 `json:"numGc"`
Goroutines int `json:"goroutines"`
Connections int64 `json:"connections"`
Accepted int64 `json:"accepted"`
Address string `json:"address,omitempty"`
HeapAlloc uint64 `json:"heapAlloc"`
HeapInuse uint64 `json:"heapInuse"`
StackInuse uint64 `json:"stackInuse"`
TotalAlloc uint64 `json:"totalAlloc"`
Mallocs uint64 `json:"mallocs"`
NumGC uint32 `json:"numGc"`
Goroutines int `json:"goroutines"`
Connections int64 `json:"connections"`
Accepted int64 `json:"accepted"`
FileDescriptors int `json:"fileDescriptors,omitempty"`
}
type result struct {
Adapter string `json:"adapter"`
Scenario string `json:"scenario"`
Concurrency int `json:"concurrency"`
Run int `json:"run"`
Requests uint64 `json:"requests,omitempty"`
Errors uint64 `json:"errors"`
FirstError string `json:"firstError,omitempty"`
ElapsedSeconds float64 `json:"elapsedSeconds,omitempty"`
RequestsPerSecond float64 `json:"requestsPerSecond,omitempty"`
P50Millis float64 `json:"p50Millis,omitempty"`
P95Millis float64 `json:"p95Millis,omitempty"`
P99Millis float64 `json:"p99Millis,omitempty"`
ServerBytesPerRequest float64 `json:"serverBytesPerRequest,omitempty"`
ServerAllocsPerRequest float64 `json:"serverAllocsPerRequest,omitempty"`
SampledPeakHeapAlloc uint64 `json:"sampledPeakHeapAlloc,omitempty"`
IdleHeapDelta int64 `json:"idleHeapDelta,omitempty"`
IdleHeapPerConnection float64 `json:"idleHeapPerConnection,omitempty"`
Before snapshot `json:"before"`
During snapshot `json:"during"`
After snapshot `json:"after"`
AfterReclaim snapshot `json:"afterReclaim"`
Adapter string `json:"adapter"`
Scenario string `json:"scenario"`
Concurrency int `json:"concurrency"`
Run int `json:"run"`
Requests uint64 `json:"requests,omitempty"`
Errors uint64 `json:"errors"`
FirstError string `json:"firstError,omitempty"`
ElapsedSeconds float64 `json:"elapsedSeconds,omitempty"`
RequestsPerSecond float64 `json:"requestsPerSecond,omitempty"`
P50Millis float64 `json:"p50Millis,omitempty"`
P95Millis float64 `json:"p95Millis,omitempty"`
P99Millis float64 `json:"p99Millis,omitempty"`
ServerBytesPerRequest float64 `json:"serverBytesPerRequest,omitempty"`
ServerAllocsPerRequest float64 `json:"serverAllocsPerRequest,omitempty"`
SampledPeakHeapAlloc uint64 `json:"sampledPeakHeapAlloc,omitempty"`
SampledPeakFileDescriptors int `json:"sampledPeakFileDescriptors,omitempty"`
IdleHeapDelta int64 `json:"idleHeapDelta,omitempty"`
IdleHeapPerConnection float64 `json:"idleHeapPerConnection,omitempty"`
Before snapshot `json:"before"`
During snapshot `json:"during"`
After snapshot `json:"after"`
AfterReclaim snapshot `json:"afterReclaim"`
}
type report struct {
@@ -239,12 +241,21 @@ func serve(adapter string, tlsEnabled bool) error {
StackInuse: memory.StackInuse, TotalAlloc: memory.TotalAlloc,
Mallocs: memory.Mallocs, NumGC: memory.NumGC,
Goroutines: runtime.NumGoroutine(), Connections: tracked.active.Load(), Accepted: tracked.accepted.Load(),
FileDescriptors: openFileDescriptorCount(),
}); err != nil {
return err
}
}
}
func openFileDescriptorCount() int {
entries, err := os.ReadDir("/proc/self/fd")
if err != nil {
return 0
}
return len(entries)
}
type child struct {
cmd *exec.Cmd
input io.WriteCloser
@@ -265,8 +276,9 @@ func startChildMode(adapter string, tlsEnabled bool) (*child, error) {
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)
// This bounds even a broken server or protocol read; it is not a load
// duration. Keep it above the supported 30-minute soak plus drain time.
ctx, cancel := context.WithTimeout(context.Background(), 35*time.Minute)
args := []string{"--server", adapter}
if tlsEnabled {
args = append(args, "--tls")
@@ -451,6 +463,9 @@ func traffic(c *child, concurrency int, duration time.Duration) (result, error)
} else if s.HeapAlloc > r.SampledPeakHeapAlloc {
r.SampledPeakHeapAlloc = s.HeapAlloc
}
if s.FileDescriptors > r.SampledPeakFileDescriptors {
r.SampledPeakFileDescriptors = s.FileDescriptors
}
}
}
r.ElapsedSeconds = time.Since(begin).Seconds()
@@ -472,6 +487,7 @@ func traffic(c *child, concurrency int, duration time.Duration) (result, error)
return r, err
}
r.SampledPeakHeapAlloc = max(r.SampledPeakHeapAlloc, r.During.HeapAlloc)
r.SampledPeakFileDescriptors = max(r.SampledPeakFileDescriptors, r.During.FileDescriptors)
if r.Requests == 0 {
return r, errors.New("no requests completed")
}
@@ -532,6 +548,9 @@ func idle(c *child, count int) (result, error) {
if r.During.Connections != int64(count) {
return r, fmt.Errorf("idle connections: want %d, have %d", count, r.During.Connections)
}
if r.During.FileDescriptors > 0 && r.Before.FileDescriptors > 0 && r.During.FileDescriptors < r.Before.FileDescriptors+count {
return r, fmt.Errorf("idle file descriptors: want at least %d, have %d", r.Before.FileDescriptors+count, r.During.FileDescriptors)
}
r.IdleHeapDelta = int64(r.During.HeapAlloc) - int64(r.Before.HeapAlloc)
r.IdleHeapPerConnection = float64(r.IdleHeapDelta) / float64(count)
for _, conn := range connections {
@@ -586,8 +605,8 @@ func compare(duration time.Duration, concurrencyText, idleText string, count int
if err != nil {
return err
}
if count < 1 || duration < 200*time.Millisecond || duration > 30*time.Second || len(concurrency)+len(idleLevels) == 0 {
return errors.New("count must be positive, duration 200ms..30s, and at least one scenario selected")
if count < 1 || duration < 200*time.Millisecond || duration > 30*time.Minute || len(concurrency)+len(idleLevels) == 0 {
return errors.New("count must be positive, duration 200ms..30m, and at least one scenario selected")
}
report := report{
CreatedAt: time.Now().UTC().Format(time.RFC3339), GoVersion: runtime.Version(),
@@ -598,6 +617,7 @@ func compare(duration time.Duration, concurrencyText, idleText string, count int
"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.",
"Memory is server-only runtime.MemStats (not RSS or kernel socket memory); sampling interval 200ms.",
"FileDescriptors is read from /proc/self/fd on Linux and omitted on other platforms.",
"Idle memory measured after a forced GC; delta includes heap growth amortized across connections, excluding stacks.",
"After is after closing clients and one forced GC; afterReclaim is a second GC to observe pooled-cache release.",
"Native limits: 8192 connections / 4096 active requests. These cases do not benchmark overload rejection.",
@@ -638,7 +658,7 @@ func compare(duration time.Duration, concurrencyText, idleText string, count int
if scenario.name == "traffic" {
fmt.Printf("%s concurrency=%d run=%d: %.0f req/s p95=%.3fms p99=%.3fms server=%.0f B/req errors=%d\n", adapter, level, run, value.RequestsPerSecond, value.P95Millis, value.P99Millis, value.ServerBytesPerRequest, value.Errors)
} else {
fmt.Printf("%s idle=%d run=%d: server heap delta=%.2f MiB (%.0f B/conn), goroutines=%d->%d->%d errors=%d\n", adapter, level, run, float64(value.IdleHeapDelta)/(1<<20), value.IdleHeapPerConnection, value.Before.Goroutines, value.During.Goroutines, value.After.Goroutines, value.Errors)
fmt.Printf("%s idle=%d run=%d: server heap delta=%.2f MiB (%.0f B/conn), goroutines=%d->%d->%d, fds=%d->%d->%d errors=%d\n", adapter, level, run, float64(value.IdleHeapDelta)/(1<<20), value.IdleHeapPerConnection, value.Before.Goroutines, value.During.Goroutines, value.After.Goroutines, value.Before.FileDescriptors, value.During.FileDescriptors, value.After.FileDescriptors, value.Errors)
}
}
}