-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaction.yml
More file actions
251 lines (230 loc) · 10.1 KB
/
Copy pathaction.yml
File metadata and controls
251 lines (230 loc) · 10.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
name: 'perfscale load test'
description: 'Run a load test with perfscale (k6, locust, or the native step engine) and collect the metrics as an artifact.'
author: 'Perfscale'
branding:
icon: 'activity'
color: 'purple'
inputs:
version:
description: 'perfscale release to install (a tag like v0.2.0, or "latest").'
required: false
default: 'latest'
k6:
description: 'Path to a k6 .js script. Runs via `perfscale run --k6`. Mutually exclusive with `locust` and `file`.'
required: false
default: ''
locust:
description: 'Path to a locustfile. Runs via `perfscale run --locust`. Mutually exclusive with `k6` and `file`.'
required: false
default: ''
file:
description: 'Path to a native test.yaml. Runs the built-in step engine via `perfscale run -f`. Requires `config`.'
required: false
default: ''
config:
description: 'Path to config.yaml (`vus`, `duration`, optional `report.url`). Required with `file`, optional for `locust`.'
required: false
default: ''
host:
description: 'Target base URL for the locust engine (`--host`).'
required: false
default: ''
report:
description: 'POST the summary to a `perfscale serve` instance after the run (`--report`).'
required: false
default: ''
args:
description: 'Extra raw arguments appended verbatim to `perfscale run`.'
required: false
default: ''
summary-export:
description: 'Path for the machine-readable run summary (JSON: metrics plus engine/VUs/duration/timestamp metadata), written via `--summary-export`. Included in the zip artifact. Set to empty to disable. Needs a perfscale release with --summary-export; older versions degrade with a warning.'
required: false
default: 'perfscale-summary.json'
job-summary:
description: 'Render the run summary as a Markdown table in the GitHub Actions job summary ($GITHUB_STEP_SUMMARY). Needs a perfscale release with --summary-export; older versions degrade with a warning.'
required: false
default: 'true'
output:
description: 'Path of the zip artifact holding the run summary and any generated files.'
required: false
default: 'perfscale-output.zip'
working-directory:
description: 'Directory to run perfscale from.'
required: false
default: '.'
outputs:
summary-file:
description: 'Path to the captured run summary (perfscale stdout).'
value: ${{ steps.run.outputs.summary-file }}
summary-json:
description: 'Path to the machine-readable JSON summary (empty when summary-export is disabled or unsupported by the installed perfscale).'
value: ${{ steps.run.outputs.summary-json }}
output-file:
description: 'Path to the zip artifact containing the summary and generated metrics files.'
value: ${{ steps.pack.outputs.output-file }}
exit-code:
description: 'Exit code returned by `perfscale run`.'
value: ${{ steps.run.outputs.exit-code }}
runs:
using: 'composite'
steps:
- name: Install perfscale
shell: bash
env:
PERFSCALE_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
case "${RUNNER_OS}" in
Linux) os=linux ;;
macOS) os=darwin ;;
Windows) os=windows ;;
*) echo "::error::unsupported runner OS: ${RUNNER_OS}"; exit 1 ;;
esac
case "${RUNNER_ARCH}" in
X64) arch=amd64 ;;
ARM64) arch=arm64 ;;
*) echo "::error::unsupported runner arch: ${RUNNER_ARCH}"; exit 1 ;;
esac
ext=""
[ "$os" = "windows" ] && ext=".exe"
asset="perfscale-${os}-${arch}${ext}"
if [ "${PERFSCALE_VERSION}" = "latest" ]; then
# Resolve the tag from the /releases/latest redirect instead of the
# REST API: api.github.com rate-limits unauthenticated calls per IP
# (403 on shared runner pools), while the web redirect does not.
loc=$(curl -fsSLI -o /dev/null -w '%{url_effective}' "https://github.com/Perfscale/perfscale/releases/latest")
tag="${loc##*/tag/}"
else
tag="${PERFSCALE_VERSION}"
fi
if [ -z "${tag}" ]; then
echo "::error::could not resolve perfscale release tag"; exit 1
fi
echo "Installing perfscale ${tag} (${asset})"
base="https://github.com/Perfscale/perfscale/releases/download/${tag}"
bindir="${RUNNER_TEMP}/perfscale-bin"
mkdir -p "${bindir}"
curl -fsSL -o "${bindir}/${asset}" "${base}/${asset}"
# Verify checksum against the release's sha256sums.txt
curl -fsSL -o "${bindir}/sha256sums.txt" "${base}/sha256sums.txt"
expected=$(grep " ${asset}\$" "${bindir}/sha256sums.txt" | awk '{print $1}' | head -n1)
if [ -n "${expected}" ]; then
if command -v sha256sum >/dev/null 2>&1; then
actual=$(sha256sum "${bindir}/${asset}" | awk '{print $1}')
else
actual=$(shasum -a 256 "${bindir}/${asset}" | awk '{print $1}')
fi
if [ "${expected}" != "${actual}" ]; then
echo "::error::checksum mismatch for ${asset}: expected ${expected}, got ${actual}"; exit 1
fi
echo "checksum ok: ${asset}"
else
echo "::warning::no checksum entry for ${asset} in sha256sums.txt — skipping verification"
fi
target="${bindir}/perfscale${ext}"
mv "${bindir}/${asset}" "${target}"
chmod +x "${target}"
echo "${bindir}" >> "${GITHUB_PATH}"
- name: Run perfscale
id: run
shell: bash
working-directory: ${{ inputs.working-directory }}
env:
IN_K6: ${{ inputs.k6 }}
IN_LOCUST: ${{ inputs.locust }}
IN_FILE: ${{ inputs.file }}
IN_CONFIG: ${{ inputs.config }}
IN_HOST: ${{ inputs.host }}
IN_REPORT: ${{ inputs.report }}
IN_ARGS: ${{ inputs.args }}
IN_SUMMARY_EXPORT: ${{ inputs.summary-export }}
IN_JOB_SUMMARY: ${{ inputs.job-summary }}
run: |
set -uo pipefail
# Exactly one engine flag is required.
engines=0
[ -n "${IN_K6}" ] && engines=$((engines+1))
[ -n "${IN_LOCUST}" ] && engines=$((engines+1))
[ -n "${IN_FILE}" ] && engines=$((engines+1))
if [ "${engines}" -ne 1 ]; then
echo "::error::exactly one of 'k6', 'locust', or 'file' must be set (got ${engines})"; exit 1
fi
if [ -n "${IN_FILE}" ] && [ -z "${IN_CONFIG}" ]; then
echo "::error::'config' is required when 'file' (native engine) is set"; exit 1
fi
cmd=(perfscale run)
[ -n "${IN_K6}" ] && cmd+=(--k6 "${IN_K6}")
[ -n "${IN_LOCUST}" ] && cmd+=(--locust "${IN_LOCUST}")
[ -n "${IN_FILE}" ] && cmd+=(-f "${IN_FILE}")
[ -n "${IN_CONFIG}" ] && cmd+=(-c "${IN_CONFIG}")
[ -n "${IN_HOST}" ] && cmd+=(--host "${IN_HOST}")
[ -n "${IN_REPORT}" ] && cmd+=(--report "${IN_REPORT}")
# shellcheck disable=SC2206
[ -n "${IN_ARGS}" ] && cmd+=(${IN_ARGS})
# Structured exports need a perfscale with --summary-export; degrade
# with a warning on older releases instead of failing the run.
summary_json=""
if [ -n "${IN_SUMMARY_EXPORT}" ] || [ "${IN_JOB_SUMMARY}" = "true" ]; then
if perfscale run --help 2>/dev/null | grep -q -- '--summary-export'; then
if [ -n "${IN_SUMMARY_EXPORT}" ]; then
summary_json="${IN_SUMMARY_EXPORT}"
cmd+=(--summary-export "${summary_json}")
fi
if [ "${IN_JOB_SUMMARY}" = "true" ] && [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
# $GITHUB_STEP_SUMMARY has no extension → --summary-format md
# applies to it, while the .json export keeps JSON.
cmd+=(--summary-export "${GITHUB_STEP_SUMMARY}" --summary-format md)
fi
else
echo "::warning::this perfscale release has no --summary-export; skipping summary-export/job-summary (set 'version' to a newer release)"
fi
fi
summary="${RUNNER_TEMP}/perfscale-summary.txt"
echo "+ ${cmd[*]}"
"${cmd[@]}" | tee "${summary}"
code=${PIPESTATUS[0]}
[ -n "${summary_json}" ] && [ ! -f "${summary_json}" ] && summary_json=""
echo "summary-file=${summary}" >> "${GITHUB_OUTPUT}"
echo "summary-json=${summary_json}" >> "${GITHUB_OUTPUT}"
echo "exit-code=${code}" >> "${GITHUB_OUTPUT}"
exit "${code}"
- name: Pack metrics
id: pack
if: always()
shell: bash
working-directory: ${{ inputs.working-directory }}
env:
IN_OUTPUT: ${{ inputs.output }}
SUMMARY_FILE: ${{ steps.run.outputs.summary-file }}
SUMMARY_JSON: ${{ steps.run.outputs.summary-json }}
run: |
set -uo pipefail
out="${IN_OUTPUT}"
staging="${RUNNER_TEMP}/perfscale-out"
rm -rf "${staging}"; mkdir -p "${staging}"
if [ -n "${SUMMARY_FILE:-}" ] && [ -f "${SUMMARY_FILE}" ]; then
cp "${SUMMARY_FILE}" "${staging}/summary.txt"
fi
if [ -n "${SUMMARY_JSON:-}" ] && [ -f "${SUMMARY_JSON}" ]; then
cp "${SUMMARY_JSON}" "${staging}/summary.json"
fi
# Common metric artifacts perfscale / underlying engines may drop.
for f in summary.json summary.csv *.csv *_stats.csv results.json; do
for m in ${f}; do [ -f "${m}" ] && cp "${m}" "${staging}/" 2>/dev/null || true; done
done
# Never let zip fail on an empty staging dir (e.g. when the run step
# failed before producing a summary) — that would mask the real error.
if [ -z "$(ls -A "${staging}")" ]; then
echo "no metrics produced" > "${staging}/EMPTY.txt"
fi
if command -v zip >/dev/null 2>&1; then
( cd "${staging}" && zip -qr - . ) > "${out}"
elif command -v powershell >/dev/null 2>&1; then
powershell -NoProfile -Command "Compress-Archive -Path '${staging}/*' -DestinationPath '${out}' -Force"
else
echo "::warning::no zip tool available — writing a tar.gz instead of a zip"
tar -czf "${out}" -C "${staging}" .
fi
echo "output-file=${out}" >> "${GITHUB_OUTPUT}"
echo "packed metrics into ${out}"