Repository navigation
236 lines (217 loc) · 9.36 KB
/
Copy pathbench.yml
File metadata and controls
236 lines (217 loc) · 9.36 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
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
name: bench
on:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
MARKER: "<!-- patchwork-bench -->"
jobs:
# Say a run is under way, keeping the last results below it, marked as out
# of date, until this one replaces them.
announce:
name: mark the comment out of date
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/github-script@v7
with:
script: |
const marker = process.env.MARKER
const previous = "<!-- previous -->"
const { owner, repo } = context.repo
const issue_number = context.issue.number
const head = context.payload.pull_request.head.sha.slice(0, 7)
const run = `${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`
const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number })
const existing = comments.find((c) => c.body.startsWith(marker))
// A run that was cancelled leaves its notice; keep only the results.
let last = existing?.body ?? ""
if (last.includes(previous)) last = last.slice(last.indexOf(previous) + previous.length)
else last = last.slice(marker.length)
last = last.trim().replace(/^## (.*)$/m, "### Out of date: $1")
const body = [
marker,
`## Benchmarks running for \`${head}\``,
``,
`[A new run](${run}) is under way.${last ? " The results below are from the last run, for an earlier commit." : ""}`,
previous,
``,
last,
].join("\n")
if (existing) {
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body })
} else {
await github.rest.issues.createComment({ owner, repo, issue_number, body })
}
# One runner per browser, so the browsers run at once without sharing a
# machine. On each, main and the PR run one after the other: runner numbers
# drift between runs, so a PR is only ever compared with main measured
# alongside it, in the same browser. Only Patchwork as it ships (`patchwork`
# mode, whose tests are titled "patchwork: …" or "S5 patchwork, …"): the
# other modes and the storage adapters are alternatives the bench explores,
# not what merging changes.
bench:
name: bench (${{ matrix.browser }})
runs-on: ubuntu-latest
timeout-minutes: 120
permissions:
contents: read
strategy:
fail-fast: false
matrix:
browser: [chromium, firefox, webkit]
steps:
- uses: actions/checkout@v4
with:
path: pr
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.sha }}
path: main
- uses: pnpm/action-setup@v4
with:
package_json_file: pr/package.json
run_install: false
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
cache-dependency-path: |
pr/pnpm-lock.yaml
main/pnpm-lock.yaml
- name: build main
working-directory: main
run: pnpm install --frozen-lockfile && pnpm build
- name: build the PR
working-directory: pr
run: pnpm install --frozen-lockfile && pnpm build
- name: install ${{ matrix.browser }}
run: |
(cd main/sites/bench && pnpm exec playwright install --with-deps ${{ matrix.browser }})
(cd pr/sites/bench && pnpm exec playwright install --with-deps ${{ matrix.browser }})
# A single run of a benchmark can be off by more than the PR changes it,
# so each side runs ROUNDS times, alternating, so that the runner
# slowing down partway affects both. Failed tests still record their
# rows, so every round reports whatever got measured. A side whose
# bench.config.ts has no project for this browser records nothing.
# Whether the PR passes is the report's call, from what's uploaded: a
# PR whose tests threw leaves `threw` beside its runs.
- name: bench main and the PR, alternating
env:
ROUNDS: 3
BROWSER: ${{ matrix.browser }}
run: |
for side in main pr; do (cd $side/sites/bench && pnpm exec vite build); done
mkdir -p runs
for round in $(seq "$ROUNDS"); do
for side in main pr; do
echo "::group::$side, round $round"
rm -f $side/sites/bench/bench-results/*.jsonl
if ! (cd $side/sites/bench && pnpm exec playwright test -c bench.config.ts --project="$BROWSER" --grep "patchwork[:,]"); then
if [ "$side" = pr ]; then touch runs/threw; fi
fi
mkdir -p "runs/$side-$round"
cp $side/sites/bench/bench-results/*.jsonl "runs/$side-$round/" 2>/dev/null || true
echo "::endgroup::"
done
done
- uses: actions/upload-artifact@v4
if: always()
with:
name: bench-runs-${{ matrix.browser }}
path: runs/
retention-days: 7
# Every browser's runs side by side, in one comment, and the one check that
# says whether the PR passes: it fails when the PR's benchmarks threw in any
# browser, or a check (an "ok" row) that main passes failed in any round of
# the PR (see compare.ts). Main failing doesn't fail the PR.
report:
name: benchmarks
needs: bench
# Not for a run cancelled by a newer push: that one reports instead.
if: ${{ !cancelled() }}
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: sites/bench/compare.ts
sparse-checkout-cone-mode: false
- uses: actions/setup-node@v4
with:
node-version: 24
- uses: actions/download-artifact@v4
with:
pattern: bench-runs-*
path: runs
- name: compare
run: |
threw=""
for browser in chromium firefox webkit; do
dir="runs/bench-runs-$browser"
if [ ! -d "$dir" ]; then
threw="$threw $browser (recorded nothing)"
elif [ -e "$dir/threw" ]; then
threw="$threw $browser"
fi
done
node sites/bench/compare.ts --main runs/*/main-* --pr runs/*/pr-* --why why.txt > compare.md || touch pr-regressed
if [ -n "$threw" ]; then
echo "$threw" > pr-failed
{ echo "**The PR's benchmarks threw in:$threw.** See the run's logs."; echo; cat compare.md; } > compare.tmp
mv compare.tmp compare.md
fi
cat compare.md >> "$GITHUB_STEP_SUMMARY"
- uses: actions/github-script@v7
with:
script: |
const fs = require("fs")
let compare = ""
try { compare = fs.readFileSync("compare.md", "utf8") } catch {}
if (!compare) return
const marker = process.env.MARKER
const { pull_request } = context.payload
const head = pull_request.head.sha.slice(0, 7)
const base = pull_request.base.sha.slice(0, 7)
const run = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`
const body = [
marker,
`## Benchmarks: Patchwork with this PR against main`,
``,
`\`${head}\` against main at \`${base}\`, Patchwork as it ships (\`patchwork\` mode), in chromium, firefox and webkit, each on its own runner with main and the PR measured one after the other ([run](${run})). Lower is better throughout. A row is in the table when every run of the PR came out on the same side of every run of main, with the medians more than 20% apart; every benchmark is below it.`,
``,
compare,
].join("\n")
const {owner, repo} = context.repo
const issue_number = context.issue.number
const comments = await github.paginate(github.rest.issues.listComments, {owner, repo, issue_number})
const existing = comments.find(c => c.body.startsWith(marker))
if (existing) {
await github.rest.issues.updateComment({owner, repo, comment_id: existing.id, body})
} else {
await github.rest.issues.createComment({owner, repo, issue_number, body})
}
# Says why as error annotations, which show on the run's summary page and
# the PR's checks. The comment has the same, with the tables.
- name: fail if the PR's benchmarks failed
run: |
failed=0
if [ -e pr-failed ]; then
echo "::error title=benchmarks threw::The PR's benchmarks threw in:$(cat pr-failed). See that browser's bench job for the test that threw."
failed=1
fi
if [ -e pr-regressed ]; then
if [ -s why.txt ]; then
while IFS= read -r line; do
echo "::error title=check failed::${line//%/%25}"
done < why.txt
else
echo "::error title=compare failed::compare.ts exited without naming a check that failed; see the compare step."
fi
failed=1
fi
exit $failed