-
Notifications
You must be signed in to change notification settings - Fork 5
305 lines (283 loc) · 11.1 KB
/
Copy pathperf-test.yml
File metadata and controls
305 lines (283 loc) · 11.1 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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
name: perf test
# Standalone perf-regression workflow.
#
# Triggers
# --------
# schedule — nightly run against main HEAD. This is the canonical
# trend source; only scheduled runs publish to Pages.
# workflow_dispatch — manual runs from the Actions tab or `gh workflow run`,
# available to members/maintainers. The `ref` input lets
# a maintainer point a run at any PR branch on demand.
# workflow_call — reusable, for invocation from another workflow.
#
# This workflow does NOT run automatically on push or pull_request — it never
# competes with per-PR CI on the shared self-hosted VMs, and untrusted PR code
# never executes here automatically.
#
# Concurrency
# -----------
# A single fixed group (`perf-test`) so scheduled, manual, and called runs all
# queue against the same physical Linode VMs. `cancel-in-progress: false`
# because a partial test leaves VM state behind; queueing is safer.
#
# Publishing
# ----------
# Results stage as a Pages artifact (180-run rolling window + dashboard) and
# only deploy on the nightly schedule. Manual / called runs upload the
# artifact for inspection but do not publish — keeps experiments from
# clobbering the main trend line at openmoq.org/moqx/.
on:
schedule:
# Nightly perf run against main HEAD (05:00 UTC). Scheduled runs always
# execute on the default branch (main) and are the only runs that publish.
- cron: '0 5 * * *'
workflow_dispatch:
# No `ref` input: the branch/tag under test is the one selected in the
# native "Use workflow from" picker (github.ref). That keeps a single
# branch control and makes the recorded branch label match what was tested.
inputs:
duration:
description: 'test duration seconds'
type: number
default: 120
subscribers:
description: 'peak subscribers'
type: number
default: 600
compare:
description: 'compare against the published nightly baseline and render a report'
type: boolean
default: true
pr:
description: 'PR number to post the report to (blank = report only in the step summary)'
type: string
default: ''
workflow_call:
inputs:
ref:
type: string
required: false
default: ''
duration:
type: number
required: false
default: 120
subscribers:
type: number
required: false
default: 600
compare:
type: boolean
required: false
default: false
pr:
type: string
required: false
default: ''
concurrency:
group: perf-test
cancel-in-progress: false
permissions:
contents: read
pull-requests: write # used only by the optional "post report to PR" step (pr input)
jobs:
perf:
name: perf-test
runs-on: [self-hosted, linode]
outputs:
run-file: ${{ steps.collect.outputs.run-file }}
steps:
- name: Generate app token
id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.OMOQ_APP_ID }}
private-key: ${{ secrets.OMOQ_APP_PRIV_KEY }}
- uses: actions/checkout@v4
with:
# workflow_call passes an explicit ref; dispatch/schedule leave it
# empty, so checkout falls back to github.ref (the selected branch on
# dispatch, main HEAD on schedule).
ref: ${{ inputs.ref }}
submodules: true
- name: Determine SHA
id: sha
run: |
SHA=$(git rev-parse HEAD)
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
echo "short=${SHA:0:7}" >> "$GITHUB_OUTPUT"
- name: Install system dependencies
run: sudo deps/moxygen/standalone/install-system-deps.sh
- name: Setup dependencies
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: bash scripts/build.sh setup
- name: Build moqx
run: bash scripts/build.sh
- name: Configure SSH for perf VMs
env:
PERF_SSH_KEY: ${{ secrets.PERF_SSH_KEY }}
run: |
install -d -m 700 ~/.ssh
printf '%s\n' "$PERF_SSH_KEY" > ~/.ssh/perf-key
chmod 600 ~/.ssh/perf-key
echo "PERF_SSH_KEY_FILE=$HOME/.ssh/perf-key" >> "$GITHUB_ENV"
- name: Run perf test on dedicated VMs
id: collect
env:
PERF_RELAY_HOST: ${{ secrets.PERF_RELAY_HOST }}
PERF_CLIENT_HOST: ${{ secrets.PERF_CLIENT_HOST }}
run: |
FILE="run-${{ steps.sha.outputs.short }}.json"
# Fallbacks: schedule events carry no `inputs`, so apply the canonical
# nightly load when invoked by cron.
bash scripts/perf-test-ci.sh \
--duration "${{ inputs.duration || '120' }}" \
--subscriber-max "${{ inputs.subscribers || '600' }}" \
--ramp 150 \
--io-threads 4 \
--client-threads 8 \
--client-args "--first_object_size=26516 --other_object_size=3788 --objects_per_group 60 --delivery_timeout 5000" \
--cooldown 10 \
--output "$FILE"
echo "run-file=$FILE" >> "$GITHUB_OUTPUT"
- name: Render step summary
if: always()
env:
FILE: ${{ steps.collect.outputs.run-file }}
run: |
{
echo "## perf-test — $(date -u +'%Y-%m-%d %H:%M UTC')"
echo
echo "ref: \`${{ steps.sha.outputs.short }}\` · branch: \`${{ github.ref_name }}\` · subs: ${{ inputs.subscribers || '600' }} · duration: ${{ inputs.duration || '120' }}s"
echo
if [ -f "$FILE" ]; then
echo '| metric | value |'
echo '|---|---|'
jq -r '
(if (.metrics | type) == "object" then .metrics
elif (.results | type) == "object" then .results
else {}
end)
| to_entries[]
| "| \(.key) | \(.value) |"
' "$FILE"
else
echo "_no results JSON produced_"
fi
} >> "$GITHUB_STEP_SUMMARY"
- name: Pull rolling baseline from currently-published Pages
# Downloads index.json + the latest N run JSONs into data/ so
# perf-compare.py can score the new run against history.
# No-op on the very first deploy (index.json 404).
env:
BASE: https://openmoq.org/moqx/perf
KEEP: 180
run: |
mkdir -p data
if curl -fsSL "$BASE/index.json" -o data/index.json; then
jq -r '.runs[].file' data/index.json | head -n $((KEEP - 1)) | \
while read -r f; do
curl -fsSL "$BASE/$f" -o "data/$f" 2>/dev/null || true
done
fi
- name: Stage Pages payload (dashboard + rolling window + new run)
env:
KEEP: 180
FILE: ${{ steps.collect.outputs.run-file }}
run: |
OUT=perf-out
mkdir -p "$OUT/perf"
# Dashboard (status/index.html) is in-repo and uses ./perf at runtime
cp status/index.html "$OUT/index.html"
# Copy historical runs, then the new run (overrides any stale copy
# of the same SHA name)
cp data/run-*.json "$OUT/perf/" 2>/dev/null || true
cp "$FILE" "$OUT/perf/$FILE"
# Rebuild manifest with per-run metadata (branch + timestamp). The
# run filenames are SHA-based, so they cannot be ordered by name —
# sort newest-first by timestamp. perf-compare.py filters entries by
# branch and the dashboard relies on chronological ordering. Cap KEEP.
for f in "$OUT/perf/"run-*.json; do
[ -e "$f" ] || continue
jq -c --arg file "$(basename "$f")" \
'{file: $file, branch: (.branch // "unknown"), timestamp: (.timestamp // ""), commit: (.commit // "")}' "$f"
done \
| jq -s "sort_by(.timestamp) | reverse | .[0:${KEEP}] | {runs: .}" \
> "$OUT/perf/index.json"
- name: Upload Pages artifact
# Picked up by actions/deploy-pages in the deploy job (gated to the
# nightly schedule). Dispatch / called runs still upload it so reviewers
# can download + inspect without publishing.
uses: actions/upload-pages-artifact@v3
with:
path: perf-out
- name: Upload workflow artifacts (always retained)
if: always()
uses: actions/upload-artifact@v4
with:
name: perf-results
path: |
${{ steps.collect.outputs.run-file }}
perf-out/
retention-days: 30
- name: Compare vs rolling baseline (report)
# Opt-in via the `compare` input. perf-compare.py is non-blocking
# (always exits 0) and renders a "no baseline yet" note on first run.
# Always compares against the published main trend, so the report is
# only meaningful when run at matching load (subs=1000).
if: ${{ inputs.compare }}
env:
FILE: ${{ steps.collect.outputs.run-file }}
run: |
python3 scripts/perf-compare.py \
--current "$FILE" \
--data-dir data \
--window 10 \
--threshold 5 \
--output comment.md
{
echo
echo "## perf comparison vs published baseline (subs=${{ inputs.subscribers || '600' }})"
echo
cat comment.md
} >> "$GITHUB_STEP_SUMMARY"
- name: Post comparison report to PR
# Only when a PR number is supplied. Blank `pr` = zero PR footprint;
# the report still lives in this run's step summary, whose URL can be
# referenced from a PR by hand.
if: ${{ inputs.compare && inputs.pr != '' }}
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ inputs.pr }}
run: |
gh pr comment "$PR_NUMBER" \
--repo "$GITHUB_REPOSITORY" \
--body-file comment.md
deploy:
needs: perf
# Only the nightly schedule publishes — manual dispatch (of any branch) and
# reusable calls upload the artifact for inspection but leave the published
# trend line untouched. Scheduled runs always execute on main HEAD.
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
# Create the Pages deployment as the omoq-sync-bot App rather than under
# the personal actor GitHub attributes scheduled runs to. Keeps the
# deployments list owned by the bot, consistent with the rest of the org
# automation. Requires the App installation to have Pages: write.
- uses: actions/create-github-app-token@v2
id: app-token
with:
app-id: ${{ secrets.OMOQ_APP_ID }}
private-key: ${{ secrets.OMOQ_APP_PRIV_KEY }}
- id: deployment
uses: actions/deploy-pages@v4
with:
token: ${{ steps.app-token.outputs.token }}