AI_Chatbot / load_test.py
Hamza-Naimat's picture
feat: Enhanced Universal Digital FTE β€” Advanced Test Suite & Multi-Client Refinement. Added Jikan bulk ingestion, hardened server logic, and a comprehensive suite of load, stress, and behavioral tests. Updated UI for live monitoring and active database switching.
6c653ef
Raw
History Blame Contribute Delete
4.3 kB
"""
Concurrent load test for AI Chatbot server.
Tests server concurrency WITHOUT burning LLM quota (uses stream=False, short questions).
Usage:
python load_test.py [--url http://localhost:8000] [--concurrency 10] [--total 50]
Results: pass/fail per request, latency stats (p50/p95/p99), error breakdown.
"""
import asyncio
import argparse
import time
import statistics
from collections import defaultdict
import aiohttp
BASE_URL = "http://localhost:8000"
DB = "agentfactory" # change if testing different DB
QUESTIONS = [
"What is AgentFactory?",
"Who is Zia Khan?",
"What courses are available?",
"How do I enroll?",
"What is the price?",
"Who is Wania Kazmi?",
"Tell me about Panaversity",
"What is an AI agent?",
"How long are the courses?",
"Is there a certificate?",
]
results = []
errors = defaultdict(int)
async def single_request(session: aiohttp.ClientSession, question: str, req_id: int):
start = time.monotonic()
try:
payload = {"question": question, "history": [], "stream": False}
async with session.post(
f"{BASE_URL}/chat",
json=payload,
timeout=aiohttp.ClientTimeout(total=90),
) as resp:
latency = (time.monotonic() - start) * 1000
body = await resp.json(content_type=None)
answer = body.get("answer", "")
ok = resp.status == 200 and len(answer) > 5
status_label = "PASS" if ok else f"FAIL({resp.status})"
if not ok:
errors[f"HTTP {resp.status}"] += 1
print(f" [{req_id:03d}] {status_label:12s} {latency:6.0f}ms q={question[:40]!r}")
return {"ok": ok, "latency_ms": latency, "status": resp.status}
except asyncio.TimeoutError:
latency = (time.monotonic() - start) * 1000
errors["timeout"] += 1
print(f" [{req_id:03d}] TIMEOUT {latency:6.0f}ms q={question[:40]!r}")
return {"ok": False, "latency_ms": latency, "status": 0}
except Exception as exc:
latency = (time.monotonic() - start) * 1000
errors[type(exc).__name__] += 1
print(f" [{req_id:03d}] ERROR {latency:6.0f}ms {exc}")
return {"ok": False, "latency_ms": latency, "status": 0}
async def run_batch(session, batch, offset):
tasks = [
single_request(session, QUESTIONS[i % len(QUESTIONS)], offset + i)
for i in range(len(batch))
]
return await asyncio.gather(*tasks)
async def main(url: str, concurrency: int, total: int):
global BASE_URL
BASE_URL = url.rstrip("/")
print(f"\nLoad test: {total} requests, concurrency={concurrency}, target={BASE_URL}")
print(f"DB: {DB}\n{'─'*60}")
connector = aiohttp.TCPConnector(limit=concurrency + 5)
async with aiohttp.ClientSession(connector=connector) as session:
wall_start = time.monotonic()
all_results = []
for batch_start in range(0, total, concurrency):
batch_size = min(concurrency, total - batch_start)
batch = list(range(batch_size))
batch_results = await run_batch(session, batch, batch_start)
all_results.extend(batch_results)
wall_elapsed = time.monotonic() - wall_start
passed = sum(1 for r in all_results if r["ok"])
failed = total - passed
latencies = [r["latency_ms"] for r in all_results]
latencies_sorted = sorted(latencies)
def pct(p):
idx = int(len(latencies_sorted) * p / 100)
return latencies_sorted[min(idx, len(latencies_sorted) - 1)]
print(f"\n{'═'*60}")
print(f"RESULTS: {passed}/{total} PASS ({failed} FAIL)")
print(f"Wall time: {wall_elapsed:.1f}s | Throughput: {total/wall_elapsed:.1f} req/s")
print(f"Latency β€” p50:{pct(50):.0f}ms p95:{pct(95):.0f}ms p99:{pct(99):.0f}ms max:{max(latencies):.0f}ms")
if errors:
print(f"Errors: {dict(errors)}")
print(f"{'═'*60}\n")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--url", default="http://localhost:8000")
parser.add_argument("--concurrency", type=int, default=10)
parser.add_argument("--total", type=int, default=50)
args = parser.parse_args()
asyncio.run(main(args.url, args.concurrency, args.total))