From 140e13a45f93c72d99e9e9bc1d8eff498117de27 Mon Sep 17 00:00:00 2001 From: Lee Yunjin Date: Sun, 4 Oct 2026 21:15:01 +0900 Subject: [PATCH 1/2] ci(perf): add HTTPS/TLS performance gates for 3.9 (issues #306, #307) Extends the perf-gate infrastructure with TLS coverage, following the runner-baseline convention: an absolute backstop per metric (must hold on any CI hardware) plus a same-runner-CPU median comparison once >=5 samples exist for that CPU, since absolute numbers move a lot between CI machines. Gates (measured CWIST 3.9 baselines in comments): 1. tls_rtt_p50_ms sequential TLS client WITHOUT TCP_NODELAY, first-response RTT p50 over 200 fresh connections; backstop <= 5ms. This is the #307 regression test: pre-#307 measures ~43ms here. 2. https_churn_rps ab -n 6000 -c 32 new-conn-per-request; >= 1200 (~1650 healthy, ~600 pre-#307). 3. https_keepalive_rps wrk -t4 -c100 -d10s; >= 130k (~167-190k). 4. https_big_gbps wrk -t4 -c32 on a 1MiB body; >= 3GB/s (~4GB/s). 5. http_* plaintext controls for the same workloads (collateral damage). The https/http keep-alive ratio (~0.65) is recorded as a diagnostic signal, not gated. Bench server and cert are generated at runtime; the gate job runs on PRs touching the TLS path and on dev pushes, and appends to benchmarks/https_gates.json only after gates pass on a dev push. Verified locally on a Ryzen 5600X: 7/7 gates pass on dev; with the quickack re-arms disabled the run measures 43.2ms RTT p50 / 683 churn rps and the evaluator exits 1 (fails gates 1 and 2). --- .github/workflows/perf-https-gates.yml | 92 +++++++++++ benchmarks/https_gates.json | 15 ++ scripts/ci/https_gates_eval.py | 131 ++++++++++++++++ scripts/ci/https_perf_gates.sh | 205 +++++++++++++++++++++++++ 4 files changed, 443 insertions(+) create mode 100644 .github/workflows/perf-https-gates.yml create mode 100644 benchmarks/https_gates.json create mode 100644 scripts/ci/https_gates_eval.py create mode 100755 scripts/ci/https_perf_gates.sh diff --git a/.github/workflows/perf-https-gates.yml b/.github/workflows/perf-https-gates.yml new file mode 100644 index 000000000..753863427 --- /dev/null +++ b/.github/workflows/perf-https-gates.yml @@ -0,0 +1,92 @@ +name: Perf — HTTPS gates + +# CWIST 3.9 TLS performance gates (issues #306 / #307). +# +# Gate 1 (tls_rtt_p50_ms) is the #307 regression test: a sequential TLS +# client WITHOUT TCP_NODELAY, which stalls ~43ms per request on pre-#307 +# code and ~0.2ms on healthy code. The other gates cover HTTPS churn, +# keep-alive throughput and 1MiB transfer, each with a plaintext control. +# See scripts/ci/https_perf_gates.sh for measured baselines. +on: + pull_request: + branches: [dev] + paths: + - '.github/workflows/perf-https-gates.yml' + - 'scripts/ci/https_perf_gates.sh' + - 'scripts/ci/https_gates_eval.py' + - 'src/net/http/https.c' + - 'src/net/http/https_upgrade_hook.c' + - 'src/net/http/tls_chain.c' + - 'src/https/**' + - 'lib/boringssl' + push: + branches: [dev] + paths: + - '.github/workflows/perf-https-gates.yml' + - 'scripts/ci/https_perf_gates.sh' + - 'scripts/ci/https_gates_eval.py' + - 'src/net/http/https.c' + - 'src/net/http/https_upgrade_hook.c' + - 'src/net/http/tls_chain.c' + - 'src/https/**' + - 'lib/boringssl' + workflow_dispatch: + +permissions: + contents: read + +jobs: + https-gates: + name: HTTPS performance gates + runs-on: ubuntu-latest + # Write is needed only for the post-gate history commit on dev pushes; + # that step never runs on pull_request events, and fork PRs get a + # read-only token regardless. + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: { submodules: recursive } + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y cmake libcurl4-openssl-dev libnghttp2-dev \ + libbrotli-dev wrk apache2-utils libssl-dev + + - name: Run HTTPS gates + run: scripts/ci/https_perf_gates.sh https-gates-result.json + + - name: Evaluate gates + run: python3 scripts/ci/https_gates_eval.py https-gates-result.json + + - name: Upload result + if: always() + uses: actions/upload-artifact@v4 + with: + name: https-gates-result + path: https-gates-result.json + + # History update: only after the gates passed on a dev push, so the + # same-CPU medians never absorb a regression. PR runs never touch the + # history file. + - name: Update gate history on dev + if: github.event_name == 'push' && github.ref == 'refs/heads/dev' + run: python3 scripts/ci/https_gates_eval.py --update https-gates-result.json + + - name: Commit updated history + if: github.event_name == 'push' && github.ref == 'refs/heads/dev' + uses: actions/github-script@v7 + with: + script: | + const { execSync } = require('child_process'); + try { + execSync('git diff --quiet -- benchmarks/https_gates.json'); + console.log('no history changes'); + } catch { + execSync('git config user.name "github-actions[bot]"'); + execSync('git config user.email "github-actions[bot]@users.noreply.github.com"'); + execSync('git add benchmarks/https_gates.json'); + execSync(`git commit -m "bench(ci): record HTTPS gate run [skip ci]"`); + execSync('git push'); + } diff --git a/benchmarks/https_gates.json b/benchmarks/https_gates.json new file mode 100644 index 000000000..f44998bf0 --- /dev/null +++ b/benchmarks/https_gates.json @@ -0,0 +1,15 @@ +[ + { + "timestamp": "2026-10-04T21:07:18+0900", + "runner_hw": "12 vCPU | AMD Ryzen 5 5600X 6-Core Processor", + "tls_rtt_p50_ms": 0.084, + "tls_rtt_p90_ms": 0.11, + "https_churn_rps": 1596.46, + "http_churn_rps": 14244.68, + "https_keepalive_rps": 151444.8, + "http_keepalive_rps": 274146.39, + "https_keepalive_ratio": 0.552, + "https_big_gbps": 4.06, + "http_big_gbps": 11.88 + } +] diff --git a/scripts/ci/https_gates_eval.py b/scripts/ci/https_gates_eval.py new file mode 100644 index 000000000..169283efc --- /dev/null +++ b/scripts/ci/https_gates_eval.py @@ -0,0 +1,131 @@ +"""Evaluate the HTTPS gate row produced by https_perf_gates.sh. + +Gate policy mirrors runner_baseline.py (which this imports): an absolute +backstop that must hold on any hardware, plus a same-runner-CPU comparison +against the median of earlier runs once at least ``min_samples`` exist on +that CPU. Absolute numbers move a lot between CI CPUs, so the backstops are +wide; the same-CPU check is what catches real regressions early. + +Throughput metrics are "min" gates (fail when below backstop / median +divided by the allowance); the RTT metric is a "max" gate. The https/http +keep-alive ratio is recorded as a diagnostic signal, not gated. + +Usage: + https_gates_eval.py RESULT.json [HISTORY.json] # evaluate, exit 1 on fail + https_gates_eval.py --update RESULT.json [HISTORY.json] +""" +import json +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from runner_baseline import runner_key, same_runner_values + +HISTORY = Path(__file__).resolve().parent.parent.parent / "benchmarks" / "https_gates.json" + +# (metric, direction, absolute backstop, same-CPU allowance) +# Backstops come from measured CWIST 3.9 numbers on a Ryzen 5600X (12-core): +# churn ~1650/s, keep-alive ~167-190k/s, 1MiB ~3.9-4.5GB/s, RTT p50 ~0.2ms, +# each with real margin; a healthy tree on a slow CI CPU still passes. The +# pre-#307 tree measured ~600/s churn, ~43ms RTT p50, so these backstops +# would fail it on any hardware. +GATES = [ + ("tls_rtt_p50_ms", "max", 5.0, 2.0), + ("https_churn_rps", "min", 1200.0, 1.25), + ("https_keepalive_rps", "min", 130000.0, 1.25), + ("https_big_gbps", "min", 3.0, 1.30), + # Plaintext controls: collateral damage to the plain path fails the gate. + ("http_churn_rps", "min", 8000.0, 1.25), + ("http_keepalive_rps", "min", 180000.0, 1.25), + ("http_big_gbps", "min", 8.0, 1.30), +] + + +def evaluate(history, row): + failures = [] + details = [] + for metric, direction, backstop, allowance in GATES: + value = row.get(metric) + if not isinstance(value, (int, float)) or value <= 0: + failures.append(f"{metric}: missing or non-positive in result row") + continue + + if direction == "max": + if value > backstop: + failures.append(f"{metric}: {value:.3f} over the {backstop}ms " + f"absolute backstop") + continue + else: + if value < backstop: + failures.append(f"{metric}: {value:.1f} under the {backstop} " + f"absolute backstop") + continue + + key = runner_key(row) + if key is None: + details.append(f"{metric}: {value:.3f}, runner not recorded, " + f"backstop only") + continue + past = same_runner_values(history, key, metric) + if len(past) < 5: + details.append(f"{metric}: {value:.3f} on {key}, only " + f"{len(past)} earlier run(s), backstop only") + continue + from statistics import median + base = median(past) + if direction == "max": + limit = base * allowance + if value > limit: + failures.append(f"{metric}: {value:.3f} on {key} over {limit:.3f} " + f"({allowance}x the {base:.3f} median of " + f"{len(past)} earlier runs)") + continue + else: + limit = base / allowance + if value < limit: + failures.append(f"{metric}: {value:.1f} on {key} under {limit:.1f} " + f"(median {base:.1f} of {len(past)} earlier " + f"runs / {allowance})") + continue + details.append(f"{metric}: {value:.3f} on {key}, within same-CPU gate") + + ratio = row.get("https_keepalive_ratio") + if isinstance(ratio, (int, float)): + details.append(f"https_keepalive_ratio: {ratio:.3f} (signal only, " + f"~0.65 expected; large drops hint at TLS-path damage " + f"even when both absolute gates pass)") + return failures, details + + +def main(): + args = sys.argv[1:] + update = args and args[0] == "--update" + if update: + args = args[1:] + result_path = Path(args[0]) if args else Path("https-gates-result.json") + history_path = Path(args[1]) if len(args) > 1 else HISTORY + + row = json.loads(result_path.read_text()) + try: + history = json.loads(history_path.read_text()) + except (OSError, ValueError): + history = [] + + if update: + history = [r for r in history + if r.get("timestamp") != row.get("timestamp")] + history.append(row) + history_path.write_text(json.dumps(history[-100:], indent=2) + "\n") + print(f"updated {history_path} ({len(history)} rows)") + + failures, details = evaluate(history, row) + for line in details: + print(f"ok: {line}") + for line in failures: + print(f"FAIL: {line}") + print(f"https gates: {len(GATES) - len(failures)}/{len(GATES)} passed") + sys.exit(1 if failures else 0) + + +if __name__ == "__main__": + main() diff --git a/scripts/ci/https_perf_gates.sh b/scripts/ci/https_perf_gates.sh new file mode 100755 index 000000000..3d8e63f43 --- /dev/null +++ b/scripts/ci/https_perf_gates.sh @@ -0,0 +1,205 @@ +#!/usr/bin/env bash +# TLS performance gates for CWIST 3.9 (issues #306 / #307). +# +# Measures five gate metrics against a bench server built from the current +# tree and prints them as a single JSON row (metrics + runner_hw) to +# https-gates-result.json in the current directory. Gate evaluation -- +# absolute backstops plus same-runner-CPU relative checks -- lives in +# https_gates_eval.py; this script only produces the row. +# +# Gate inventory (measured baselines on a 12-core Ryzen 5600X, loopback, +# ECDSA P-256 self-signed cert generated below): +# 1. tls_rtt_p50_ms sequential TLS client WITHOUT TCP_NODELAY, +# first-response RTT p50 over 200 fresh connections. +# Regression test for the #307 Nagle/delayed-ACK +# stall (43 ms before the fix, ~0.2 ms after). +# Backstop: p50 <= 5 ms. +# 2. https_churn_rps ab -n 6000 -c 32, new connection per request. +# ~600/s before #307, ~1650/s after. Backstop >= 1200. +# 3. https_keepalive_rps wrk -t4 -c100 -d10s. ~167-190k/s. Backstop >= 130k. +# 4. https_big_Bps wrk -t4 -c32 -d10s on a 1 MiB body. ~3.9-4.5GB/s. +# Backstop >= 3 GB/s. +# 5. http_* controls same workloads plaintext; detect collateral damage +# to the plain path. The https/http keep-alive ratio +# (~0.65) is recorded as a signal, not gated. +set -eu + +OUT=${1:-https-gates-result.json} + +# --- cert: self-signed, generated at runtime (no example/ certs) ----------- +openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 \ + -keyout /tmp/https_gates_key.pem -out /tmp/https_gates_cert.pem \ + -days 1 -nodes -subj "/CN=localhost" 2>/dev/null + +# --- bench server ----------------------------------------------------------- +cat << 'EOF' > /tmp/https_gates_server.c +#include +#include +#include +#include + +static char big_body[1024 * 1024]; +static void hello(cwist_http_request *req, cwist_http_response *res) { + (void)req; + cwist_sstring_assign(res->body, "{\"msg\":\"hello\"}"); +} +static void big(cwist_http_request *req, cwist_http_response *res) { + (void)req; + cwist_http_response_set_body_ptr(res, big_body, sizeof(big_body)); +} + +int main(int argc, char **argv) { + for (size_t i = 0; i < sizeof(big_body); i++) big_body[i] = (char)('a' + (i % 26)); + if (argc < 3) { fprintf(stderr, "usage: %s http|https port\n", argv[0]); return 1; } + cwist_app *app = cwist_app_create(); + if (strcmp(argv[1], "https") == 0) + cwist_app_use_https(app, "/tmp/https_gates_cert.pem", "/tmp/https_gates_key.pem"); + cwist_app_get(app, "/", hello); + cwist_app_get(app, "/big", big); + cwist_app_listen(app, atoi(argv[2])); + cwist_app_destroy(app); + return 0; +} +EOF + +# --- gate 1 client: sequential TLS, NO TCP_NODELAY -------------------------- +cat << 'EOF' > /tmp/https_gates_rtt.c +/* Sequential TLS first-response RTT probe. Deliberately does NOT set + * TCP_NODELAY: this is the client shape that exposed the #307 quickack + * stall (request held behind Nagle waiting for the server's delayed ACK + * of the TLS Finished). Prints "p50_ms=". */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static double now(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec + ts.tv_nsec * 1e-9; +} + +static int cmpd(const void *a, const void *b) { + double x = *(const double *)a, y = *(const double *)b; + return x < y ? -1 : x > y; +} + +int main(int argc, char **argv) { + if (argc < 4) { fprintf(stderr, "usage: %s ip port iters\n", argv[0]); return 2; } + const char *req = "GET / HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n"; + int iters = atoi(argv[3]); + double *rtt = malloc(sizeof(double) * iters); + SSL_CTX *ctx = SSL_CTX_new(TLS_client_method()); + SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); /* self-signed cert */ + for (int i = 0; i < iters; i++) { + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in sa = {0}; + sa.sin_family = AF_INET; + sa.sin_port = htons(atoi(argv[2])); + inet_pton(AF_INET, argv[1], &sa.sin_addr); + if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) return 1; + /* Intentionally no TCP_NODELAY here -- see file header. */ + SSL *ssl = SSL_new(ctx); + SSL_set_fd(ssl, fd); + if (SSL_connect(ssl) != 1) return 1; + double t0 = now(); + SSL_write(ssl, req, (int)strlen(req)); + char buf[4096]; + if (SSL_read(ssl, buf, sizeof(buf)) <= 0) return 1; + rtt[i] = now() - t0; + SSL_shutdown(ssl); + SSL_free(ssl); + close(fd); + } + qsort(rtt, iters, sizeof(double), cmpd); + printf("p50_ms=%.3f p90_ms=%.3f\n", rtt[iters / 2] * 1e3, rtt[(int)(iters * 0.9)] * 1e3); + return 0; +} +EOF + +# --- build ------------------------------------------------------------------ +make -j"$(nproc)" libcwist.a > /tmp/https_gates_build.log 2>&1 || { + tail -30 /tmp/https_gates_build.log; exit 1; } + +CWIST_LIBS="libcwist.a lib/cnats/build/lib/libnats_static.a lib/libttak/lib/libttak.a \ + lib/cjson/libcjson.a lib/uriparser/build/liburiparser.a \ + lib/lsquic/build/src/liblsquic/liblsquic.a lib/boringssl/build/libssl.a \ + lib/boringssl/build/libcrypto.a -lcurl -lnghttp2 -lbrotlienc -lbrotlicommon \ + -lbrotlidec -pthread -ldl -lm -lstdc++ -lz -lzstd" + +gcc -O2 -I./include -I./lib -I./lib/libttak/include -I./lib/cjson -I./lib/sqlite3 \ + -I./lib/uriparser/include -I./lib/cnats/src -I./lib/boringssl/include \ + -I./lib/lsquic/include -I./lib/multipart-parser-c \ + -D_GNU_SOURCE -D_XOPEN_SOURCE=700 -D_REENTRANT -DSQLITE_ENABLE_DESERIALIZE \ + -o /tmp/https_gates_server /tmp/https_gates_server.c $CWIST_LIBS + +gcc -O2 -o /tmp/https_gates_rtt /tmp/https_gates_rtt.c -lssl -lcrypto + +# --- run servers ------------------------------------------------------------ +/tmp/https_gates_server https 18443 > /tmp/https_gates_srv.log 2>&1 & +HTTPS_PID=$! +/tmp/https_gates_server http 18080 > /tmp/https_gates_srv_http.log 2>&1 & +HTTP_PID=$! +trap 'kill -TERM $HTTPS_PID $HTTP_PID 2>/dev/null || true' EXIT + +for i in $(seq 1 30); do + curl -sfk https://127.0.0.1:18443/ > /dev/null 2>&1 && curl -sf http://127.0.0.1:18080/ > /dev/null 2>&1 && break + sleep 1 +done + +# --- measurements ----------------------------------------------------------- +# Gate 1: TLS request-phase RTT, no-TCP_NODELAY sequential client. +RTT_OUT=$(/tmp/https_gates_rtt 127.0.0.1 18443 200) +RTT_P50=$(echo "$RTT_OUT" | sed -n 's/^p50_ms=\([0-9.]*\).*/\1/p') +RTT_P90=$(echo "$RTT_OUT" | sed -n 's/.*p90_ms=\([0-9.]*\)/\1/p') + +# Gate 2: connection churn (ab, new connection per request). +HTTPS_CHURN=$(ab -n 6000 -c 32 https://127.0.0.1:18443/ 2>/dev/null | sed -n 's/^Requests per second:\s*\([0-9.]*\).*/\1/p') +HTTP_CHURN=$(ab -n 6000 -c 32 http://127.0.0.1:18080/ 2>/dev/null | sed -n 's/^Requests per second:\s*\([0-9.]*\).*/\1/p') + +# Warmup for the keep-alive gates (results discarded). +wrk -t4 -c100 -d5s https://127.0.0.1:18443/ > /dev/null 2>&1 || true +wrk -t4 -c100 -d5s http://127.0.0.1:18080/ > /dev/null 2>&1 || true + +# Gate 3: keep-alive throughput. +HTTPS_KEEP=$(wrk -t4 -c100 -d10s https://127.0.0.1:18443/ 2>/dev/null | sed -n 's/^Requests\/sec:\s*\([0-9.]*\).*/\1/p') +HTTP_KEEP=$(wrk -t4 -c100 -d10s http://127.0.0.1:18080/ 2>/dev/null | sed -n 's/^Requests\/sec:\s*\([0-9.]*\).*/\1/p') + +# Gate 4: 1 MiB transfer throughput. +HTTPS_BIG=$(wrk -t4 -c32 -d10s https://127.0.0.1:18443/big 2>/dev/null | sed -n 's/^Transfer\/sec:\s*\([0-9.]*\)GB.*/\1/p') +[ -n "$HTTPS_BIG" ] || HTTPS_BIG=$(wrk -t4 -c32 -d10s https://127.0.0.1:18443/big 2>/dev/null | sed -n 's/^Transfer\/sec:\s*\([0-9.]*\)MB.*/0\1/p') +HTTP_BIG=$(wrk -t4 -c32 -d10s http://127.0.0.1:18080/big 2>/dev/null | sed -n 's/^Transfer\/sec:\s*\([0-9.]*\)GB.*/\1/p') + +RUNNER_HW="$(nproc) vCPU | $(grep -m1 'model name' /proc/cpuinfo | cut -d: -f2- | sed 's/^ *//')" + +# --- row -------------------------------------------------------------------- +python3 - "$OUT" "$RTT_P50" "$RTT_P90" "$HTTPS_CHURN" "$HTTP_CHURN" \ + "$HTTPS_KEEP" "$HTTP_KEEP" "$HTTPS_BIG" "$HTTP_BIG" "$RUNNER_HW" <<'EOF' +import json, sys, time + +(out, rtt_p50, rtt_p90, https_churn, http_churn, https_keep, http_keep, + https_big, http_big, runner_hw) = sys.argv[1:11] + +row = { + "timestamp": time.strftime("%Y-%m-%dT%H:%M:%S%z"), + "runner_hw": runner_hw, + "tls_rtt_p50_ms": float(rtt_p50), + "tls_rtt_p90_ms": float(rtt_p90), + "https_churn_rps": float(https_churn), + "http_churn_rps": float(http_churn), + "https_keepalive_rps": float(https_keep), + "http_keepalive_rps": float(http_keep), + "https_keepalive_ratio": round(float(https_keep) / float(http_keep), 3), + "https_big_gbps": float(https_big), + "http_big_gbps": float(http_big), +} +with open(out, "w") as f: + json.dump(row, f, indent=2) + f.write("\n") +print(json.dumps(row, indent=2)) +EOF From 94eb670bd6aef565ee404fd2c6d52b99d438ac8e Mon Sep 17 00:00:00 2001 From: Lee Yunjin Date: Sun, 4 Oct 2026 22:01:28 +0900 Subject: [PATCH 2/2] ci(perf): relax HTTPS gate backstops for shared CI runner noise CI run 37201724744 (PR #309, shared AMD EPYC 9V45 runner) failed only http_big_gbps: 7.8 measured vs the 8.0 backstop, while every other gate passed with the local-box numbers as reference. The absolute backstops were calibrated on a quiet 12-core desktop and are too tight for noisy shared runners. Recalibrate each backstop against the observed CI values with variance headroom: tls_rtt_p50_ms <=5ms (unchanged; observed 0.070) https_churn_rps >=1200 -> >=1000 (observed 2013) https_keepalive_rps >=130k -> >=110k (observed 135570) https_big_gbps >=3 -> >=2.5 (observed 4.03) http_churn_rps >=8000 -> >=6000 (observed 15793) http_keepalive_rps >=180k -> >=150k (observed 206650) http_big_gbps >=8 -> >=5.5 (observed 7.8) Verified: a row rebuilt from the failed run's observed values now passes 7/7, and the pre-#307 row (43.2ms RTT p50, 683/s churn) still fails gates 1 and 2. The https_keepalive_ratio diagnostic signal is unchanged. --- scripts/ci/https_gates_eval.py | 28 +++++++++++++++++----------- scripts/ci/https_perf_gates.sh | 6 +++--- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/scripts/ci/https_gates_eval.py b/scripts/ci/https_gates_eval.py index 169283efc..2922b26f3 100644 --- a/scripts/ci/https_gates_eval.py +++ b/scripts/ci/https_gates_eval.py @@ -24,20 +24,26 @@ HISTORY = Path(__file__).resolve().parent.parent.parent / "benchmarks" / "https_gates.json" # (metric, direction, absolute backstop, same-CPU allowance) -# Backstops come from measured CWIST 3.9 numbers on a Ryzen 5600X (12-core): -# churn ~1650/s, keep-alive ~167-190k/s, 1MiB ~3.9-4.5GB/s, RTT p50 ~0.2ms, -# each with real margin; a healthy tree on a slow CI CPU still passes. The -# pre-#307 tree measured ~600/s churn, ~43ms RTT p50, so these backstops -# would fail it on any hardware. +# Backstops are calibrated so a healthy tree passes on the noisiest shared +# CI runner we have seen, with real margin for run-to-run variance: +# local Ryzen 5600X (12-core): churn ~1650/s, keep-alive ~151-190k/s, +# 1MiB ~4.1GB/s (https) / ~12-14GB/s (http), RTT p50 ~0.09ms. +# shared EPYC 9V45 CI runner (run 37201724744): churn 2013/s https / +# 15793/s http, keep-alive 135570/s https / 206650/s http, +# 1MiB 4.03GB/s https / 7.8GB/s http. +# The pre-#307 tree measured ~600/s churn, ~43ms RTT p50, so these +# backstops still fail it on any hardware. The same-CPU relative check +# (runner_baseline.py convention) is what catches smaller regressions once +# enough history accumulates for a runner CPU. GATES = [ ("tls_rtt_p50_ms", "max", 5.0, 2.0), - ("https_churn_rps", "min", 1200.0, 1.25), - ("https_keepalive_rps", "min", 130000.0, 1.25), - ("https_big_gbps", "min", 3.0, 1.30), + ("https_churn_rps", "min", 1000.0, 1.25), + ("https_keepalive_rps", "min", 110000.0, 1.25), + ("https_big_gbps", "min", 2.5, 1.30), # Plaintext controls: collateral damage to the plain path fails the gate. - ("http_churn_rps", "min", 8000.0, 1.25), - ("http_keepalive_rps", "min", 180000.0, 1.25), - ("http_big_gbps", "min", 8.0, 1.30), + ("http_churn_rps", "min", 6000.0, 1.25), + ("http_keepalive_rps", "min", 150000.0, 1.25), + ("http_big_gbps", "min", 5.5, 1.30), ] diff --git a/scripts/ci/https_perf_gates.sh b/scripts/ci/https_perf_gates.sh index 3d8e63f43..1f2571475 100755 --- a/scripts/ci/https_perf_gates.sh +++ b/scripts/ci/https_perf_gates.sh @@ -15,10 +15,10 @@ # stall (43 ms before the fix, ~0.2 ms after). # Backstop: p50 <= 5 ms. # 2. https_churn_rps ab -n 6000 -c 32, new connection per request. -# ~600/s before #307, ~1650/s after. Backstop >= 1200. -# 3. https_keepalive_rps wrk -t4 -c100 -d10s. ~167-190k/s. Backstop >= 130k. +# ~600/s before #307, ~1650/s after. Backstop >= 1000. +# 3. https_keepalive_rps wrk -t4 -c100 -d10s. ~135-190k/s observed. Backstop >= 110k. # 4. https_big_Bps wrk -t4 -c32 -d10s on a 1 MiB body. ~3.9-4.5GB/s. -# Backstop >= 3 GB/s. +# Backstop >= 2.5 GB/s. # 5. http_* controls same workloads plaintext; detect collateral damage # to the plain path. The https/http keep-alive ratio # (~0.65) is recorded as a signal, not gated.