mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-26 08:18:18 +00:00
2157470224
* ci(perf): warp A/B relative-budget gate for the hotpath series performance.yml only uploads a samply profile and a cargo-bench artifact with no pass/fail, so a 26x-class write-path regression like #4221 or the GET perf churn would sail through CI and only surface in customer load tests. This adds the missing gate — the acceptance surface every queued HP change (#922/#923/ #925/#927/#930/#932) needs before its default can flip. - scripts/hotpath_warp_ab_gate.sh: applies the relative budget to the baseline_compare.csv that run_object_batch_bench_enhanced.sh already emits (it computes deltas but never gates). A metric regressing past --fail-pct fails, past --warn-pct warns; reqps/throughput are higher-is-better, latency lower-is-better. --allow-regression downgrades a FAIL to an exempted WARN so a deliberate correctness cost (e.g. #4221) is recorded, not blocked (rustfs/backlog#935 correction 1). Unit-checked across pass/warn/fail/exempt. - scripts/run_hotpath_warp_ab.sh: single-host baseline-binary vs candidate- binary A/B over {put-4mib, get-4mib, mixed-256k} x {drive-sync on, off}, reusing the enhanced bench as the warp driver and the single-node local-disk lifecycle. Has --dry-run; warp is assumed pre-installed as elsewhere in scripts/. Drive-sync on/off keeps a sync-semantics change from being masked by nosync numbers. - .github/workflows/performance-ab.yml: nightly on main (post-merge detection) plus opt-in pre-merge via the `perf-ab` label; `perf-deliberate-tradeoff` runs the gate with --allow-regression; posts the gate table as a PR comment and uploads the run. A Linux runner answers "do the macOS conclusions hold". The gate logic is unit-validated with synthetic compare CSVs; the orchestrator and workflow are shellcheck- and --dry-run-validated. The first real warp measurement belongs on the Linux runner (no warp/multi-disk rig locally). Refs: rustfs/backlog#935 (HP-14 warp A/B gate, item 4), rustfs/backlog#725 (cooled A/B harness precedent), rustfs/backlog#936 Co-Authored-By: heihutu <heihutu@gmail.com> * ci(perf): pin upload-artifact, add external-cluster A/B mode + runbook - Pin actions/upload-artifact to the repo-standard full-length SHA (# v6); the previous @v4 float tripped the workflow-pin guard. - Add an external deployment mode to run_hotpath_warp_ab.sh so warp can target an already-running cluster instead of a throwaway single-node server: --endpoint selects the cluster and --deploy-hook runs between phases to swap in the phase's binary and drive-sync config (context passed via HOTPATH_AB_PHASE / HOTPATH_AB_BINARY / HOTPATH_AB_DRIVE_SYNC). This maps onto the team's ansible harness (cargo zigbuild -> ansible rustfs-manage --tags stop,config,binary-copy,start -> warp). - Restructure the matrix loop to bring a deployment up once per (phase, drive-sync) and run all three workloads against it, instead of restarting per workload — fewer server starts / cluster redeploys. Baseline medians are read from a deterministic path, dropping the bash-4-only associative array so the script (and its --dry-run self-check) runs on macOS bash 3.2 too. - Add docs/operations/hotpath-warp-ab-runbook.md tying local mode, the ansible external mode, and the budget/exemption together. Verification: shellcheck clean; --dry-run in both modes prints the expected 4 deployments x 3 workloads and passes exactly 6 compare CSVs to the gate; check_workflow_pins.sh, check_doc_paths.sh, and make pre-commit all pass. Refs: rustfs/backlog#935, rustfs/backlog#936 Co-Authored-By: heihutu <heihutu@gmail.com> --------- Co-authored-by: heihutu <heihutu@gmail.com>
147 lines
5.3 KiB
Bash
Executable File
147 lines
5.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# hotpath_warp_ab_gate.sh — relative-budget gate for the hotpath warp A/B rig
|
|
# (rustfs/backlog#935 HP-14, item 4).
|
|
#
|
|
# The load driver scripts/run_object_batch_bench_enhanced.sh already emits a
|
|
# baseline_compare.csv with delta_{reqps,latency,throughput}_pct columns but
|
|
# does not itself decide pass/fail. This script applies the relative budget:
|
|
# a metric that regresses past --fail-pct fails the gate, past --warn-pct
|
|
# warns, otherwise passes. Because #4221 shows a deliberate 26x cost is
|
|
# sometimes the correct durability fix, --allow-regression downgrades every
|
|
# FAIL to an exempted WARN and records the reason instead of blocking.
|
|
#
|
|
# Metric direction:
|
|
# reqps, throughput -> higher is better; regression is a negative delta.
|
|
# latency (incl p99) -> lower is better; regression is a positive delta.
|
|
#
|
|
# Input CSV header (from run_object_batch_bench_enhanced.sh compare_baseline):
|
|
# size,tool,concurrency,new_median_reqps,baseline_median_reqps,delta_reqps_pct,
|
|
# new_median_latency_ms,baseline_median_latency_ms,delta_latency_pct,
|
|
# new_median_throughput_bps,baseline_median_throughput_bps,delta_throughput_pct
|
|
#
|
|
# Exit code: 0 when no unexempted FAIL (PASS or WARN), 1 otherwise.
|
|
|
|
set -euo pipefail
|
|
|
|
FAIL_PCT=10
|
|
WARN_PCT=5
|
|
ALLOW_REGRESSION="false"
|
|
MARKDOWN_OUT=""
|
|
EXEMPTION_REASON="deliberate correctness tradeoff"
|
|
declare -a COMPARE_CSVS=()
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
Usage: hotpath_warp_ab_gate.sh --compare-csv <file> [--compare-csv <file> ...] [options]
|
|
|
|
--compare-csv <file> baseline_compare.csv to evaluate (repeatable).
|
|
--fail-pct <n> Regression budget that fails the gate (default 10).
|
|
--warn-pct <n> Regression budget that warns (default 5).
|
|
--allow-regression Downgrade every FAIL to an exempted WARN (deliberate
|
|
correctness tradeoff); the gate then always exits 0.
|
|
--exemption-reason <s> Reason recorded when --allow-regression is set.
|
|
--markdown <file> Also write the result table as Markdown to <file>.
|
|
-h, --help Show this help.
|
|
USAGE
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--compare-csv) COMPARE_CSVS+=("$2"); shift 2 ;;
|
|
--fail-pct) FAIL_PCT="$2"; shift 2 ;;
|
|
--warn-pct) WARN_PCT="$2"; shift 2 ;;
|
|
--allow-regression) ALLOW_REGRESSION="true"; shift ;;
|
|
--exemption-reason) EXEMPTION_REASON="$2"; shift 2 ;;
|
|
--markdown) MARKDOWN_OUT="$2"; shift 2 ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
*) echo "unknown argument: $1" >&2; usage >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
if [[ ${#COMPARE_CSVS[@]} -eq 0 ]]; then
|
|
echo "error: at least one --compare-csv is required" >&2
|
|
usage >&2
|
|
exit 2
|
|
fi
|
|
for csv in "${COMPARE_CSVS[@]}"; do
|
|
[[ -f "$csv" ]] || { echo "error: compare CSV not found: $csv" >&2; exit 2; }
|
|
done
|
|
|
|
# One awk pass over all CSVs. Emits TSV rows "verdict\tworkload\tmetric\tdelta"
|
|
# on stdout and a final "OVERALL\t<verdict>" line. Verdict is PASS/WARN/FAIL.
|
|
gate_output="$(
|
|
awk -v fail_pct="$FAIL_PCT" -v warn_pct="$WARN_PCT" '
|
|
function classify(delta, higher_is_better, regress) {
|
|
if (delta == "" || delta == "N/A") return "SKIP"
|
|
# Signed regression magnitude: positive means "worse than baseline".
|
|
regress = higher_is_better ? -delta : delta
|
|
if (regress > fail_pct) return "FAIL"
|
|
if (regress > warn_pct) return "WARN"
|
|
return "PASS"
|
|
}
|
|
function emit(v, workload, metric, delta, rank) {
|
|
if (v == "SKIP") return
|
|
print v "\t" workload "\t" metric "\t" delta "%"
|
|
rank = (v == "FAIL") ? 3 : (v == "WARN") ? 2 : 1
|
|
if (rank > worst) worst = rank
|
|
}
|
|
BEGIN { worst = 1 }
|
|
FNR == 1 { next } # skip each file header
|
|
{
|
|
n = split($0, f, ",")
|
|
if (n < 12) next
|
|
workload = f[1] "/" f[2] "@" f[3] # size/tool@concurrency
|
|
emit(classify(f[6], 1), workload, "reqps", f[6])
|
|
emit(classify(f[9], 0), workload, "latency", f[9])
|
|
emit(classify(f[12], 1), workload, "throughput", f[12])
|
|
}
|
|
END {
|
|
print "OVERALL\t" (worst == 3 ? "FAIL" : worst == 2 ? "WARN" : "PASS")
|
|
}
|
|
' "${COMPARE_CSVS[@]}"
|
|
)"
|
|
|
|
overall="$(printf '%s\n' "$gate_output" | awk -F'\t' '$1=="OVERALL"{print $2}')"
|
|
rows="$(printf '%s\n' "$gate_output" | awk -F'\t' '$1!="OVERALL"')"
|
|
|
|
exempted="false"
|
|
effective="$overall"
|
|
if [[ "$overall" == "FAIL" && "$ALLOW_REGRESSION" == "true" ]]; then
|
|
exempted="true"
|
|
effective="WARN"
|
|
fi
|
|
|
|
emoji() { case "$1" in FAIL) echo '❌';; WARN) echo '⚠️';; PASS) echo '✅';; *) echo '';; esac; }
|
|
|
|
render() {
|
|
echo "### Hotpath warp A/B gate — $(emoji "$effective") $effective"
|
|
echo
|
|
echo "Budget: regression > ${FAIL_PCT}% fails, > ${WARN_PCT}% warns (relative to baseline)."
|
|
if [[ "$exempted" == "true" ]]; then
|
|
echo
|
|
echo "> Gate FAIL exempted via \`--allow-regression\`: ${EXEMPTION_REASON}."
|
|
fi
|
|
echo
|
|
echo "| Workload | Metric | Δ vs baseline | Verdict |"
|
|
echo "| --- | --- | --- | --- |"
|
|
if [[ -z "$rows" ]]; then
|
|
echo "| _(no rows)_ | | | |"
|
|
else
|
|
printf '%s\n' "$rows" | while IFS=$'\t' read -r v workload metric delta; do
|
|
[[ -z "$v" ]] && continue
|
|
echo "| $workload | $metric | $delta | $(emoji "$v") $v |"
|
|
done
|
|
fi
|
|
}
|
|
|
|
table="$(render)"
|
|
printf '%s\n' "$table"
|
|
if [[ -n "$MARKDOWN_OUT" ]]; then
|
|
printf '%s\n' "$table" >"$MARKDOWN_OUT"
|
|
fi
|
|
|
|
if [[ "$overall" == "FAIL" && "$exempted" != "true" ]]; then
|
|
exit 1
|
|
fi
|
|
exit 0
|