From 868ec3af3085a9dad844fd1fd83e1c853f26f802 Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Tue, 21 Apr 2026 10:53:54 -0500 Subject: [PATCH 1/4] feat: add isolated vsr registry benchmark profile --- .github/workflows/benchmark.yaml | 8 ++ README.md | 16 +++ bench | 46 ++++++++- scripts/network-isolation.sh | 166 +++++++++++++++++++++++++++++++ scripts/registry/common.sh | 31 +++++- scripts/setup.sh | 10 ++ 6 files changed, 274 insertions(+), 3 deletions(-) create mode 100644 scripts/network-isolation.sh diff --git a/.github/workflows/benchmark.yaml b/.github/workflows/benchmark.yaml index 5e4068a47b..585efa6abf 100644 --- a/.github/workflows/benchmark.yaml +++ b/.github/workflows/benchmark.yaml @@ -18,6 +18,12 @@ on: runs: description: "The number of runs on each benchmark" default: "5" + network_profile: + description: "Optional network isolation profile for registry benchmarks" + default: "" + network_rate_kbps: + description: "Bandwidth cap in KB/s for supported network profiles" + default: "8192" schedule: # GitHub Actions cron uses UTC. 10:17 UTC ~= 2:17 AM Pacific (PST). - cron: "17 10 * * *" @@ -104,6 +110,8 @@ jobs: BENCH_INCLUDE: ${{ fromJson(inputs.binaries || '"npm,yarn,berry,zpm,pnpm,pnpm11,pacquet,vlt,bun,deno,aube,nx,turbo,vp,node"') }} BENCH_WARMUP: ${{ (github.event_name == 'push' && github.ref != 'refs/heads/main') && '1' || (inputs.warmup || '2') }} BENCH_RUNS: ${{ (github.event_name == 'push' && github.ref != 'refs/heads/main') && '1' || (inputs.runs || '5') }} + BENCH_NETWORK_PROFILE: ${{ inputs.network_profile || '' }} + BENCH_TOXIPROXY_RATE_KBPS: ${{ inputs.network_rate_kbps || '8192' }} steps: - uses: actions/checkout@v6 - name: Install Node diff --git a/README.md b/README.md index 789cca645b..f9c3eff8c9 100644 --- a/README.md +++ b/README.md @@ -90,12 +90,25 @@ Examples: # Run only aws registry benchmarks (requires token) CODEARTIFACT_AUTH_TOKEN= ./bench run --variation=registry-clean --fixtures=next --registries=aws + +# Run isolated VSR registry benchmarks through toxiproxy with a bandwidth cap +VLT_REGISTRY_AUTH_TOKEN= ./bench run --variation=registry-clean --fixtures=next --registries=vlt --network-profile=vsr-bandwidth --network-rate-kbps=8192 ``` Auth notes: - `aws` requires `CODEARTIFACT_AUTH_TOKEN`. +#### Network isolation + +Registry benchmarks can optionally be routed through a local `toxiproxy` instance to make the benchmark use a controlled network path instead of the host's default connection. + +- `vsr-bandwidth` is currently supported for `vlt` registry benchmarks only. +- This profile rewrites `registry.vlt.io` to a local toxiproxy listener, then proxies TLS traffic to the real VSR upstream with symmetric bandwidth limits. +- Use `--registries=vlt` together with `--network-profile=vsr-bandwidth`. +- `--network-rate-kbps` controls both upstream and downstream bandwidth in KB/s. +- The helper temporarily edits `/etc/hosts`, so local runs require `sudo` access. + ## Testing Script Execution This suite also tests the performance of basic script execution (ex. `npm run foo`). Notably, for any given build, test or deployment task the spawning of the process is a fraction of the overall execution time. That said, this is a commonly tracked workflow by various developer tools as it involves the common set of tasks: startup, filesystem read (`package.json`) & finally, spawning the process/command. @@ -155,6 +168,9 @@ This suite also tests the performance of basic script execution (ex. `npm run fo # Run multiple variations in one command ./bench run --fixtures=svelte --variation=lockfile,registry-lockfile,run --registries=npm,vlt + # Run an isolated VSR benchmark with a bandwidth cap + VLT_REGISTRY_AUTH_TOKEN= ./bench run --variation=registry-clean --fixtures=next --registries=vlt --network-profile=vsr-bandwidth --network-rate-kbps=8192 + # Script-execution benchmark ./bench run --variation=run --pms=vlt diff --git a/bench b/bench index 9d7b940775..e69b4605e7 100755 --- a/bench +++ b/bench @@ -53,7 +53,7 @@ AVAILABLE_REGISTRIES=( usage() { cat <<'EOF' Usage: - ./bench run [--fixtures=] [--pms=] [--variation=] [--runs=] [--warmup=] [--chart] [--process] [--date=] [--clean] + ./bench run [--fixtures=] [--pms=] [--variation=] [--runs=] [--warmup=] [--chart] [--process] [--date=] [--clean] [--network-profile=] [--network-rate-kbps=] ./bench chart [--fixtures=] [--variation=] [--date=] [--clean] ./bench process [--dry-run] ./bench list @@ -66,6 +66,8 @@ Options: --runs Hyperfine runs (default: scripts/variations/common.sh default) --warmup Hyperfine warmup runs (default: scripts/variations/common.sh default) --timeout Per-command timeout in seconds (default: 300) + --network-profile Optional network isolation profile for registry-* runs + --network-rate-kbps Bandwidth cap in KB/s for supported profiles (default: 8192) --chart Generate chart data and copy to app/latest --process Process results after run (clean + dated/latest outputs + chart data) --date Date folder for chart output (default: today) @@ -82,6 +84,7 @@ Examples: ./bench run --fixtures=next --chart --process --runs=3 ./bench run --variation=registry-clean --fixtures=next ./bench run --variation=registry-lockfile --registries=npm,vlt + ./bench run --variation=registry-clean --fixtures=next --registries=vlt --network-profile=vsr-bandwidth --network-rate-kbps=8192 CODEARTIFACT_AUTH_TOKEN= ./bench run --variation=registry-clean --fixtures=next --registries=aws GH_REGISTRY= GH_AUTH_TOKEN= ./bench run --variation=registry-clean --fixtures=next --registries=github ./bench chart --fixtures=next --variation=clean @@ -378,6 +381,8 @@ run_bench() { local registries_input="${10:-}" local process_results="${11:-0}" local timeout_input="${12:-}" + local network_profile_input="${13:-}" + local network_rate_input="${14:-}" if [[ ! -f "$BENCH_SCRIPT" ]]; then echo "Error: Missing benchmark script at $BENCH_SCRIPT" @@ -399,6 +404,9 @@ run_bench() { if [[ -n "$timeout_input" ]]; then validate_number "$timeout_input" "--timeout" fi + if [[ -n "$network_rate_input" ]]; then + validate_number "$network_rate_input" "--network-rate-kbps" + fi local pms_env="" if [[ -n "$pms_input" ]]; then @@ -432,6 +440,16 @@ run_bench() { else echo " timeout: 300s (default)" fi + if [[ -n "$network_profile_input" ]]; then + echo " network profile: $network_profile_input" + else + echo " network profile: disabled" + fi + if [[ -n "$network_rate_input" ]]; then + echo " network rate: ${network_rate_input} KB/s" + else + echo " network rate: 8192 KB/s (default)" + fi if [[ "$clean_results" -eq 1 ]]; then echo " clean: enabled" else @@ -488,6 +506,12 @@ run_bench() { if [[ -n "$timeout_input" ]]; then env_args+=("BENCH_TIMEOUT=$timeout_input") fi + if [[ -n "$network_profile_input" ]]; then + env_args+=("BENCH_NETWORK_PROFILE=$network_profile_input") + fi + if [[ -n "$network_rate_input" ]]; then + env_args+=("BENCH_TOXIPROXY_RATE_KBPS=$network_rate_input") + fi local cmd=(bash "$BENCH_SCRIPT" "$fixture" "$variation") @@ -645,6 +669,8 @@ case "$command" in CHART_RESULTS=0 PROCESS_RESULTS=0 CHART_DATE="" + NETWORK_PROFILE_INPUT="" + NETWORK_RATE_INPUT="" while [[ $# -gt 0 ]]; do case "$1" in @@ -704,6 +730,22 @@ case "$command" in TIMEOUT_INPUT="${1#*=}" shift ;; + --network-profile) + NETWORK_PROFILE_INPUT="${2:-}" + shift 2 + ;; + --network-profile=*) + NETWORK_PROFILE_INPUT="${1#*=}" + shift + ;; + --network-rate-kbps) + NETWORK_RATE_INPUT="${2:-}" + shift 2 + ;; + --network-rate-kbps=*) + NETWORK_RATE_INPUT="${1#*=}" + shift + ;; --chart) CHART_RESULTS=1 shift @@ -744,7 +786,7 @@ case "$command" in esac done - run_bench "$FIXTURES_INPUT" "$PMS_INPUT" "$VARIATION_INPUT" "$RUNS_INPUT" "$WARMUP_INPUT" "$DRY_RUN" "$CLEAN_RESULTS" "$CHART_RESULTS" "$CHART_DATE" "$REGISTRIES_INPUT" "$PROCESS_RESULTS" "$TIMEOUT_INPUT" + run_bench "$FIXTURES_INPUT" "$PMS_INPUT" "$VARIATION_INPUT" "$RUNS_INPUT" "$WARMUP_INPUT" "$DRY_RUN" "$CLEAN_RESULTS" "$CHART_RESULTS" "$CHART_DATE" "$REGISTRIES_INPUT" "$PROCESS_RESULTS" "$TIMEOUT_INPUT" "$NETWORK_PROFILE_INPUT" "$NETWORK_RATE_INPUT" ;; process) DRY_RUN=0 diff --git a/scripts/network-isolation.sh b/scripts/network-isolation.sh new file mode 100644 index 0000000000..25ba4a6739 --- /dev/null +++ b/scripts/network-isolation.sh @@ -0,0 +1,166 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +BENCH_TOXIPROXY_API_HOST="${BENCH_TOXIPROXY_API_HOST:-127.0.0.1}" +BENCH_TOXIPROXY_API_PORT="${BENCH_TOXIPROXY_API_PORT:-8474}" +BENCH_TOXIPROXY_LISTEN_HOST="${BENCH_TOXIPROXY_LISTEN_HOST:-127.0.0.1}" +BENCH_TOXIPROXY_LISTEN_PORT="${BENCH_TOXIPROXY_LISTEN_PORT:-7443}" +BENCH_TOXIPROXY_RATE_KBPS="${BENCH_TOXIPROXY_RATE_KBPS:-8192}" +BENCH_TOXIPROXY_PROXY_NAME="${BENCH_TOXIPROXY_PROXY_NAME:-bench-vsr}" +BENCH_TOXIPROXY_PID_FILE="${BENCH_TOXIPROXY_PID_FILE:-/tmp/vlt-benchmarks-toxiproxy.pid}" +BENCH_TOXIPROXY_LOG_FILE="${BENCH_TOXIPROXY_LOG_FILE:-/tmp/vlt-benchmarks-toxiproxy.log}" +BENCH_TOXIPROXY_HOSTS_START="# >>> vlt-benchmarks toxiproxy >>>" +BENCH_TOXIPROXY_HOSTS_END="# <<< vlt-benchmarks toxiproxy <<<" + +toxiproxy_api_url() { + echo "http://${BENCH_TOXIPROXY_API_HOST}:${BENCH_TOXIPROXY_API_PORT}" +} + +ensure_toxiproxy_installed() { + if ! command -v toxiproxy-server >/dev/null 2>&1; then + echo "Error: toxiproxy-server is required for BENCH_NETWORK_PROFILE=$BENCH_NETWORK_PROFILE" + exit 1 + fi +} + +resolve_ipv4() { + local host="$1" + + node -e ' + const dns = require("node:dns").promises + dns.lookup(process.argv[1], { family: 4 }).then((result) => { + process.stdout.write(result.address) + }).catch((error) => { + console.error(error.message) + process.exit(1) + }) + ' "$host" +} + +wait_for_toxiproxy() { + local api + api="$(toxiproxy_api_url)" + + for _ in {1..40}; do + if curl -fsS "$api/version" >/dev/null 2>&1; then + return 0 + fi + sleep 0.25 + done + + echo "Error: toxiproxy did not start on $api" + exit 1 +} + +start_toxiproxy_server() { + local api + api="$(toxiproxy_api_url)" + + if curl -fsS "$api/version" >/dev/null 2>&1; then + return 0 + fi + + nohup toxiproxy-server -host "$BENCH_TOXIPROXY_API_HOST" -port "$BENCH_TOXIPROXY_API_PORT" \ + >"$BENCH_TOXIPROXY_LOG_FILE" 2>&1 & + echo "$!" > "$BENCH_TOXIPROXY_PID_FILE" + BENCH_TOXIPROXY_STARTED=1 + wait_for_toxiproxy +} + +reset_toxiproxy() { + curl -fsS -X POST "$(toxiproxy_api_url)/reset" >/dev/null +} + +update_hosts_mapping() { + local host="$1" + local ip="$2" + local target="${ip} ${host}" + + sudo node -e ' + const fs = require("node:fs") + + const filePath = "/etc/hosts" + const start = process.argv[1] + const end = process.argv[2] + const entry = process.argv[3] + const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + + let content = fs.readFileSync(filePath, "utf8") + const block = `${start}\n${entry}\n${end}` + const pattern = new RegExp(`${escapeRegex(start)}\\n?[\\s\\S]*?${escapeRegex(end)}\\n?`, "g") + content = content.replace(pattern, "") + content = content.replace(/\n*$/, "\n") + fs.writeFileSync(filePath, `${content}${block}\n`) + ' "$BENCH_TOXIPROXY_HOSTS_START" "$BENCH_TOXIPROXY_HOSTS_END" "$target" +} + +clear_hosts_mapping() { + sudo node -e ' + const fs = require("node:fs") + + const filePath = "/etc/hosts" + const start = process.argv[1] + const end = process.argv[2] + const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + + let content = fs.readFileSync(filePath, "utf8") + const pattern = new RegExp(`${escapeRegex(start)}\\n?[\\s\\S]*?${escapeRegex(end)}\\n?`, "g") + content = content.replace(pattern, "") + fs.writeFileSync(filePath, content.replace(/\n*$/, "\n")) + ' "$BENCH_TOXIPROXY_HOSTS_START" "$BENCH_TOXIPROXY_HOSTS_END" +} + +create_toxiproxy_proxy() { + local upstream_host="$1" + local upstream_ip="$2" + local upstream_port="$3" + local rate_kbps="$4" + + curl -fsS -X POST "$(toxiproxy_api_url)/proxies" \ + -H 'Content-Type: application/json' \ + -d "{\"name\":\"${BENCH_TOXIPROXY_PROXY_NAME}\",\"listen\":\"${BENCH_TOXIPROXY_LISTEN_HOST}:${BENCH_TOXIPROXY_LISTEN_PORT}\",\"upstream\":\"${upstream_ip}:${upstream_port}\"}" \ + >/dev/null + + curl -fsS -X POST "$(toxiproxy_api_url)/proxies/${BENCH_TOXIPROXY_PROXY_NAME}/toxics" \ + -H 'Content-Type: application/json' \ + -d "{\"name\":\"bandwidth_upstream\",\"type\":\"bandwidth\",\"stream\":\"upstream\",\"attributes\":{\"rate\":${rate_kbps}}}" \ + >/dev/null + + curl -fsS -X POST "$(toxiproxy_api_url)/proxies/${BENCH_TOXIPROXY_PROXY_NAME}/toxics" \ + -H 'Content-Type: application/json' \ + -d "{\"name\":\"bandwidth_downstream\",\"type\":\"bandwidth\",\"stream\":\"downstream\",\"attributes\":{\"rate\":${rate_kbps}}}" \ + >/dev/null + + echo "Toxiproxy configured for ${upstream_host} at ${BENCH_TOXIPROXY_LISTEN_HOST}:${BENCH_TOXIPROXY_LISTEN_PORT} (${rate_kbps} KB/s)" +} + +setup_vsr_bandwidth_proxy() { + local upstream_host="registry.vlt.io" + local upstream_path="/npm/" + local upstream_port="443" + local upstream_ip + + ensure_toxiproxy_installed + upstream_ip="$(resolve_ipv4 "$upstream_host")" + start_toxiproxy_server + reset_toxiproxy + create_toxiproxy_proxy "$upstream_host" "$upstream_ip" "$upstream_port" "$BENCH_TOXIPROXY_RATE_KBPS" + update_hosts_mapping "$upstream_host" "$BENCH_TOXIPROXY_LISTEN_HOST" + + BENCH_VSR_PROXY_URL="https://${upstream_host}:${BENCH_TOXIPROXY_LISTEN_PORT}${upstream_path}" + export BENCH_VSR_PROXY_URL +} + +cleanup_network_isolation() { + clear_hosts_mapping || true + + if curl -fsS "$(toxiproxy_api_url)/version" >/dev/null 2>&1; then + curl -fsS -X POST "$(toxiproxy_api_url)/reset" >/dev/null || true + fi + + if [ -n "${BENCH_TOXIPROXY_STARTED:-}" ] && [ -f "$BENCH_TOXIPROXY_PID_FILE" ]; then + kill "$(cat "$BENCH_TOXIPROXY_PID_FILE")" >/dev/null 2>&1 || true + rm -f "$BENCH_TOXIPROXY_PID_FILE" + fi +} diff --git a/scripts/registry/common.sh b/scripts/registry/common.sh index 9cef6d8d19..38457acb85 100644 --- a/scripts/registry/common.sh +++ b/scripts/registry/common.sh @@ -46,6 +46,8 @@ BENCH_RUNS="${BENCH_RUNS:=5}" # Per-command timeout in seconds (default: 5 minutes) BENCH_TIMEOUT="${BENCH_TIMEOUT:=300}" BENCH_LOGLEVEL="${BENCH_LOGLEVEL:=http}" +BENCH_NETWORK_PROFILE="${BENCH_NETWORK_PROFILE:=}" +BENCH_TOXIPROXY_RATE_KBPS="${BENCH_TOXIPROXY_RATE_KBPS:=8192}" BENCH_OUTPUT_FOLDER="$BENCH_RESULTS/$BENCH_FIXTURE/$BENCH_VARIATION" # Add --force for large fixture to bypass peer dependency errors @@ -71,6 +73,8 @@ BENCH_REGISTRY_AWS_NPMRC_KEY="${BENCH_REGISTRY_AWS_URL#http*://}" # The auth .npmrc key uses the URL as-is (without protocol). BENCH_REGISTRY_GITHUB_URL="https:${GH_REGISTRY:-}" BENCH_REGISTRY_GITHUB_NPMRC_KEY="${GH_REGISTRY#//}" +BENCH_REGISTRY_VLT_EFFECTIVE_URL="$BENCH_REGISTRY_VLT_URL" +BENCH_REGISTRY_VLT_EFFECTIVE_NPMRC_KEY="${BENCH_REGISTRY_VLT_EFFECTIVE_URL#http*://}" # Registry setup commands run in hyperfine --prepare (untimed, before each run). # Auth token is written as a literal placeholder so npm resolves it from env. @@ -81,7 +85,7 @@ BENCH_SETUP_REGISTRY_NPM="npm config set registry \"$BENCH_REGISTRY_NPM_URL\" -- # The vlt registry auth token is appended with a random suffix with the iteration number to # ensure that a fresh cache is used for each iteration. A future update to the vlt registry # will allow sharing a public cache regardless of the authorization header. -BENCH_SETUP_REGISTRY_VLT="npm config set registry \"$BENCH_REGISTRY_VLT_URL\" --location=project && npm config set \"//${BENCH_REGISTRY_VLT_URL_NPMRC_KEY}:_authToken=\\\${VLT_REGISTRY_AUTH_TOKEN}:$(head -c 16 /dev/urandom | xxd -p)_\\\${HYPERFINE_ITERATION}\" --location=project" +BENCH_SETUP_REGISTRY_VLT="npm config set registry \"$BENCH_REGISTRY_VLT_EFFECTIVE_URL\" --location=project && npm config set \"//${BENCH_REGISTRY_VLT_EFFECTIVE_NPMRC_KEY}:_authToken=\\\${VLT_REGISTRY_AUTH_TOKEN}:$(head -c 16 /dev/urandom | xxd -p)_\\\${HYPERFINE_ITERATION}\" --location=project" BENCH_SETUP_REGISTRY_AWS="npm config set registry \"$BENCH_REGISTRY_AWS_URL\" --location=project && npm config set \"//${BENCH_REGISTRY_AWS_NPMRC_KEY}:_authToken=\\\${CODEARTIFACT_AUTH_TOKEN}\" --location=project" BENCH_SETUP_REGISTRY_GITHUB="npm config set registry \"$BENCH_REGISTRY_GITHUB_URL\" --location=project && npm config set \"//${BENCH_REGISTRY_GITHUB_NPMRC_KEY}:_authToken=\\\${GH_AUTH_TOKEN}\" --location=project" @@ -153,7 +157,32 @@ if [ -n "$BENCH_INCLUDE_REG_GITHUB" ] && [ -z "${GH_REGISTRY:-}" ]; then exit 1 fi +if [ -n "$BENCH_NETWORK_PROFILE" ]; then + case "$BENCH_NETWORK_PROFILE" in + vsr-bandwidth) + if [ -z "$BENCH_INCLUDE_REG_VLT" ] || [ -n "$BENCH_INCLUDE_REG_NPM" ] || [ -n "$BENCH_INCLUDE_REG_AWS" ] || [ -n "$BENCH_INCLUDE_REG_GITHUB" ]; then + echo "Error: BENCH_NETWORK_PROFILE=vsr-bandwidth currently requires BENCH_INCLUDE_REGISTRY=vlt" + exit 1 + fi + + source "$BENCH_SCRIPTS/network-isolation.sh" + setup_vsr_bandwidth_proxy + trap cleanup_network_isolation EXIT + BENCH_REGISTRY_VLT_EFFECTIVE_URL="$BENCH_VSR_PROXY_URL" + BENCH_REGISTRY_VLT_EFFECTIVE_NPMRC_KEY="${BENCH_REGISTRY_VLT_EFFECTIVE_URL#http*://}" + BENCH_SETUP_REGISTRY_VLT="npm config set registry \"$BENCH_REGISTRY_VLT_EFFECTIVE_URL\" --location=project && npm config set \"//${BENCH_REGISTRY_VLT_EFFECTIVE_NPMRC_KEY}:_authToken=\\\${VLT_REGISTRY_AUTH_TOKEN}:$(head -c 16 /dev/urandom | xxd -p)_\\\${HYPERFINE_ITERATION}\" --location=project" + ;; + *) + echo "Error: Unknown BENCH_NETWORK_PROFILE '$BENCH_NETWORK_PROFILE'" + exit 1 + ;; + esac +fi + echo "Registry benchmarks will run: $BENCH_INCLUDE_REGISTRY" +if [ -n "$BENCH_NETWORK_PROFILE" ]; then + echo "Network isolation profile: $BENCH_NETWORK_PROFILE (${BENCH_TOXIPROXY_RATE_KBPS} KB/s)" +fi # Clean up & create the results directory rm -rf "$BENCH_OUTPUT_FOLDER" diff --git a/scripts/setup.sh b/scripts/setup.sh index 91ed6d114f..ba93f090b4 100644 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -37,11 +37,21 @@ wget -q "https://github.com/sharkdp/hyperfine/releases/download/${HYPERFINE_VERS sudo dpkg -i /tmp/hyperfine.deb rm -f /tmp/hyperfine.deb +# Install toxiproxy for opt-in network-isolated registry benchmarks +TOXIPROXY_VERSION_TAG="v2.12.0" +for binary in toxiproxy-server toxiproxy-cli; do + curl -fsSL "https://github.com/Shopify/toxiproxy/releases/download/${TOXIPROXY_VERSION_TAG}/${binary}-linux-${ARCH}" -o "/tmp/${binary}" + chmod +x "/tmp/${binary}" + sudo mv "/tmp/${binary}" "/usr/local/bin/${binary}" +done + echo "Required system dependencies installed successfully!" JQ_VERSION=$(jq --version) HYPERFINE_VERSION=$(hyperfine --version) +TOXIPROXY_SERVER_VERSION=$(toxiproxy-server -version 2>/dev/null || toxiproxy-server --version 2>/dev/null || echo "installed") echo "jq: $JQ_VERSION" echo "hyperfine: $HYPERFINE_VERSION" +echo "toxiproxy: $TOXIPROXY_SERVER_VERSION" # Install Node.js package managers and tools echo "Installing package managers and tools..." From cbf54beee0c3d809c1b69ef14a0a7d611d472d92 Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Tue, 21 Apr 2026 10:57:22 -0500 Subject: [PATCH 2/4] fix: make registry isolation the default benchmark path --- .github/workflows/benchmark.yaml | 4 +- README.md | 12 ++-- bench | 6 +- scripts/network-isolation.sh | 106 +++++++++++++++++++------------ scripts/registry/common.sh | 66 +++++++++++-------- 5 files changed, 115 insertions(+), 79 deletions(-) diff --git a/.github/workflows/benchmark.yaml b/.github/workflows/benchmark.yaml index 585efa6abf..f4e213e4af 100644 --- a/.github/workflows/benchmark.yaml +++ b/.github/workflows/benchmark.yaml @@ -20,7 +20,7 @@ on: default: "5" network_profile: description: "Optional network isolation profile for registry benchmarks" - default: "" + default: "registry-bandwidth" network_rate_kbps: description: "Bandwidth cap in KB/s for supported network profiles" default: "8192" @@ -110,7 +110,7 @@ jobs: BENCH_INCLUDE: ${{ fromJson(inputs.binaries || '"npm,yarn,berry,zpm,pnpm,pnpm11,pacquet,vlt,bun,deno,aube,nx,turbo,vp,node"') }} BENCH_WARMUP: ${{ (github.event_name == 'push' && github.ref != 'refs/heads/main') && '1' || (inputs.warmup || '2') }} BENCH_RUNS: ${{ (github.event_name == 'push' && github.ref != 'refs/heads/main') && '1' || (inputs.runs || '5') }} - BENCH_NETWORK_PROFILE: ${{ inputs.network_profile || '' }} + BENCH_NETWORK_PROFILE: ${{ inputs.network_profile || 'registry-bandwidth' }} BENCH_TOXIPROXY_RATE_KBPS: ${{ inputs.network_rate_kbps || '8192' }} steps: - uses: actions/checkout@v6 diff --git a/README.md b/README.md index f9c3eff8c9..444cd33893 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ Examples: CODEARTIFACT_AUTH_TOKEN= ./bench run --variation=registry-clean --fixtures=next --registries=aws # Run isolated VSR registry benchmarks through toxiproxy with a bandwidth cap -VLT_REGISTRY_AUTH_TOKEN= ./bench run --variation=registry-clean --fixtures=next --registries=vlt --network-profile=vsr-bandwidth --network-rate-kbps=8192 +VLT_REGISTRY_AUTH_TOKEN= ./bench run --variation=registry-clean --fixtures=next --registries=vlt --network-profile=registry-bandwidth --network-rate-kbps=8192 ``` Auth notes: @@ -101,11 +101,11 @@ Auth notes: #### Network isolation -Registry benchmarks can optionally be routed through a local `toxiproxy` instance to make the benchmark use a controlled network path instead of the host's default connection. +Registry benchmarks default to a local `toxiproxy` path so benchmark traffic uses a controlled network path instead of the host's default connection. -- `vsr-bandwidth` is currently supported for `vlt` registry benchmarks only. -- This profile rewrites `registry.vlt.io` to a local toxiproxy listener, then proxies TLS traffic to the real VSR upstream with symmetric bandwidth limits. -- Use `--registries=vlt` together with `--network-profile=vsr-bandwidth`. +- `registry-bandwidth` proxies each included registry through a dedicated local toxiproxy listener. +- This rewrites registry hosts to local listeners, then proxies TLS traffic to the real upstream registries with symmetric bandwidth limits. +- Use `--network-profile=none` to bypass the proxy path. - `--network-rate-kbps` controls both upstream and downstream bandwidth in KB/s. - The helper temporarily edits `/etc/hosts`, so local runs require `sudo` access. @@ -169,7 +169,7 @@ This suite also tests the performance of basic script execution (ex. `npm run fo ./bench run --fixtures=svelte --variation=lockfile,registry-lockfile,run --registries=npm,vlt # Run an isolated VSR benchmark with a bandwidth cap - VLT_REGISTRY_AUTH_TOKEN= ./bench run --variation=registry-clean --fixtures=next --registries=vlt --network-profile=vsr-bandwidth --network-rate-kbps=8192 + VLT_REGISTRY_AUTH_TOKEN= ./bench run --variation=registry-clean --fixtures=next --registries=vlt --network-profile=registry-bandwidth --network-rate-kbps=8192 # Script-execution benchmark ./bench run --variation=run --pms=vlt diff --git a/bench b/bench index e69b4605e7..c01e60d24b 100755 --- a/bench +++ b/bench @@ -66,7 +66,7 @@ Options: --runs Hyperfine runs (default: scripts/variations/common.sh default) --warmup Hyperfine warmup runs (default: scripts/variations/common.sh default) --timeout Per-command timeout in seconds (default: 300) - --network-profile Optional network isolation profile for registry-* runs + --network-profile Network isolation profile for registry-* runs (default: registry-bandwidth, use none to disable) --network-rate-kbps Bandwidth cap in KB/s for supported profiles (default: 8192) --chart Generate chart data and copy to app/latest --process Process results after run (clean + dated/latest outputs + chart data) @@ -84,7 +84,7 @@ Examples: ./bench run --fixtures=next --chart --process --runs=3 ./bench run --variation=registry-clean --fixtures=next ./bench run --variation=registry-lockfile --registries=npm,vlt - ./bench run --variation=registry-clean --fixtures=next --registries=vlt --network-profile=vsr-bandwidth --network-rate-kbps=8192 + ./bench run --variation=registry-clean --fixtures=next --registries=vlt --network-profile=registry-bandwidth --network-rate-kbps=8192 CODEARTIFACT_AUTH_TOKEN= ./bench run --variation=registry-clean --fixtures=next --registries=aws GH_REGISTRY= GH_AUTH_TOKEN= ./bench run --variation=registry-clean --fixtures=next --registries=github ./bench chart --fixtures=next --variation=clean @@ -443,7 +443,7 @@ run_bench() { if [[ -n "$network_profile_input" ]]; then echo " network profile: $network_profile_input" else - echo " network profile: disabled" + echo " network profile: registry script default" fi if [[ -n "$network_rate_input" ]]; then echo " network rate: ${network_rate_input} KB/s" diff --git a/scripts/network-isolation.sh b/scripts/network-isolation.sh index 25ba4a6739..961f812b9e 100644 --- a/scripts/network-isolation.sh +++ b/scripts/network-isolation.sh @@ -7,11 +7,11 @@ BENCH_TOXIPROXY_API_PORT="${BENCH_TOXIPROXY_API_PORT:-8474}" BENCH_TOXIPROXY_LISTEN_HOST="${BENCH_TOXIPROXY_LISTEN_HOST:-127.0.0.1}" BENCH_TOXIPROXY_LISTEN_PORT="${BENCH_TOXIPROXY_LISTEN_PORT:-7443}" BENCH_TOXIPROXY_RATE_KBPS="${BENCH_TOXIPROXY_RATE_KBPS:-8192}" -BENCH_TOXIPROXY_PROXY_NAME="${BENCH_TOXIPROXY_PROXY_NAME:-bench-vsr}" BENCH_TOXIPROXY_PID_FILE="${BENCH_TOXIPROXY_PID_FILE:-/tmp/vlt-benchmarks-toxiproxy.pid}" BENCH_TOXIPROXY_LOG_FILE="${BENCH_TOXIPROXY_LOG_FILE:-/tmp/vlt-benchmarks-toxiproxy.log}" BENCH_TOXIPROXY_HOSTS_START="# >>> vlt-benchmarks toxiproxy >>>" BENCH_TOXIPROXY_HOSTS_END="# <<< vlt-benchmarks toxiproxy <<<" +BENCH_TOXIPROXY_HOSTS_ENTRIES="${BENCH_TOXIPROXY_HOSTS_ENTRIES:-}" toxiproxy_api_url() { echo "http://${BENCH_TOXIPROXY_API_HOST}:${BENCH_TOXIPROXY_API_PORT}" @@ -72,10 +72,8 @@ reset_toxiproxy() { curl -fsS -X POST "$(toxiproxy_api_url)/reset" >/dev/null } -update_hosts_mapping() { - local host="$1" - local ip="$2" - local target="${ip} ${host}" +write_hosts_mapping() { + local entries="$1" sudo node -e ' const fs = require("node:fs") @@ -83,77 +81,101 @@ update_hosts_mapping() { const filePath = "/etc/hosts" const start = process.argv[1] const end = process.argv[2] - const entry = process.argv[3] + const entries = process.argv[3] const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") let content = fs.readFileSync(filePath, "utf8") - const block = `${start}\n${entry}\n${end}` const pattern = new RegExp(`${escapeRegex(start)}\\n?[\\s\\S]*?${escapeRegex(end)}\\n?`, "g") content = content.replace(pattern, "") content = content.replace(/\n*$/, "\n") - fs.writeFileSync(filePath, `${content}${block}\n`) - ' "$BENCH_TOXIPROXY_HOSTS_START" "$BENCH_TOXIPROXY_HOSTS_END" "$target" + if (entries.length > 0) { + const block = `${start}\n${entries}\n${end}` + content = `${content}${block}\n` + } + fs.writeFileSync(filePath, content) + ' "$BENCH_TOXIPROXY_HOSTS_START" "$BENCH_TOXIPROXY_HOSTS_END" "$entries" } -clear_hosts_mapping() { - sudo node -e ' - const fs = require("node:fs") - - const filePath = "/etc/hosts" - const start = process.argv[1] - const end = process.argv[2] - const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") +append_hosts_mapping() { + local host="$1" + local entry="${BENCH_TOXIPROXY_LISTEN_HOST} ${host}" + + if ! printf '%s\n' "$BENCH_TOXIPROXY_HOSTS_ENTRIES" | grep -Fxq "$entry"; then + if [ -n "$BENCH_TOXIPROXY_HOSTS_ENTRIES" ]; then + BENCH_TOXIPROXY_HOSTS_ENTRIES="${BENCH_TOXIPROXY_HOSTS_ENTRIES} +${entry}" + else + BENCH_TOXIPROXY_HOSTS_ENTRIES="$entry" + fi + fi - let content = fs.readFileSync(filePath, "utf8") - const pattern = new RegExp(`${escapeRegex(start)}\\n?[\\s\\S]*?${escapeRegex(end)}\\n?`, "g") - content = content.replace(pattern, "") - fs.writeFileSync(filePath, content.replace(/\n*$/, "\n")) - ' "$BENCH_TOXIPROXY_HOSTS_START" "$BENCH_TOXIPROXY_HOSTS_END" + write_hosts_mapping "$BENCH_TOXIPROXY_HOSTS_ENTRIES" } create_toxiproxy_proxy() { - local upstream_host="$1" - local upstream_ip="$2" - local upstream_port="$3" - local rate_kbps="$4" + local proxy_name="$1" + local upstream_host="$2" + local upstream_ip="$3" + local upstream_port="$4" + local listen_port="$5" + local rate_kbps="$6" curl -fsS -X POST "$(toxiproxy_api_url)/proxies" \ -H 'Content-Type: application/json' \ - -d "{\"name\":\"${BENCH_TOXIPROXY_PROXY_NAME}\",\"listen\":\"${BENCH_TOXIPROXY_LISTEN_HOST}:${BENCH_TOXIPROXY_LISTEN_PORT}\",\"upstream\":\"${upstream_ip}:${upstream_port}\"}" \ + -d "{\"name\":\"${proxy_name}\",\"listen\":\"${BENCH_TOXIPROXY_LISTEN_HOST}:${listen_port}\",\"upstream\":\"${upstream_ip}:${upstream_port}\"}" \ >/dev/null - curl -fsS -X POST "$(toxiproxy_api_url)/proxies/${BENCH_TOXIPROXY_PROXY_NAME}/toxics" \ + curl -fsS -X POST "$(toxiproxy_api_url)/proxies/${proxy_name}/toxics" \ -H 'Content-Type: application/json' \ -d "{\"name\":\"bandwidth_upstream\",\"type\":\"bandwidth\",\"stream\":\"upstream\",\"attributes\":{\"rate\":${rate_kbps}}}" \ >/dev/null - curl -fsS -X POST "$(toxiproxy_api_url)/proxies/${BENCH_TOXIPROXY_PROXY_NAME}/toxics" \ + curl -fsS -X POST "$(toxiproxy_api_url)/proxies/${proxy_name}/toxics" \ -H 'Content-Type: application/json' \ -d "{\"name\":\"bandwidth_downstream\",\"type\":\"bandwidth\",\"stream\":\"downstream\",\"attributes\":{\"rate\":${rate_kbps}}}" \ >/dev/null - echo "Toxiproxy configured for ${upstream_host} at ${BENCH_TOXIPROXY_LISTEN_HOST}:${BENCH_TOXIPROXY_LISTEN_PORT} (${rate_kbps} KB/s)" + echo "Toxiproxy configured for ${upstream_host} at ${BENCH_TOXIPROXY_LISTEN_HOST}:${listen_port} (${rate_kbps} KB/s)" >&2 } -setup_vsr_bandwidth_proxy() { - local upstream_host="registry.vlt.io" - local upstream_path="/npm/" - local upstream_port="443" +url_field() { + local url="$1" + local field="$2" + + node -e ' + const parsed = new URL(process.argv[1]) + const field = process.argv[2] + const value = field === "port" + ? (parsed.port || (parsed.protocol === "https:" ? "443" : "80")) + : parsed[field] + process.stdout.write(value) + ' "$url" "$field" +} + +setup_registry_bandwidth_proxy() { + local proxy_name="$1" + local registry_url="$2" + local listen_port="$3" + local upstream_host + local upstream_path + local upstream_protocol + local upstream_port local upstream_ip - ensure_toxiproxy_installed + upstream_host="$(url_field "$registry_url" hostname)" + upstream_path="$(url_field "$registry_url" pathname)" + upstream_protocol="$(url_field "$registry_url" protocol)" + upstream_port="$(url_field "$registry_url" port)" upstream_ip="$(resolve_ipv4 "$upstream_host")" - start_toxiproxy_server - reset_toxiproxy - create_toxiproxy_proxy "$upstream_host" "$upstream_ip" "$upstream_port" "$BENCH_TOXIPROXY_RATE_KBPS" - update_hosts_mapping "$upstream_host" "$BENCH_TOXIPROXY_LISTEN_HOST" - BENCH_VSR_PROXY_URL="https://${upstream_host}:${BENCH_TOXIPROXY_LISTEN_PORT}${upstream_path}" - export BENCH_VSR_PROXY_URL + create_toxiproxy_proxy "$proxy_name" "$upstream_host" "$upstream_ip" "$upstream_port" "$listen_port" "$BENCH_TOXIPROXY_RATE_KBPS" + append_hosts_mapping "$upstream_host" + + echo "${upstream_protocol}//${upstream_host}:${listen_port}${upstream_path}" } cleanup_network_isolation() { - clear_hosts_mapping || true + write_hosts_mapping "" || true if curl -fsS "$(toxiproxy_api_url)/version" >/dev/null 2>&1; then curl -fsS -X POST "$(toxiproxy_api_url)/reset" >/dev/null || true diff --git a/scripts/registry/common.sh b/scripts/registry/common.sh index 38457acb85..48d74ca230 100644 --- a/scripts/registry/common.sh +++ b/scripts/registry/common.sh @@ -46,7 +46,7 @@ BENCH_RUNS="${BENCH_RUNS:=5}" # Per-command timeout in seconds (default: 5 minutes) BENCH_TIMEOUT="${BENCH_TIMEOUT:=300}" BENCH_LOGLEVEL="${BENCH_LOGLEVEL:=http}" -BENCH_NETWORK_PROFILE="${BENCH_NETWORK_PROFILE:=}" +BENCH_NETWORK_PROFILE="${BENCH_NETWORK_PROFILE:=registry-bandwidth}" BENCH_TOXIPROXY_RATE_KBPS="${BENCH_TOXIPROXY_RATE_KBPS:=8192}" BENCH_OUTPUT_FOLDER="$BENCH_RESULTS/$BENCH_FIXTURE/$BENCH_VARIATION" @@ -66,28 +66,18 @@ BENCH_REGISTRY_NPM_URL="https://registry.npmjs.org/" BENCH_REGISTRY_VLT_URL="https://registry.vlt.io/vlt/npm/" BENCH_REGISTRY_VLT_URL_NPMRC_KEY="${BENCH_REGISTRY_VLT_URL#http*://}" BENCH_REGISTRY_AWS_URL="https://vlt-451504312483.d.codeartifact.us-east-1.amazonaws.com/npm/code-artifact-benchmark-test/" -BENCH_REGISTRY_AWS_NPMRC_KEY="${BENCH_REGISTRY_AWS_URL#http*://}" # GitHub registry URL is injected without the protocol prefix # (e.g. "//npm.pkg.github.com/..."), so we prepend "https:" for the registry config. # The auth .npmrc key uses the URL as-is (without protocol). BENCH_REGISTRY_GITHUB_URL="https:${GH_REGISTRY:-}" -BENCH_REGISTRY_GITHUB_NPMRC_KEY="${GH_REGISTRY#//}" +BENCH_REGISTRY_NPM_EFFECTIVE_URL="$BENCH_REGISTRY_NPM_URL" BENCH_REGISTRY_VLT_EFFECTIVE_URL="$BENCH_REGISTRY_VLT_URL" +BENCH_REGISTRY_AWS_EFFECTIVE_URL="$BENCH_REGISTRY_AWS_URL" +BENCH_REGISTRY_GITHUB_EFFECTIVE_URL="$BENCH_REGISTRY_GITHUB_URL" BENCH_REGISTRY_VLT_EFFECTIVE_NPMRC_KEY="${BENCH_REGISTRY_VLT_EFFECTIVE_URL#http*://}" - -# Registry setup commands run in hyperfine --prepare (untimed, before each run). -# Auth token is written as a literal placeholder so npm resolves it from env. -# For vlt registry, a random suffix is appended to the auth token on every -# iteration (separated by `:`) so that each run uses a unique token string. -# This ensures the registry does not serve cached responses across iterations. -BENCH_SETUP_REGISTRY_NPM="npm config set registry \"$BENCH_REGISTRY_NPM_URL\" --location=project" -# The vlt registry auth token is appended with a random suffix with the iteration number to -# ensure that a fresh cache is used for each iteration. A future update to the vlt registry -# will allow sharing a public cache regardless of the authorization header. -BENCH_SETUP_REGISTRY_VLT="npm config set registry \"$BENCH_REGISTRY_VLT_EFFECTIVE_URL\" --location=project && npm config set \"//${BENCH_REGISTRY_VLT_EFFECTIVE_NPMRC_KEY}:_authToken=\\\${VLT_REGISTRY_AUTH_TOKEN}:$(head -c 16 /dev/urandom | xxd -p)_\\\${HYPERFINE_ITERATION}\" --location=project" -BENCH_SETUP_REGISTRY_AWS="npm config set registry \"$BENCH_REGISTRY_AWS_URL\" --location=project && npm config set \"//${BENCH_REGISTRY_AWS_NPMRC_KEY}:_authToken=\\\${CODEARTIFACT_AUTH_TOKEN}\" --location=project" -BENCH_SETUP_REGISTRY_GITHUB="npm config set registry \"$BENCH_REGISTRY_GITHUB_URL\" --location=project && npm config set \"//${BENCH_REGISTRY_GITHUB_NPMRC_KEY}:_authToken=\\\${GH_AUTH_TOKEN}\" --location=project" +BENCH_REGISTRY_AWS_EFFECTIVE_NPMRC_KEY="${BENCH_REGISTRY_AWS_EFFECTIVE_URL#http*://}" +BENCH_REGISTRY_GITHUB_EFFECTIVE_NPMRC_KEY="${BENCH_REGISTRY_GITHUB_EFFECTIVE_URL#http*://}" # Registry verification helper runs in hyperfine --conclude (untimed, after each run). BENCH_VERIFY_REGISTRY="npm config get registry && ((grep -m3 '\"resolved\"' package-lock.json 2>/dev/null | sed 's/^[[:space:]]*//') || echo 'no lockfile yet') && echo ''" @@ -159,18 +149,29 @@ fi if [ -n "$BENCH_NETWORK_PROFILE" ]; then case "$BENCH_NETWORK_PROFILE" in - vsr-bandwidth) - if [ -z "$BENCH_INCLUDE_REG_VLT" ] || [ -n "$BENCH_INCLUDE_REG_NPM" ] || [ -n "$BENCH_INCLUDE_REG_AWS" ] || [ -n "$BENCH_INCLUDE_REG_GITHUB" ]; then - echo "Error: BENCH_NETWORK_PROFILE=vsr-bandwidth currently requires BENCH_INCLUDE_REGISTRY=vlt" - exit 1 - fi - + registry-bandwidth) source "$BENCH_SCRIPTS/network-isolation.sh" - setup_vsr_bandwidth_proxy + ensure_toxiproxy_installed + start_toxiproxy_server + reset_toxiproxy trap cleanup_network_isolation EXIT - BENCH_REGISTRY_VLT_EFFECTIVE_URL="$BENCH_VSR_PROXY_URL" + if [ -n "$BENCH_INCLUDE_REG_NPM" ]; then + BENCH_REGISTRY_NPM_EFFECTIVE_URL="$(setup_registry_bandwidth_proxy bench-npm "$BENCH_REGISTRY_NPM_URL" 7441)" + fi + if [ -n "$BENCH_INCLUDE_REG_VLT" ]; then + BENCH_REGISTRY_VLT_EFFECTIVE_URL="$(setup_registry_bandwidth_proxy bench-vlt "$BENCH_REGISTRY_VLT_URL" 7443)" + fi + if [ -n "$BENCH_INCLUDE_REG_AWS" ]; then + BENCH_REGISTRY_AWS_EFFECTIVE_URL="$(setup_registry_bandwidth_proxy bench-aws "$BENCH_REGISTRY_AWS_URL" 7444)" + fi + if [ -n "$BENCH_INCLUDE_REG_GITHUB" ]; then + BENCH_REGISTRY_GITHUB_EFFECTIVE_URL="$(setup_registry_bandwidth_proxy bench-github "$BENCH_REGISTRY_GITHUB_URL" 7445)" + fi + BENCH_REGISTRY_AWS_EFFECTIVE_NPMRC_KEY="${BENCH_REGISTRY_AWS_EFFECTIVE_URL#http*://}" + BENCH_REGISTRY_GITHUB_EFFECTIVE_NPMRC_KEY="${BENCH_REGISTRY_GITHUB_EFFECTIVE_URL#http*://}" BENCH_REGISTRY_VLT_EFFECTIVE_NPMRC_KEY="${BENCH_REGISTRY_VLT_EFFECTIVE_URL#http*://}" - BENCH_SETUP_REGISTRY_VLT="npm config set registry \"$BENCH_REGISTRY_VLT_EFFECTIVE_URL\" --location=project && npm config set \"//${BENCH_REGISTRY_VLT_EFFECTIVE_NPMRC_KEY}:_authToken=\\\${VLT_REGISTRY_AUTH_TOKEN}:$(head -c 16 /dev/urandom | xxd -p)_\\\${HYPERFINE_ITERATION}\" --location=project" + ;; + none) ;; *) echo "Error: Unknown BENCH_NETWORK_PROFILE '$BENCH_NETWORK_PROFILE'" @@ -180,10 +181,23 @@ if [ -n "$BENCH_NETWORK_PROFILE" ]; then fi echo "Registry benchmarks will run: $BENCH_INCLUDE_REGISTRY" -if [ -n "$BENCH_NETWORK_PROFILE" ]; then +if [ -n "$BENCH_NETWORK_PROFILE" ] && [ "$BENCH_NETWORK_PROFILE" != "none" ]; then echo "Network isolation profile: $BENCH_NETWORK_PROFILE (${BENCH_TOXIPROXY_RATE_KBPS} KB/s)" fi +# Registry setup commands run in hyperfine --prepare (untimed, before each run). +# Auth token is written as a literal placeholder so npm resolves it from env. +# For vlt registry, a random suffix is appended to the auth token on every +# iteration (separated by `:`) so that each run uses a unique token string. +# This ensures the registry does not serve cached responses across iterations. +BENCH_SETUP_REGISTRY_NPM="npm config set registry \"$BENCH_REGISTRY_NPM_EFFECTIVE_URL\" --location=project" +# The vlt registry auth token is appended with a random suffix with the iteration number to +# ensure that a fresh cache is used for each iteration. A future update to the vlt registry +# will allow sharing a public cache regardless of the authorization header. +BENCH_SETUP_REGISTRY_VLT="npm config set registry \"$BENCH_REGISTRY_VLT_EFFECTIVE_URL\" --location=project && npm config set \"//${BENCH_REGISTRY_VLT_EFFECTIVE_NPMRC_KEY}:_authToken=\\\${VLT_REGISTRY_AUTH_TOKEN}:$(head -c 16 /dev/urandom | xxd -p)_\\\${HYPERFINE_ITERATION}\" --location=project" +BENCH_SETUP_REGISTRY_AWS="npm config set registry \"$BENCH_REGISTRY_AWS_EFFECTIVE_URL\" --location=project && npm config set \"//${BENCH_REGISTRY_AWS_EFFECTIVE_NPMRC_KEY}:_authToken=\\\${CODEARTIFACT_AUTH_TOKEN}\" --location=project" +BENCH_SETUP_REGISTRY_GITHUB="npm config set registry \"$BENCH_REGISTRY_GITHUB_EFFECTIVE_URL\" --location=project && npm config set \"//${BENCH_REGISTRY_GITHUB_EFFECTIVE_NPMRC_KEY}:_authToken=\\\${GH_AUTH_TOKEN}\" --location=project" + # Clean up & create the results directory rm -rf "$BENCH_OUTPUT_FOLDER" mkdir -p "$BENCH_OUTPUT_FOLDER" From 1b9128dc88451d3f74628e0916dfc73309fb26da Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Thu, 30 Apr 2026 09:12:50 -0500 Subject: [PATCH 3/4] fix: force aube metadata primer in benchmarks --- scripts/variations/common.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/variations/common.sh b/scripts/variations/common.sh index f74ffbc3f4..0c37b22052 100644 --- a/scripts/variations/common.sh +++ b/scripts/variations/common.sh @@ -103,7 +103,7 @@ BENCH_INSTALL_PACQUET="pacquet install" BENCH_INSTALL_VLT="vlt install --view=silent" BENCH_INSTALL_BUN="bun install --ignore-scripts --silent" BENCH_INSTALL_DENO="deno install --quiet" -BENCH_INSTALL_AUBE="aube install --silent" +BENCH_INSTALL_AUBE="env AUBE_FORCE_METADATA_PRIMER=1 aube install --silent" BENCH_COMMAND_NPM="timeout $BENCH_TIMEOUT $BENCH_INSTALL_NPM >> $BENCH_OUTPUT_FOLDER/npm-output-\${HYPERFINE_ITERATION}.log 2>&1" BENCH_COMMAND_YARN="timeout $BENCH_TIMEOUT $BENCH_INSTALL_YARN > $BENCH_OUTPUT_FOLDER/yarn-output-\${HYPERFINE_ITERATION}.log 2>&1" From 57db93febde77396efac8eb8b03c75d9fd6f089f Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Thu, 30 Apr 2026 11:30:51 -0500 Subject: [PATCH 4/4] fix: add latency to registry network profile --- .github/workflows/benchmark.yaml | 4 ++++ README.md | 7 ++++--- bench | 28 +++++++++++++++++++++++++--- scripts/network-isolation.sh | 11 +++++++++-- scripts/registry/common.sh | 2 +- 5 files changed, 43 insertions(+), 9 deletions(-) diff --git a/.github/workflows/benchmark.yaml b/.github/workflows/benchmark.yaml index f4e213e4af..79e8d678bc 100644 --- a/.github/workflows/benchmark.yaml +++ b/.github/workflows/benchmark.yaml @@ -24,6 +24,9 @@ on: network_rate_kbps: description: "Bandwidth cap in KB/s for supported network profiles" default: "8192" + network_latency_ms: + description: "Fixed latency in milliseconds for supported network profiles" + default: "50" schedule: # GitHub Actions cron uses UTC. 10:17 UTC ~= 2:17 AM Pacific (PST). - cron: "17 10 * * *" @@ -112,6 +115,7 @@ jobs: BENCH_RUNS: ${{ (github.event_name == 'push' && github.ref != 'refs/heads/main') && '1' || (inputs.runs || '5') }} BENCH_NETWORK_PROFILE: ${{ inputs.network_profile || 'registry-bandwidth' }} BENCH_TOXIPROXY_RATE_KBPS: ${{ inputs.network_rate_kbps || '8192' }} + BENCH_TOXIPROXY_LATENCY_MS: ${{ inputs.network_latency_ms || '50' }} steps: - uses: actions/checkout@v6 - name: Install Node diff --git a/README.md b/README.md index 444cd33893..6130db6bd6 100644 --- a/README.md +++ b/README.md @@ -91,8 +91,8 @@ Examples: # Run only aws registry benchmarks (requires token) CODEARTIFACT_AUTH_TOKEN= ./bench run --variation=registry-clean --fixtures=next --registries=aws -# Run isolated VSR registry benchmarks through toxiproxy with a bandwidth cap -VLT_REGISTRY_AUTH_TOKEN= ./bench run --variation=registry-clean --fixtures=next --registries=vlt --network-profile=registry-bandwidth --network-rate-kbps=8192 +# Run isolated VSR registry benchmarks through toxiproxy with a bandwidth cap and fixed latency +VLT_REGISTRY_AUTH_TOKEN= ./bench run --variation=registry-clean --fixtures=next --registries=vlt --network-profile=registry-bandwidth --network-rate-kbps=8192 --network-latency-ms=50 ``` Auth notes: @@ -104,9 +104,10 @@ Auth notes: Registry benchmarks default to a local `toxiproxy` path so benchmark traffic uses a controlled network path instead of the host's default connection. - `registry-bandwidth` proxies each included registry through a dedicated local toxiproxy listener. -- This rewrites registry hosts to local listeners, then proxies TLS traffic to the real upstream registries with symmetric bandwidth limits. +- This rewrites registry hosts to local listeners, then proxies TLS traffic to the real upstream registries with symmetric bandwidth limits and fixed downstream latency. - Use `--network-profile=none` to bypass the proxy path. - `--network-rate-kbps` controls both upstream and downstream bandwidth in KB/s. +- `--network-latency-ms` controls fixed downstream latency in milliseconds. - The helper temporarily edits `/etc/hosts`, so local runs require `sudo` access. ## Testing Script Execution diff --git a/bench b/bench index c01e60d24b..3f4eac4f7f 100755 --- a/bench +++ b/bench @@ -53,7 +53,7 @@ AVAILABLE_REGISTRIES=( usage() { cat <<'EOF' Usage: - ./bench run [--fixtures=] [--pms=] [--variation=] [--runs=] [--warmup=] [--chart] [--process] [--date=] [--clean] [--network-profile=] [--network-rate-kbps=] + ./bench run [--fixtures=] [--pms=] [--variation=] [--runs=] [--warmup=] [--chart] [--process] [--date=] [--clean] [--network-profile=] [--network-rate-kbps=] [--network-latency-ms=] ./bench chart [--fixtures=] [--variation=] [--date=] [--clean] ./bench process [--dry-run] ./bench list @@ -68,6 +68,7 @@ Options: --timeout Per-command timeout in seconds (default: 300) --network-profile Network isolation profile for registry-* runs (default: registry-bandwidth, use none to disable) --network-rate-kbps Bandwidth cap in KB/s for supported profiles (default: 8192) + --network-latency-ms Fixed downstream latency for supported profiles (default: 50) --chart Generate chart data and copy to app/latest --process Process results after run (clean + dated/latest outputs + chart data) --date Date folder for chart output (default: today) @@ -84,7 +85,7 @@ Examples: ./bench run --fixtures=next --chart --process --runs=3 ./bench run --variation=registry-clean --fixtures=next ./bench run --variation=registry-lockfile --registries=npm,vlt - ./bench run --variation=registry-clean --fixtures=next --registries=vlt --network-profile=registry-bandwidth --network-rate-kbps=8192 + ./bench run --variation=registry-clean --fixtures=next --registries=vlt --network-profile=registry-bandwidth --network-rate-kbps=8192 --network-latency-ms=50 CODEARTIFACT_AUTH_TOKEN= ./bench run --variation=registry-clean --fixtures=next --registries=aws GH_REGISTRY= GH_AUTH_TOKEN= ./bench run --variation=registry-clean --fixtures=next --registries=github ./bench chart --fixtures=next --variation=clean @@ -383,6 +384,7 @@ run_bench() { local timeout_input="${12:-}" local network_profile_input="${13:-}" local network_rate_input="${14:-}" + local network_latency_input="${15:-}" if [[ ! -f "$BENCH_SCRIPT" ]]; then echo "Error: Missing benchmark script at $BENCH_SCRIPT" @@ -407,6 +409,9 @@ run_bench() { if [[ -n "$network_rate_input" ]]; then validate_number "$network_rate_input" "--network-rate-kbps" fi + if [[ -n "$network_latency_input" ]]; then + validate_number "$network_latency_input" "--network-latency-ms" + fi local pms_env="" if [[ -n "$pms_input" ]]; then @@ -450,6 +455,11 @@ run_bench() { else echo " network rate: 8192 KB/s (default)" fi + if [[ -n "$network_latency_input" ]]; then + echo " network latency: ${network_latency_input}ms" + else + echo " network latency: 50ms (default)" + fi if [[ "$clean_results" -eq 1 ]]; then echo " clean: enabled" else @@ -512,6 +522,9 @@ run_bench() { if [[ -n "$network_rate_input" ]]; then env_args+=("BENCH_TOXIPROXY_RATE_KBPS=$network_rate_input") fi + if [[ -n "$network_latency_input" ]]; then + env_args+=("BENCH_TOXIPROXY_LATENCY_MS=$network_latency_input") + fi local cmd=(bash "$BENCH_SCRIPT" "$fixture" "$variation") @@ -671,6 +684,7 @@ case "$command" in CHART_DATE="" NETWORK_PROFILE_INPUT="" NETWORK_RATE_INPUT="" + NETWORK_LATENCY_INPUT="" while [[ $# -gt 0 ]]; do case "$1" in @@ -746,6 +760,14 @@ case "$command" in NETWORK_RATE_INPUT="${1#*=}" shift ;; + --network-latency-ms) + NETWORK_LATENCY_INPUT="${2:-}" + shift 2 + ;; + --network-latency-ms=*) + NETWORK_LATENCY_INPUT="${1#*=}" + shift + ;; --chart) CHART_RESULTS=1 shift @@ -786,7 +808,7 @@ case "$command" in esac done - run_bench "$FIXTURES_INPUT" "$PMS_INPUT" "$VARIATION_INPUT" "$RUNS_INPUT" "$WARMUP_INPUT" "$DRY_RUN" "$CLEAN_RESULTS" "$CHART_RESULTS" "$CHART_DATE" "$REGISTRIES_INPUT" "$PROCESS_RESULTS" "$TIMEOUT_INPUT" "$NETWORK_PROFILE_INPUT" "$NETWORK_RATE_INPUT" + run_bench "$FIXTURES_INPUT" "$PMS_INPUT" "$VARIATION_INPUT" "$RUNS_INPUT" "$WARMUP_INPUT" "$DRY_RUN" "$CLEAN_RESULTS" "$CHART_RESULTS" "$CHART_DATE" "$REGISTRIES_INPUT" "$PROCESS_RESULTS" "$TIMEOUT_INPUT" "$NETWORK_PROFILE_INPUT" "$NETWORK_RATE_INPUT" "$NETWORK_LATENCY_INPUT" ;; process) DRY_RUN=0 diff --git a/scripts/network-isolation.sh b/scripts/network-isolation.sh index 961f812b9e..71bfbceb7c 100644 --- a/scripts/network-isolation.sh +++ b/scripts/network-isolation.sh @@ -7,6 +7,7 @@ BENCH_TOXIPROXY_API_PORT="${BENCH_TOXIPROXY_API_PORT:-8474}" BENCH_TOXIPROXY_LISTEN_HOST="${BENCH_TOXIPROXY_LISTEN_HOST:-127.0.0.1}" BENCH_TOXIPROXY_LISTEN_PORT="${BENCH_TOXIPROXY_LISTEN_PORT:-7443}" BENCH_TOXIPROXY_RATE_KBPS="${BENCH_TOXIPROXY_RATE_KBPS:-8192}" +BENCH_TOXIPROXY_LATENCY_MS="${BENCH_TOXIPROXY_LATENCY_MS:-50}" BENCH_TOXIPROXY_PID_FILE="${BENCH_TOXIPROXY_PID_FILE:-/tmp/vlt-benchmarks-toxiproxy.pid}" BENCH_TOXIPROXY_LOG_FILE="${BENCH_TOXIPROXY_LOG_FILE:-/tmp/vlt-benchmarks-toxiproxy.log}" BENCH_TOXIPROXY_HOSTS_START="# >>> vlt-benchmarks toxiproxy >>>" @@ -119,6 +120,7 @@ create_toxiproxy_proxy() { local upstream_port="$4" local listen_port="$5" local rate_kbps="$6" + local latency_ms="$7" curl -fsS -X POST "$(toxiproxy_api_url)/proxies" \ -H 'Content-Type: application/json' \ @@ -135,7 +137,12 @@ create_toxiproxy_proxy() { -d "{\"name\":\"bandwidth_downstream\",\"type\":\"bandwidth\",\"stream\":\"downstream\",\"attributes\":{\"rate\":${rate_kbps}}}" \ >/dev/null - echo "Toxiproxy configured for ${upstream_host} at ${BENCH_TOXIPROXY_LISTEN_HOST}:${listen_port} (${rate_kbps} KB/s)" >&2 + curl -fsS -X POST "$(toxiproxy_api_url)/proxies/${proxy_name}/toxics" \ + -H 'Content-Type: application/json' \ + -d "{\"name\":\"latency_downstream\",\"type\":\"latency\",\"stream\":\"downstream\",\"attributes\":{\"latency\":${latency_ms},\"jitter\":0}}" \ + >/dev/null + + echo "Toxiproxy configured for ${upstream_host} at ${BENCH_TOXIPROXY_LISTEN_HOST}:${listen_port} (${rate_kbps} KB/s, ${latency_ms}ms latency)" >&2 } url_field() { @@ -168,7 +175,7 @@ setup_registry_bandwidth_proxy() { upstream_port="$(url_field "$registry_url" port)" upstream_ip="$(resolve_ipv4 "$upstream_host")" - create_toxiproxy_proxy "$proxy_name" "$upstream_host" "$upstream_ip" "$upstream_port" "$listen_port" "$BENCH_TOXIPROXY_RATE_KBPS" + create_toxiproxy_proxy "$proxy_name" "$upstream_host" "$upstream_ip" "$upstream_port" "$listen_port" "$BENCH_TOXIPROXY_RATE_KBPS" "$BENCH_TOXIPROXY_LATENCY_MS" append_hosts_mapping "$upstream_host" echo "${upstream_protocol}//${upstream_host}:${listen_port}${upstream_path}" diff --git a/scripts/registry/common.sh b/scripts/registry/common.sh index 48d74ca230..5f41bc9d78 100644 --- a/scripts/registry/common.sh +++ b/scripts/registry/common.sh @@ -182,7 +182,7 @@ fi echo "Registry benchmarks will run: $BENCH_INCLUDE_REGISTRY" if [ -n "$BENCH_NETWORK_PROFILE" ] && [ "$BENCH_NETWORK_PROFILE" != "none" ]; then - echo "Network isolation profile: $BENCH_NETWORK_PROFILE (${BENCH_TOXIPROXY_RATE_KBPS} KB/s)" + echo "Network isolation profile: $BENCH_NETWORK_PROFILE (${BENCH_TOXIPROXY_RATE_KBPS} KB/s, ${BENCH_TOXIPROXY_LATENCY_MS:-50}ms latency)" fi # Registry setup commands run in hyperfine --prepare (untimed, before each run).