v0.7.0 4c16dd8

quic_performance_benchmark_test.go RawHistoryFolder

1// This file holds the documentable QUIC performance benchmarks. They reuse the
2// harness in perf_evidence_test.go: the real internal/adapters/quic transport
3// behind a [quicnettest.Proxy] and a real sessionwire conversation whose
4// representative exchange is "Input -> valid Output -> Ack".
5//
6// Each benchmark reports latency percentiles (p50/p95/p99/max) in ns/op,
7// allocation counters via -benchmem, process CPU per exchange (getrusage),
8// proxy-carried datagram bytes per exchange, retained heap, and goroutines. Run
9// them isolated so the process-wide CPU figure stays meaningful:
10//
11//	go test ./internal/adapters/quicnettest -run '^$' -bench BenchmarkRealQUICExchange -benchmem -benchtime 50x
12//	go test ./internal/adapters/quicnettest -run '^$' -bench BenchmarkRealQUICBlackoutRecovery -benchtime 10x
13package quicnettest_test
14
15import (
16	"runtime"
17	"testing"
18	"time"
19
20	"github.com/bnema/vev/internal/adapters/quicnettest"
21	"github.com/bnema/vev/internal/ports"
22)
23
24// BenchmarkRealQUICExchange measures the representative typed exchange for both
25// representative profiles.
26func BenchmarkRealQUICExchange(b *testing.B) {
27	for _, profile := range perfProfiles() {
28		b.Run(profile.name, func(b *testing.B) {
29			benchmarkPerfExchange(b, profile.link)
30		})
31	}
32}
33
34func benchmarkPerfExchange(b *testing.B, link quicnettest.LinkConfig) {
35	if testing.Short() {
36		b.Skip("real QUIC benchmark is skipped in short mode")
37	}
38	baselineGoroutines := runtime.NumGoroutine()
39	fx := startPerfFixture(b, link)
40	client, server := startPerfConversation(b, fx)
41	if _, err := perfExchange(client, server, 1); err != nil {
42		b.Fatalf("warmup exchange: %v", err)
43	}
44
45	latencies := make([]time.Duration, 0, b.N)
46	var appBytes uint64
47	wireBefore := fx.wireBytes()
48	cpuBefore := readPerfCPU()
49	b.ReportAllocs()
50	for i := 0; b.Loop(); i++ {
51		duration, err := perfExchange(client, server, uint64(1000+i))
52		if err != nil {
53			b.Fatalf("exchange %d: %v", i, err)
54		}
55		latencies = append(latencies, duration)
56		appBytes += perfInputPayload + perfOutputPayload
57	}
58	cpuAfter := readPerfCPU()
59	wireAfter := fx.wireBytes()
60
61	runtime.GC()
62	retained := readPerfMem()
63
64	reportPerfBenchmarkMetrics(b, latencies, cpuAfter.total()-cpuBefore.total(), wireAfter-wireBefore, appBytes)
65	b.ReportMetric(float64(retained.heapAlloc), "heap-bytes")
66	b.ReportMetric(float64(retained.goroutines), "goroutines")
67
68	perfBoundedCleanup(b, fx, client, server, baselineGoroutines)
69}
70
71// BenchmarkRealQUICBlackoutRecovery measures the latency from a blackout
72// clearing until an exchange that was in flight during the blackout completes,
73// on the train profile. Run with a small -benchtime; each iteration waits for
74// perfBlackoutWindow plus QUIC recovery.
75func BenchmarkRealQUICBlackoutRecovery(b *testing.B) {
76	benchmarkPerfRecovery(b, perfBlackoutRecoveryOnce)
77}
78
79// BenchmarkRealQUICNATRebindRecovery measures the latency from a proxy NAT
80// rebinding until the next exchange completes, on the train profile.
81func BenchmarkRealQUICNATRebindRecovery(b *testing.B) {
82	benchmarkPerfRecovery(b, perfRebindRecoveryOnce)
83}
84
85func benchmarkPerfRecovery(b *testing.B, recoverOnce func(*perfFixture, ports.ClientConnection, ports.ServerConnection, uint64) (time.Duration, error)) {
86	if testing.Short() {
87		b.Skip("real QUIC recovery benchmark is skipped in short mode")
88	}
89	baselineGoroutines := runtime.NumGoroutine()
90	fx := startPerfFixture(b, trainProfile())
91	client, server := startPerfConversation(b, fx)
92	if _, err := perfExchange(client, server, 1); err != nil {
93		b.Fatalf("warmup exchange: %v", err)
94	}
95
96	latencies := make([]time.Duration, 0, b.N)
97	b.ReportAllocs()
98	for i := 0; b.Loop(); i++ {
99		duration, err := recoverOnce(fx, client, server, uint64(1000+i))
100		if err != nil {
101			b.Fatalf("recovery %d: %v", i, err)
102		}
103		latencies = append(latencies, duration)
104	}
105	reportPerfBenchmarkMetrics(b, latencies, 0, 0, 0)
106
107	perfBoundedCleanup(b, fx, client, server, baselineGoroutines)
108}
109
110// reportPerfBenchmarkMetrics emits the custom per-op metrics shared by every
111// benchmark in this file. cpuTotal, wireBytes, and appBytes are totals for the
112// whole measured loop; a zero total disables that metric, which is what the
113// recovery benchmarks want because their dominant cost is QUIC backoff, not
114// throughput.
115func reportPerfBenchmarkMetrics(b *testing.B, latencies []time.Duration, cpuTotal time.Duration, wireBytes, appBytes uint64) {
116	b.Helper()
117	latency := perfLatencyFor(latencies)
118	b.ReportMetric(float64(latency.P50.Nanoseconds()), "p50-ns/op")
119	b.ReportMetric(float64(latency.P95.Nanoseconds()), "p95-ns/op")
120	b.ReportMetric(float64(latency.P99.Nanoseconds()), "p99-ns/op")
121	b.ReportMetric(float64(latency.Max.Nanoseconds()), "max-ns/op")
122
123	operations := float64(len(latencies))
124	if operations == 0 {
125		return
126	}
127	if cpuTotal > 0 {
128		b.ReportMetric(float64(cpuTotal.Nanoseconds())/operations, "cpu-ns/op")
129	}
130	if wireBytes > 0 {
131		b.ReportMetric(float64(wireBytes)/operations, "wire-bytes/op")
132	}
133	if appBytes > 0 {
134		b.ReportMetric(float64(appBytes)/operations, "app-bytes/op")
135	}
136}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close