11#! /usr/bin/env bash
2- set -euo pipefail
2+ set -eEuo pipefail
33
44require_env () {
55 local key=" $1 "
@@ -29,27 +29,48 @@ log() {
2929 printf ' [remote] %s\n' " $* "
3030}
3131
32+ dump_failure_context () {
33+ local exit_code=$?
34+ log " Command failed with exit code ${exit_code} . Dumping available logs from ${RESULT_DIR} :"
35+
36+ local found=false
37+ for file in " $RESULT_DIR " /* .log; do
38+ if [[ -f " $file " ]]; then
39+ found=true
40+ echo " ----- ${file} (tail) -----"
41+ tail -n 200 " $file " || true
42+ fi
43+ done
44+
45+ if [[ " $found " == false ]]; then
46+ log " No log files found."
47+ fi
48+
49+ exit " $exit_code "
50+ }
51+ trap dump_failure_context ERR
52+
3253base64_encode_file () {
3354 local path=" $1 "
3455 if base64 --help 2> /dev/null | grep -q -- " -w" ; then
3556 base64 -w 0 " $path "
3657 else
37- base64 " $path " | tr -d ' \n'
58+ base64 < " $path " | tr -d ' \n'
3859 fi
3960}
4061
4162install_system_packages () {
4263 if command -v dnf > /dev/null 2>&1 ; then
4364 log " Installing packages via dnf"
44- dnf install -y git curl tar xz gzip gcc gcc-c++ make perl-core which > /dev/null
65+ dnf install -y git tar xz gzip gcc gcc-c++ make perl-core which cmake clang clang-libs > /dev/null
4566 return
4667 fi
4768
4869 if command -v apt-get > /dev/null 2>&1 ; then
4970 log " Installing packages via apt-get"
5071 export DEBIAN_FRONTEND=noninteractive
5172 apt-get update -y > /dev/null
52- apt-get install -y git curl ca-certificates build-essential xz-utils > /dev/null
73+ apt-get install -y git curl ca-certificates build-essential xz-utils cmake clang libclang-dev > /dev/null
5374 return
5475 fi
5576
@@ -106,13 +127,15 @@ ensure_rust() {
106127}
107128
108129source_rust_env () {
130+ export HOME=" ${HOME:-/ root} "
131+
109132 if [[ -f " /root/.cargo/env" ]]; then
110133 # shellcheck disable=SC1091
111134 source /root/.cargo/env
112135 fi
113- if [[ -f " $HOME /.cargo/env" ]]; then
136+ if [[ -f " ${ HOME} /.cargo/env" ]]; then
114137 # shellcheck disable=SC1090
115- source " $HOME /.cargo/env"
138+ source " ${ HOME} /.cargo/env"
116139 fi
117140}
118141
@@ -138,16 +161,24 @@ run_bench() {
138161 git -C " $repo_dir " reset --hard " $ref " > /dev/null
139162
140163 log " Installing npm dependencies for ${label} "
141- (
164+ if ! (
142165 cd " $repo_dir "
143166 npm ci --no-audit --no-fund > " $RESULT_DIR /${label} -npm.log" 2>&1
144- )
167+ ); then
168+ echo " ----- ${RESULT_DIR} /${label} -npm.log (tail) -----" >&2
169+ tail -n 200 " $RESULT_DIR /${label} -npm.log" >&2 || true
170+ return 1
171+ fi
145172
146173 log " Building native module for ${label} "
147- (
174+ if ! (
148175 cd " $repo_dir "
149176 npm run build:rust > " $build_log " 2>&1
150- )
177+ ); then
178+ echo " ----- ${build_log} (tail) -----" >&2
179+ tail -n 200 " $build_log " >&2 || true
180+ return 1
181+ fi
151182
152183 local -a bench_args
153184 bench_args=(
@@ -167,10 +198,14 @@ run_bench() {
167198 bench_args+=(" --json" " $json_out " )
168199
169200 log " Running benchmark ${label} "
170- (
201+ if ! (
171202 cd " $repo_dir "
172203 npm run bench:run -- " ${bench_args[@]} " > " $bench_log " 2>&1
173- )
204+ ); then
205+ echo " ----- ${bench_log} (tail) -----" >&2
206+ tail -n 200 " $bench_log " >&2 || true
207+ return 1
208+ fi
174209}
175210
176211main () {
@@ -203,16 +238,78 @@ main() {
203238 run_bench " head" " $HEAD_REF " " $repo_dir "
204239
205240 log " Generating comparison report"
206- (
207- cd " $repo_dir "
208- node scripts/aws-perf/compare-bench.mjs \
209- --base " $RESULT_DIR /base.json" \
210- --head " $RESULT_DIR /head.json" \
211- --threshold-pct " $THRESHOLD_PCT " \
212- --markdown " $RESULT_DIR /report.md" \
213- --json " $RESULT_DIR /report.json" \
214- --no-fail > " $RESULT_DIR /compare.log" 2>&1
215- )
241+ node - " $RESULT_DIR /base.json" " $RESULT_DIR /head.json" " $THRESHOLD_PCT " " $RESULT_DIR /report.md" " $RESULT_DIR /report.json" > " $RESULT_DIR /compare.log" 2>&1 << 'NODE '
242+ const fs = require("node:fs");
243+
244+ const basePath = process.argv[2];
245+ const headPath = process.argv[3];
246+ const thresholdPct = Number(process.argv[4]);
247+ const markdownPath = process.argv[5];
248+ const jsonPath = process.argv[6];
249+
250+ const baseRun = JSON.parse(fs.readFileSync(basePath, "utf8"));
251+ const headRun = JSON.parse(fs.readFileSync(headPath, "utf8"));
252+
253+ const baseMap = new Map(baseRun.results.map((result) => [result.name, result]));
254+ const headMap = new Map(headRun.results.map((result) => [result.name, result]));
255+
256+ const names = [...baseMap.keys()].filter((name) => headMap.has(name)).sort();
257+ const scenarios = names.map((name) => {
258+ const base = baseMap.get(name);
259+ const head = headMap.get(name);
260+ const deltaPct = ((head.mean - base.mean) / base.mean) * 100;
261+ const regression = deltaPct <= -thresholdPct;
262+ const improvement = deltaPct >= thresholdPct;
263+ return {
264+ name,
265+ baseMean: base.mean,
266+ headMean: head.mean,
267+ deltaPct,
268+ baseCiPct: base.ci95.marginPct,
269+ headCiPct: head.ci95.marginPct,
270+ status: regression ? "REGRESSION" : improvement ? "IMPROVEMENT" : "OK",
271+ };
272+ });
273+
274+ const regressions = scenarios.filter((item) => item.status === "REGRESSION").map((item) => item.name);
275+ const report = {
276+ generatedAt: new Date().toISOString(),
277+ thresholdPct,
278+ baseCommit: baseRun.git?.commit,
279+ headCommit: headRun.git?.commit,
280+ regressions,
281+ pass: regressions.length === 0,
282+ scenarios,
283+ };
284+
285+ const num = (value) => value.toLocaleString("en-US", { maximumFractionDigits: 2 });
286+ const pct = (value) => `${value > 0 ? "+" : ""}${value.toFixed(2)}%`;
287+
288+ const lines = [];
289+ lines.push("# AWS Perf Compare");
290+ lines.push("");
291+ lines.push(`- Generated: ${report.generatedAt}`);
292+ lines.push(`- Threshold: ${report.thresholdPct}% throughput drop => regression`);
293+ if (report.baseCommit) lines.push(`- Base commit: ${report.baseCommit}`);
294+ if (report.headCommit) lines.push(`- Head commit: ${report.headCommit}`);
295+ lines.push("");
296+ lines.push("| Scenario | Base req/s | Head req/s | Delta | Status | Base CI | Head CI |");
297+ lines.push("|---|---:|---:|---:|---|---:|---:|");
298+ for (const row of scenarios) {
299+ lines.push(
300+ `| ${row.name} | ${num(row.baseMean)} | ${num(row.headMean)} | ${pct(row.deltaPct)} | ${row.status} | ±${row.baseCiPct.toFixed(2)}% | ±${row.headCiPct.toFixed(2)}% |`,
301+ );
302+ }
303+ lines.push("");
304+ lines.push(`## Gate: ${report.pass ? "PASS" : "FAIL"}`);
305+ if (!report.pass) {
306+ lines.push(`Regressions: ${report.regressions.join(", ")}`);
307+ }
308+
309+ const markdown = lines.join("\n");
310+ fs.writeFileSync(markdownPath, markdown, "utf8");
311+ fs.writeFileSync(jsonPath, JSON.stringify(report, null, 2), "utf8");
312+ NODE
216313
217314 cat " $RESULT_DIR /report.md"
218315 echo " "
0 commit comments