whaleprep/scripts/bench.py
dev dc754fe996 Student sign-in UX, gzip, landing stats cache
Sign-in page rewritten for non-technical students: Google-first when
configured, plain-language email path with honest 30-day scope, recovery
note, placeholders, full-width CTA. deploy/termux-start.sh sources .env
for GOOGLE_* credentials; deploy/env.example documents the 5-minute
Cloud Console setup; .gitignore covers secrets, DBs, pid/log files.
bench scripts send a browser UA so Cloudflare bot checks stop 403ing
local load tests. Landing bank counts cached 60s (five aggregate
subselects no longer run per hit). gzip compression on all responses.
2026-10-12 23:33:33 +05:30

69 lines
No EOL
2 KiB
Python
Executable file

#!/usr/bin/env python3
"""Load check for the PYQ server.
scripts/bench.py <base-url> [requests] [concurrency]
Reports latency percentiles per route and sustained throughput under load.
"""
import http.cookiejar
import statistics
import sys
import time
import urllib.request
from concurrent.futures import ThreadPoolExecutor
BASE = sys.argv[1] if len(sys.argv) > 1 else "http://127.0.0.1:8171"
N = int(sys.argv[2]) if len(sys.argv) > 2 else 300
CONC = int(sys.argv[3]) if len(sys.argv) > 3 else 20
jar = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(jar))
opener.addheaders = [("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) WhalePrep-bench/1.0")]
def sign_in():
body = b"name=Bench&email=bench@example.com&next=/build"
req = urllib.request.Request(f"{BASE}/student", data=body, method="POST")
opener.open(req).read()
def timed(path):
t0 = time.perf_counter()
with opener.open(BASE + path) as r:
r.read()
return (time.perf_counter() - t0) * 1000
def percentile(values, p):
s = sorted(values)
return s[min(int(len(s) * p), len(s) - 1)]
def report(path, samples):
print(
f" {path:<22} n={len(samples):<5} "
f"p50={statistics.median(samples):6.2f}ms "
f"p95={percentile(samples, .95):6.2f}ms "
f"max={max(samples):6.2f}ms"
)
def load(path, concurrency):
t0 = time.perf_counter()
with ThreadPoolExecutor(max_workers=concurrency) as pool:
list(pool.map(lambda _: timed(path), range(N)))
return N / (time.perf_counter() - t0)
sign_in()
print(f"=== {BASE} | {N} requests | {CONC} parallel ===")
print("sequential latency")
for p in ["/", "/static/style.css", "/build", "/dashboard", "/history"]:
report(p, [timed(p) for _ in range(min(N, 50))])
print(f"throughput at {CONC} concurrent")
for p in ["/", "/build"]:
warm = [timed(p) for _ in range(CONC * 2)]
rps = load(p, CONC)
report(p, warm)
print(f" -> {p:<20} {rps:,.0f} req/s sustained")