traceRAG-backend / scripts /stress_test.py
Karush2807
TraceRAG backend + demo graphs
db284b9
Raw
History Blame Contribute Delete
3.68 kB
"""Async load test for the TraceRAG FastAPI endpoint.
Start the API first (uvicorn api:app --port 8000), then:
python scripts/stress_test.py --requests 200 --concurrency 25
"""
from __future__ import annotations
import argparse
import asyncio
import time
from collections import Counter
import aiohttp
QUERIES = [
"What was incident INC-4471 about?",
"What is the responsibility of the PaymentService?",
"Who is the tech lead and owning team for PaymentService?",
"What does payment-service depend on?",
"How does the AuthLayer handle login and JWT issuance?",
"Explain the ShopFlow commerce platform architecture.",
"What is related to PR #847?",
"Which component was affected in incident INC-4480?",
]
async def _one(session, url, query, sem, timeout) -> dict:
async with sem:
start = time.perf_counter()
try:
async with session.post(url, json={"query": query}, timeout=timeout) as resp:
await resp.read() # drain
return {"status": resp.status, "latency": time.perf_counter() - start}
except asyncio.TimeoutError:
return {"status": "timeout", "latency": time.perf_counter() - start}
except aiohttp.ClientError as exc:
return {"status": f"error:{type(exc).__name__}", "latency": time.perf_counter() - start}
async def run(url: str, total: int, concurrency: int, timeout: float) -> None:
sem = asyncio.Semaphore(concurrency)
print(f"Hammering {url}\n {total} requests, concurrency={concurrency}, "
f"timeout={timeout}s\n")
wall_start = time.perf_counter()
async with aiohttp.ClientSession() as session:
tasks = [
_one(session, url, QUERIES[i % len(QUERIES)], sem, timeout)
for i in range(total)
]
results = await asyncio.gather(*tasks)
wall = time.perf_counter() - wall_start
statuses = Counter(r["status"] for r in results)
ok = [r["latency"] for r in results if r["status"] == 200]
lat = sorted(r["latency"] for r in results)
def pct(p: float) -> float:
return lat[min(len(lat) - 1, int(len(lat) * p))] if lat else 0.0
print("=" * 60)
print(f"{'Status / outcome':<28}{'count':>10}")
print("-" * 60)
for status, n in sorted(statuses.items(), key=lambda kv: str(kv[0])):
print(f"{str(status):<28}{n:>10}")
print("-" * 60)
print(f"wall time {wall:>8.2f}s")
print(f"throughput {total / wall:>8.1f} req/s")
print(f"success (200) {len(ok):>8} / {total}")
print(f"latency p50/p90/p99 {pct(.5):.3f} / {pct(.9):.3f} / {pct(.99):.3f} s")
print(f"latency max {(lat[-1] if lat else 0):.3f} s")
print("=" * 60)
if any(str(s).startswith(("error", "timeout")) or s in (500, 502) for s in statuses):
print("\n[!] Hard failures present (5xx/timeout/conn-error): the worker may "
"not be degrading gracefully — check for crashes or DB read-locks.")
if statuses.get(429) or statuses.get(503):
print("\n[ok] Server shed load with 429/503 under pressure (graceful).")
def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(description="TraceRAG async load test.")
p.add_argument("--url", default="http://127.0.0.1:8000/api/trace")
p.add_argument("--requests", type=int, default=100)
p.add_argument("--concurrency", type=int, default=20)
p.add_argument("--timeout", type=float, default=30.0)
args = p.parse_args(argv)
asyncio.run(run(args.url, args.requests, args.concurrency, args.timeout))
return 0
if __name__ == "__main__":
raise SystemExit(main())