-
-
Notifications
You must be signed in to change notification settings - Fork 1
219 lines (193 loc) · 8.22 KB
/
Copy pathbenchmarks.yml
File metadata and controls
219 lines (193 loc) · 8.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
name: Benchmark
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: benchmarks-${{ github.head_ref || github.ref }}
cancel-in-progress: true
defaults:
run:
shell: bash
jobs:
benchmarks:
name: run
runs-on: ubuntu-latest
# The last step posts the benchmark comparison as a pull request comment,
# and the default token is read-only, so it failed with "Resource not
# accessible by integration" — after the build and every benchmark had
# succeeded. Only pull requests comment, so main pushes stayed green and
# the job looked healthy. binary-size.yml had the identical bug and was
# fixed in #22; this workflow was missed.
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Zig
uses: pantry-pm/pantry/packages/action@235036fa0f48bae99b2293df5a3dc35c809b1777 # pinned: last SHA whose bundled typescript resolves on linux-x64
- name: Install Linux dependencies
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev
- name: Cache Zig artifacts
uses: actions/cache@v5
with:
path: |
~/.cache/zig
packages/zig/zig-cache
key: bench-${{ runner.os }}-zig-master-${{ hashFiles('packages/zig/build.zig') }}
restore-keys: |
bench-${{ runner.os }}-zig-master-
- name: First-party Zig dependencies
uses: ./.github/actions/first-party-zig-deps
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
# ---------------------------------------------------------------------
# Binary load time, A/B against main on this runner
#
# What is measured is `craft --help`: process spawn, dynamic linking and
# argument parsing. It never opens a window, so it cannot see a change in
# window or webview startup. This step used to call that "Startup Time"
# and fail pull requests on it, which is how a number that cannot move
# ended up gating the code that would move it. Real startup is
# `benchmarks/startup.bench.ts`, and it needs a display.
#
# The comparison is A/B on one runner rather than against a stored
# number, because the stored number compared machines instead of
# binaries. Measured on one unchanging binary: p50 22.9ms, p95 38.8ms,
# max 65.6ms, and the old mean-of-5-against-a-cached-number method swung
# 22ms to 32ms — a 45% spread through a gate that fired at 20%.
# Interleaving both binaries in one sitting brought the same experiment
# to 3.5% worst case.
#
# So the cache holds main's *binary*, not main's timing.
# ---------------------------------------------------------------------
- name: Build binary
working-directory: packages/zig
run: |
zig build -Doptimize=ReleaseFast
mkdir -p "$RUNNER_TEMP/bench"
cp zig-out/bin/craft "$RUNNER_TEMP/bench/craft-head"
- name: Restore main's binary
if: github.event_name == 'pull_request'
id: baseline
uses: actions/cache/restore@v4
with:
path: baseline-binary
key: benchmarks-baseline-binary-${{ github.base_ref }}
restore-keys: |
benchmarks-baseline-binary-main
- name: Compare against main
if: github.event_name == 'pull_request'
id: compare
working-directory: benchmarks
run: |
if [ ! -f "../baseline-binary/craft" ]; then
echo "No baseline binary cached yet — reporting without a comparison."
echo "compared=false" >> $GITHUB_OUTPUT
exit 0
fi
echo "compared=true" >> $GITHUB_OUTPUT
chmod +x ../baseline-binary/craft
# Redirect, then cat — deliberately not `| tee`. `$?` after a
# pipeline is the last command's status, so `... | tee file` would
# report tee's success and the gate below could never fire. Whether
# `pipefail` happens to be set is not something a gate should depend
# on.
set +e
bun run binary-load-ab.ts \
--base ../baseline-binary/craft \
--head "$RUNNER_TEMP/bench/craft-head" \
--rounds 25 > ab-output.txt 2>&1
RC=$?
set -e
cat ab-output.txt
echo "regressed=$RC" >> $GITHUB_OUTPUT
DELTA=$(python3 -c "import json;print(json.load(open('binary-load-ab.json'))['delta_percent'])")
echo "delta=$DELTA" >> $GITHUB_OUTPUT
- name: Report
run: |
echo "## Binary load time" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.compare.outputs.compared }}" = "true" ]; then
echo '```' >> $GITHUB_STEP_SUMMARY
cat benchmarks/ab-output.txt >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi
echo "Measures \`craft --help\` — process spawn, dynamic linking and argument" >> $GITHUB_STEP_SUMMARY
echo "parsing. It does **not** measure window or webview startup." >> $GITHUB_STEP_SUMMARY
- name: Save main's binary as the baseline
if: github.ref == 'refs/heads/main'
run: |
mkdir -p baseline-binary
cp "$RUNNER_TEMP/bench/craft-head" baseline-binary/craft
- name: Cache main's binary
if: github.ref == 'refs/heads/main'
uses: actions/cache/save@v4
with:
path: baseline-binary
key: benchmarks-baseline-binary-main-${{ github.sha }}
- name: Comment on PR
if: github.event_name == 'pull_request' && steps.compare.outputs.compared == 'true'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const out = fs.readFileSync('benchmarks/ab-output.txt', 'utf8');
const regressed = '${{ steps.compare.outputs.regressed }}' === '1';
const body = `## ${regressed ? '\u{1F534}' : '\u2705'} Binary load time
\`\`\`
${out.trim()}
\`\`\`
<details>
<summary>What this measures</summary>
\`craft --help\`: process spawn, dynamic linking and argument parsing.
It never opens a window, so it **cannot** see a change in window or
webview startup — real startup is \`benchmarks/startup.bench.ts\`, which
needs a display.
Both binaries are measured interleaved on this runner and compared by
p50, rather than against a number recorded on another machine. On
byte-identical binaries that method reads within ~3.5%; the old one
swung 45%.
</details>`;
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existing = comments.find(c =>
c.user.login === 'github-actions[bot]' && c.body.includes('Binary load time')
);
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner, repo: context.repo.repo,
comment_id: existing.id, body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: context.issue.number, body,
});
}
- name: Upload benchmark results
if: always()
uses: actions/upload-artifact@v4
with:
name: benchmark-results
path: |
benchmarks/ab-output.txt
benchmarks/binary-load-ab.json
if-no-files-found: ignore
retention-days: 90
- name: Fail on regression
if: steps.compare.outputs.regressed == '1'
run: |
echo "::error::Binary load time regressed by ${{ steps.compare.outputs.delta }}% against main, measured A/B on this runner."
exit 1