""" stress_test.py — measure the ceilings that actually matter for this backend. The GPU is NOT this server's bottleneck: it runs on external ZeroGPU Spaces and is billed per-user, so you can't meaningfully stress it from one account. The two real questions are: 1. WRITE ceiling — how fast can we commit to the dataset before HF starts returning 429s? All discoveries funnel through one write token, so this is the central limit. (Probe against a THROWAWAY dataset.) 2. READ path — the frontend reads straight from the dataset CDN; confirm those /resolve/ URLs are fast and CORS-open (they shouldn't touch the server at all). An optional --live probe hits the real /scan endpoint concurrently; it consumes your GPU quota and creates real monsters, so it's off by default. Examples: # 1) Find the commit-rate ceiling on a scratch dataset you own: HF_TOKEN=hf_xxx python stress_test.py commits --repo /piclets-stress --n 60 # 2) Check the read/CDN path for your real dataset: python stress_test.py reads --repo Fraser/piclets --n 200 --concurrency 20 # 3) (optional) Live end-to-end concurrency against the deployed Space: python stress_test.py live --space /piclets-server --image test.jpg \ --token hf_xxx --n 5 --concurrency 3 """ import io import os import sys import time import json import argparse import statistics import concurrent.futures as cf import requests # -------------------------------------------------------------------------- # 1) WRITE ceiling: burst commits to a throwaway dataset, watch for 429s # -------------------------------------------------------------------------- def probe_commits(repo: str, n: int): from huggingface_hub import HfApi, CommitOperationAdd from huggingface_hub.utils import HfHubHTTPError token = os.getenv("HF_TOKEN") if not token: sys.exit("Set HF_TOKEN (write access to the scratch repo).") api = HfApi(token=token) api.create_repo(repo, repo_type="dataset", exist_ok=True, private=True) print(f"[commits] probing {repo} with {n} sequential commits " f"(huggingface_hub auto-retries 429; watch for slpowdowns)...\n") latencies, throttles, errors = [], 0, 0 t0 = time.time() for i in range(n): blob = io.BytesIO(json.dumps({"i": i, "t": time.time()}).encode()) op = CommitOperationAdd(path_in_repo=f"stress/{i}.json", path_or_fileobj=blob) start = time.time() try: api.create_commit(repo_id=repo, repo_type="dataset", operations=[op], commit_message=f"stress {i}") dt = time.time() - start latencies.append(dt) # A commit that took much longer than the median usually means the SDK # slept off a 429 for us. flag = " <- slow (likely throttled + retried)" if dt > 3 else "" print(f" commit {i:>3}: {dt:5.2f}s{flag}") except HfHubHTTPError as exc: code = getattr(getattr(exc, "response", None), "status_code", "?") if code == 429: throttles += 1 print(f" commit {i:>3}: 429 rate-limited") else: errors += 1 print(f" commit {i:>3}: HTTP {code}") except Exception as exc: errors += 1 print(f" commit {i:>3}: {type(exc).__name__}: {exc}") total = time.time() - t0 print("\n[commits] summary") print(f" committed: {len(latencies)}/{n}") print(f" 429s (uncaught): {throttles}") print(f" other errors: {errors}") if latencies: print(f" median latency: {statistics.median(latencies):.2f}s") print(f" p90 latency: {sorted(latencies)[int(len(latencies)*0.9)-1]:.2f}s") print(f" sustained rate: {len(latencies)/total:.2f} commits/s over {total:.1f}s") print("\n Interpretation: the rate where latency climbs / retries kick in is your\n" " practical write ceiling. Since one token serves ALL discoveries, that's the\n" " number that bounds global new-monster throughput. Remember to delete this\n" " scratch dataset afterwards.") # -------------------------------------------------------------------------- # 2) READ path: hammer a /resolve/ URL, check latency + CORS # -------------------------------------------------------------------------- def probe_reads(repo: str, n: int, concurrency: int): url = f"https://huggingface.co/datasets/{repo}/resolve/main/index/stats.json" print(f"[reads] GET {url}\n {n} requests, {concurrency} concurrent\n") def one(_): start = time.time() r = requests.get(url, timeout=30) return time.time() - start, r.status_code, r.headers.get("access-control-allow-origin") lat, codes, cors_seen = [], {}, set() with cf.ThreadPoolExecutor(max_workers=concurrency) as ex: for dt, code, cors in ex.map(one, range(n)): lat.append(dt) codes[code] = codes.get(code, 0) + 1 if cors: cors_seen.add(cors) print("[reads] summary") print(f" status codes: {codes}") print(f" median latency: {statistics.median(lat)*1000:.0f} ms") print(f" p90 latency: {sorted(lat)[int(len(lat)*0.9)-1]*1000:.0f} ms") print(f" CORS header seen: {cors_seen or 'NONE (check if the browser can read this cross-origin)'}") print("\n Interpretation: reads are per-client and CDN-served, so this should stay fast\n" " under load and never involve the server. A '*' or your origin in CORS means\n" " the frontend can fetch it directly.") # -------------------------------------------------------------------------- # 3) LIVE end-to-end (optional; spends GPU quota, creates real monsters) # -------------------------------------------------------------------------- def probe_live(space: str, image: str, token: str, n: int, concurrency: int): from gradio_client import Client, handle_file if not os.path.exists(image): sys.exit(f"Image not found: {image}") print(f"[live] WARNING: this spends your ZeroGPU quota and writes real monsters.\n" f" {n} scans, {concurrency} concurrent, against {space}\n") def one(_): start = time.time() try: client = Client(space, hf_token=token) res = client.predict(handle_file(image), token, api_name="/scan") return time.time() - start, (res.get("status") if isinstance(res, dict) else "?"), None except Exception as exc: return time.time() - start, "error", str(exc)[:120] lat, outcomes = [], {} with cf.ThreadPoolExecutor(max_workers=concurrency) as ex: for dt, status, err in ex.map(one, range(n)): lat.append(dt) outcomes[status] = outcomes.get(status, 0) + 1 if err: print(f" error: {err}") print("\n[live] summary") print(f" outcomes: {outcomes}") print(f" median latency: {statistics.median(lat):.1f}s") print(f" p90 latency: {sorted(lat)[int(len(lat)*0.9)-1]:.1f}s") def main(): p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) sub = p.add_subparsers(dest="cmd", required=True) c = sub.add_parser("commits", help="probe the dataset commit-rate ceiling (throwaway repo)") c.add_argument("--repo", required=True) c.add_argument("--n", type=int, default=60) r = sub.add_parser("reads", help="probe the CDN read path") r.add_argument("--repo", required=True) r.add_argument("--n", type=int, default=200) r.add_argument("--concurrency", type=int, default=20) l = sub.add_parser("live", help="optional live end-to-end scan probe") l.add_argument("--space", required=True) l.add_argument("--image", required=True) l.add_argument("--token", required=True) l.add_argument("--n", type=int, default=5) l.add_argument("--concurrency", type=int, default=3) args = p.parse_args() if args.cmd == "commits": probe_commits(args.repo, args.n) elif args.cmd == "reads": probe_reads(args.repo, args.n, args.concurrency) elif args.cmd == "live": probe_live(args.space, args.image, args.token, args.n, args.concurrency) if __name__ == "__main__": main()