diff --git a/analyze_spin_ab_urus.py b/analyze_spin_ab_urus.py index f01dfd5..b193d5d 100755 --- a/analyze_spin_ab_urus.py +++ b/analyze_spin_ab_urus.py @@ -18,7 +18,10 @@ import os import re import sys -GATED = {2, 4} # core counts where the RFC 004 spin policy is active +def gate_active(cores): + """RFC 004 spinner cap is non-zero only at 2..=4 schedulers (1 and >=5 + allow 0 spinners), so the policy is active exactly there.""" + return 2 <= cores <= 4 def parse_latency_ms(s): @@ -88,7 +91,7 @@ def main(): b = fnum(before.get(k, {}).get("sustained_rps")) a = fnum(after.get(k, {}).get("sustained_rps")) spd = f"{a / b:.2f}x" if (a and b) else "—" - gate = "ACTIVE" if cores in GATED else "inert" + gate = "ACTIVE" if gate_active(cores) else "inert" bs = f"{b:,.0f}" if b is not None else "—" as_ = f"{a:,.0f}" if a is not None else "—" print(f"{srv:<10} {scn:<4} {cores:>5} {bs:>12} {as_:>12} {spd:>8} {gate}") @@ -107,7 +110,7 @@ def main(): delta = f"{(a - b):+.2f}" else: delta = "—" - gate = "ACTIVE" if cores in GATED else "inert" + gate = "ACTIVE" if gate_active(cores) else "inert" bs = f"{b:.2f}" if b is not None else "—" as_ = f"{a:.2f}" if a is not None else "—" print(f"{srv:<10} {scn:<4} {cores:>5} {bs:>10} {as_:>10} {delta:>9} {gate}") @@ -126,8 +129,10 @@ def main(): print( "\nReading it: spin is 'worth it' if s3 (and/or s2) RPS rises and/or " - "tail latency drops at cores 2/4 (gate ACTIVE), with cores 1/8 (inert) " - "staying flat. Flat-or-worse at 2/4 = not worth it on real traffic." + "tail latency drops at the gate-ACTIVE cores (2-4), with the inert " + "controls (1 and >=5, e.g. 6) staying flat. Flat-or-worse at 2/4 = " + "not worth it on real traffic. Cross-check that the saturating control " + "(6 cores) is actually CPU-bound, not loadgen-bound, before trusting it." ) if "axum-mem" in {k[0] for k in keys}: print( diff --git a/spin_ab_urus.sh b/spin_ab_urus.sh index 4a8515c..61cdd74 100755 --- a/spin_ab_urus.sh +++ b/spin_ab_urus.sh @@ -20,10 +20,13 @@ # Scenarios (per RECON §6.4): s2 (router+auth, no store) and s3 (memory store, # the headline). Server is urus-mem for both. We do NOT touch s4/sqlite. # -# Core sweep (per RECON §6.3): the spin gate is active at 2-4 schedulers and -# inert at 1 and >=5, so 2 and 4 should show the effect and 1 and 8 bracket it -# as controls. Server is pinned to cores 0..N-1; the load generator gets its -# own disjoint cores (LOADGEN_CPUS). +# Core sweep: the spin gate is active at 2-4 schedulers and inert at 1 and +# >=5. We sweep 1/2/4/6 server cores: 2 and 4 are gate-ACTIVE, 1 and 6 are +# inert controls -- 6 also being a *saturating* control, since the old 8-core +# point couldn't be saturated by an 8-core loadgen (it plateaued ~102k rps, +# loadgen-bound). Server is pinned to cores 0..N-1; the load generator gets +# its own disjoint cores (LOADGEN_CPUS), sized ~>=2x the server cores per the +# saturation goal while leaving cores free for the OS. # # Usage: # ./spin_ab_urus.sh # full A/B, defaults @@ -37,8 +40,10 @@ # Env knobs (all overridable): # BEFORE / AFTER smarm commit-ishes (default b0c9685 / HEAD) # SCENARIOS space-separated (default "s2 s3") -# CORES server core counts to sweep (default "1 2 4 8") -# LOADGEN_CPUS loadgen taskset range (default "12-19") +# CORES server core counts to sweep (default "1 2 4 6") +# LOADGEN_CPUS loadgen taskset range (default "8-23" = 16 cores) +# WRK_THREADS loadgen wrk/wrk2 threads (default 16) +# WRK_CONNS loadgen open connections (default 512) # SERVER memory server backend (default urus-mem) # PROBE_SEC/WARMUP_SEC/MEASURE_SEC/SAT_RATIO/PREPOP/WRK_CONNS/WRK_THREADS # passed straight through to runner.sh @@ -74,9 +79,15 @@ AFTER_DEFAULT="$(git -C "$SMARM_DIR" rev-parse --short HEAD)" AFTER="${AFTER:-$AFTER_DEFAULT}" SCENARIOS="${SCENARIOS:-s2 s3}" -CORES="${CORES:-1 2 4 8}" +CORES="${CORES:-1 2 4 6}" SERVER="${SERVER:-urus-mem}" -LOADGEN_CPUS="${LOADGEN_CPUS:-12-19}" +LOADGEN_CPUS="${LOADGEN_CPUS:-8-23}" + +# Loadgen sizing: ~2x loadgen cores per server core. 16 cores covers the +# 6-core server at ~2.7x and the 4-core at 4x. Threads track the core count; +# connections bumped so the probe can actually find the server's peak. +export WRK_THREADS="${WRK_THREADS:-16}" +export WRK_CONNS="${WRK_CONNS:-512}" QUICK=0 DRY=0 @@ -111,7 +122,8 @@ echo ">>> smarm: $SMARM_DIR" echo ">>> BEFORE=$BEFORE AFTER=$AFTER" echo ">>> servers: ${SERVERS_TO_RUN[*]}" echo ">>> scenarios: $SCENARIOS" -echo ">>> cores: $CORES (loadgen on $LOADGEN_CPUS)" +echo ">>> cores: $CORES" +echo ">>> loadgen: cpus=$LOADGEN_CPUS threads=$WRK_THREADS conns=$WRK_CONNS" echo ">>> machine: $NPROC cores" echo ">>> outdir: $OUTDIR" [[ "$QUICK" == "1" ]] && echo ">>> --quick: probe/warmup/measure = ${PROBE_SEC}/${WARMUP_SEC}/${MEASURE_SEC}s"