Files
sower/internal/admin/stats_bench_test.go
wweir defea58ac2 perf: batch byte accounting and bound gateway memory
- countingConn batches per-domain attribution (32KiB/500ms) so the relay
  hot path pays two atomics per I/O instead of a global mutex; Close drains
- Precompute OTel byte attributes to drop per-I/O allocations
- suffixtree: indexMap only for nodes with >=8 children; Match stays
  read-only for concurrent queries
- Soft Go memory limit (128MiB default, GOMEMLIMIT/SOWER_MEMORY_LIMIT_MB
  override) cuts resident memory from ~240MB to ~130MB on the default
  ad/china/gfw rule sets
- Split stats.go into snapshot/eviction/counting by responsibility
2026-08-07 12:02:26 +08:00

131 lines
2.7 KiB
Go

package admin
import (
"context"
"fmt"
"sync"
"sync/atomic"
"testing"
"time"
)
var (
benchmarkSnapshot TrafficSnapshot
benchmarkTotals Totals
)
func newBenchmarkStats(b *testing.B, domains int) *Stats {
b.Helper()
s, err := NewStats()
if err != nil {
b.Fatalf("new stats: %v", err)
}
b.Cleanup(func() {
ctx, cancel := context.WithTimeout(b.Context(), time.Second)
defer cancel()
if err := s.Metrics().Shutdown(ctx); err != nil {
b.Errorf("shutdown metrics: %v", err)
}
})
now := time.Now()
func() {
s.mu.Lock()
defer s.mu.Unlock()
for i := 0; i < domains; i++ {
domain := fmt.Sprintf("host%d.example", i)
s.domains[domain] = &domainStat{
conns: uint64(i + 1),
bytesUp: uint64(i + 1),
bytesDown: uint64(i + 1),
lastSeen: now,
bySource: map[Source]*sourceStat{
SourceHTTP: {conns: 1, bytesUp: 1, bytesDown: 1, lastSeen: now},
},
}
}
}()
return s
}
func BenchmarkStatsSnapshot(b *testing.B) {
for _, domains := range []int{1_000, 10_000, 100_000} {
b.Run(fmt.Sprintf("domains=%d", domains), func(b *testing.B) {
s := newBenchmarkStats(b, domains)
b.ReportAllocs()
b.ResetTimer()
for range b.N {
benchmarkSnapshot = s.Snapshot(DomainSortBytes, SourceAll, "")
}
})
}
}
func BenchmarkStatsTotals(b *testing.B) {
for _, domains := range []int{1_000, 10_000, 100_000} {
b.Run(fmt.Sprintf("domains=%d", domains), func(b *testing.B) {
s := newBenchmarkStats(b, domains)
b.ReportAllocs()
b.ResetTimer()
for range b.N {
benchmarkTotals = s.Totals()
}
})
}
}
func BenchmarkStatsRecordConcurrentWithSnapshots(b *testing.B) {
s := newBenchmarkStats(b, 10_000)
stop := make(chan struct{})
var snapshots sync.WaitGroup
snapshots.Add(1)
go func() {
defer snapshots.Done()
ticker := time.NewTicker(time.Millisecond)
defer ticker.Stop()
for {
select {
case <-stop:
return
case <-ticker.C:
benchmarkSnapshot = s.Snapshot(DomainSortBytes, SourceAll, "")
}
}
}()
b.Cleanup(func() {
close(stop)
snapshots.Wait()
})
var next atomic.Uint64
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
i := next.Add(1) % 10_000
s.record(fmt.Sprintf("host%d.example", i), SourceHTTP, "", 0, 1, 1,
func(d *domainStat, src *sourceStat, dc *clientStat) {
d.bytesUp++
d.bytesDown++
src.bytesUp++
src.bytesDown++
})
}
})
}
func BenchmarkStatsInsertAtDomainCapacity(b *testing.B) {
s := newBenchmarkStats(b, maxTotalDomains)
var next atomic.Uint64
b.ReportAllocs()
b.ResetTimer()
for range b.N {
i := next.Add(1)
s.record(fmt.Sprintf("new%d.example", i), SourceHTTP, "", 1, 0, 0,
func(d *domainStat, src *sourceStat, dc *clientStat) {
d.conns++
src.conns++
})
}
}