"""Locust load profile for the Ping River Monitor API + dashboard. Two user types mirror real traffic: dashboard visitors (page + the API calls the page makes, polling like the auto-refresh does) and API consumers (direct endpoint hits, including heavy history queries). Full stress against a LOCAL instance (never full-stress production — it hosts live flood monitoring): # separate shell: python -m uvicorn src.web_api:app --port 8125 .venv/Scripts/python.exe -m locust -f scripts/locustfile.py \ --host http://localhost:8125 --headless \ --users 200 --spawn-rate 20 --run-time 2m \ --html load-report.html Interactive UI instead: drop --headless and open http://localhost:8089. """ import random from locust import FastHttpUser, between, task class DashboardVisitor(FastHttpUser): """A browser session: initial page load, then periodic refresh polling.""" weight = 3 wait_time = between(2, 6) def on_start(self): # What one real page load requests self.client.get("/") self.client.get("/stations") self.client.get("/measurements/latest?limit=500") self.client.get("/api/hii/waterlevel/latest") self.client.get("/api/hii/rainfall/latest") @task(4) def poll_latest(self): self.client.get("/measurements/latest?limit=500") @task(2) def poll_forecast(self): self.client.get("/forecast") @task(2) def poll_rain(self): self.client.get("/api/hii/rainfall/latest") @task(1) def view_history(self): station = random.choice(["P.1", "P.67", "P.103", "P.75", "P.20"]) hours = random.choice([24, 168, 720]) self.client.get( f"/measurements/history/{station}?hours={hours}", name="/measurements/history/[station]", ) @task(1) def stats(self): self.client.get("/api/stats") class ApiConsumer(FastHttpUser): """A script/integration hitting the JSON API directly, no think time.""" weight = 1 wait_time = between(0.1, 1) @task(3) def latest(self): self.client.get("/measurements/latest?limit=100") @task(3) def hii_feeds(self): self.client.get(random.choice( ["/api/hii/waterlevel/latest", "/api/hii/rainfall/latest"] ), name="/api/hii/[feed]/latest") @task(2) def forecast(self): self.client.get("/forecast") @task(2) def heavy_history(self): self.client.get( "/measurements/history/P.1?hours=8760", name="/measurements/history/P.1 [heavy]", ) @task(1) def health(self): self.client.get("/health")