1name: Benchmarks
2
3on:
4 pull_request:
5 branches: [master]
6 push:
7 branches: [master]
8
9permissions:
10 contents: read
11
12jobs:
13 benchmark:
14 runs-on: ubuntu-latest
15 timeout-minutes: 30
16 steps:
17 - name: Checkout PR
18 uses: actions/checkout@v6
19 with:
20 fetch-depth: 0
21
22 - name: Set up Go
23 uses: actions/setup-go@v6
24 with:
25 go-version: "1.26.4"
26
27 - name: Install system dependencies
28 run: sudo apt-get update && sudo apt-get install -y libpcsclite-dev
29
30 - name: Install benchstat
31 run: go install golang.org/x/perf/cmd/benchstat@latest
32
33 - name: Resolve base ref
34 id: base
35 run: |
36 if [ "${{ github.event_name }}" = "pull_request" ]; then
37 echo "ref=${{ github.event.pull_request.base.sha }}" >> "$GITHUB_OUTPUT"
38 else
39 echo "ref=${{ github.event.before }}" >> "$GITHUB_OUTPUT"
40 fi
41
42 - name: Benchmark PR
43 run: |
44 go test -run=^$ -bench=. -benchmem -benchtime=3x -count=6 ./backend/ ./tui/ \
45 | tee new.txt
46
47 - name: Checkout base
48 run: git checkout ${{ steps.base.outputs.ref }}
49
50 - name: Benchmark base
51 run: |
52 go test -run=^$ -bench=. -benchmem -benchtime=3x -count=6 ./backend/ ./tui/ \
53 | tee old.txt || echo "base benchmarks failed" > old.txt
54
55 - name: Restore PR checkout
56 run: git checkout ${{ github.sha }}
57
58 - name: Compare with benchstat
59 run: |
60 set +e
61 benchstat old.txt new.txt | tee benchstat.txt
62
63 - name: Classify result
64 run: |
65 python3 - <<'PY' > verdict.txt
66 import re
67 worse, better = 0, 0
68 with open("benchstat.txt") as f:
69 for line in f:
70 m = re.search(r"([-+]?\d+\.\d+)%", line)
71 if not m:
72 continue
73 delta = float(m.group(1))
74 if "ns/op" in line or "B/op" in line or "allocs/op" in line:
75 if delta > 3:
76 worse += 1
77 elif delta < -3:
78 better += 1
79 status = "neutral"
80 if worse > 0 and worse >= better:
81 status = "regression"
82 elif better > 0:
83 status = "improvement"
84 print(f"status={status}")
85 print(f"worse={worse}")
86 print(f"better={better}")
87 PY
88 cat verdict.txt
89
90 - name: Record PR metadata
91 if: github.event_name == 'pull_request'
92 run: |
93 echo "${{ github.event.pull_request.number }}" > pr-number.txt
94
95 - name: Upload artifacts
96 if: always()
97 uses: actions/upload-artifact@v7
98 with:
99 name: benchmarks
100 path: |
101 old.txt
102 new.txt
103 benchstat.txt
104 verdict.txt
105 pr-number.txt
106 if-no-files-found: ignore