Reticulum-Go/scripts/ci/bench-gate.sh
2026-08-13 10:45:58 -05:00

97 lines
2.7 KiB
Bash
Executable file

#!/usr/bin/env sh
# Run load/concurrency benchmarks and fail when results exceed thresholds.
# Usage: bench-gate.sh
set -eu
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
cd "$ROOT"
THRESHOLDS="${BENCH_THRESHOLDS:-$ROOT/scripts/ci/bench-thresholds.tsv}"
COUNT="${BENCH_COUNT:-3}"
BENCHTIME="${BENCH_TIME:-1s}"
PACKAGES="./pkg/packet ./pkg/backbone ./pkg/transport ./pkg/identity ./pkg/cryptography ./pkg/ifac ./pkg/interfaces"
BENCH_RE='BenchmarkPacketThroughput|BenchmarkHubEcho|BenchmarkSimConcurrentLineRelay|BenchmarkSimLineRelayThroughput|BenchmarkHasPath_ParallelMixed|BenchmarkHandleAnnouncePacket_DebugCritical|BenchmarkSimIFACMaskUnmask|BenchmarkIdentityHashCached|BenchmarkDeriveKey|BenchmarkRememberUnchanged|BenchmarkTCPHDLCDecoderBurst|BenchmarkHandlePacketCopy'
if [ ! -f "$THRESHOLDS" ]; then
echo "bench-gate: thresholds file not found: $THRESHOLDS" >&2
exit 1
fi
OUT="$(mktemp)"
trap 'rm -f "$OUT"' EXIT
go test -run=^$ -bench="$BENCH_RE" -benchmem -benchtime="$BENCHTIME" -count="$COUNT" $PACKAGES >"$OUT" 2>&1
awk -v thresholds="$THRESHOLDS" '
function median(vals, n, i, j, tmp) {
for (i = 1; i <= n; i++) {
for (j = i + 1; j <= n; j++) {
if (vals[i] > vals[j]) {
tmp = vals[i]; vals[i] = vals[j]; vals[j] = tmp
}
}
}
if (n % 2 == 1) return vals[(n + 1) / 2]
return (vals[n / 2] + vals[n / 2 + 1]) / 2
}
BEGIN {
while ((getline line < thresholds) > 0) {
if (line ~ /^#/ || line ~ /^[[:space:]]*$/) continue
split(line, f, "\t")
pat[f[1]] = f[2]
allocPat[f[1]] = f[3]
}
close(thresholds)
}
/^Benchmark/ {
name = $1
gsub(/-[0-9]+$/, "", name)
nsVal = 0
allocVal = 0
for (i = 1; i <= NF; i++) {
if ($i == "ns/op") nsVal = $(i - 1) + 0
if ($i == "allocs/op") allocVal = $(i - 1) + 0
}
ns[name, ++nsCount[name]] = nsVal
alloc[name, ++allocCount[name]] = allocVal
}
END {
failed = 0
for (name in nsCount) {
n = nsCount[name]
if (n == 0) continue
for (i = 1; i <= n; i++) {
nsVals[i] = ns[name, i]
allocVals[i] = alloc[name, i]
}
gotNS = median(nsVals, n)
gotAlloc = median(allocVals, n)
limitNS = 0
limitAlloc = 0
matched = ""
for (patName in pat) {
if (name ~ patName) {
limitNS = pat[patName]
limitAlloc = allocPat[patName]
matched = patName
}
}
if (matched == "") continue
if (gotNS > limitNS) {
printf "FAIL %s: %.0f ns/op > %d ns/op\n", name, gotNS, limitNS
failed = 1
} else {
printf "OK %s: %.0f ns/op (limit %d)\n", name, gotNS, limitNS
}
if (gotAlloc > limitAlloc) {
printf "FAIL %s: %.0f allocs/op > %d allocs/op\n", name, gotAlloc, limitAlloc
failed = 1
} else {
printf "OK %s: %.0f allocs/op (limit %d)\n", name, gotAlloc, limitAlloc
}
}
if (failed) exit 1
}
' "$OUT"
echo "bench-gate: all thresholds satisfied"