"""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 # Explicit so measurements reflect compressed transfer (browsers always send this) GZIP = {"Accept-Encoding": "gzip, deflate"} 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("/", headers=GZIP) self.client.get("/stations", headers=GZIP) self.client.get("/measurements/latest?limit=500", headers=GZIP) self.client.get("/api/hii/waterlevel/latest", headers=GZIP) self.client.get("/api/hii/rainfall/latest", headers=GZIP) @task(4) def poll_latest(self): self.client.get("/measurements/latest?limit=500", headers=GZIP) @task(2) def poll_forecast(self): self.client.get("/forecast", headers=GZIP) @task(2) def poll_rain(self): self.client.get("/api/hii/rainfall/latest", headers=GZIP) @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}", headers=GZIP, name="/measurements/history/[station]", ) @task(1) def stats(self): self.client.get("/api/stats", headers=GZIP) 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", headers=GZIP) @task(3) def hii_feeds(self): self.client.get(random.choice( ["/api/hii/waterlevel/latest", "/api/hii/rainfall/latest"] ), headers=GZIP, name="/api/hii/[feed]/latest") @task(2) def forecast(self): self.client.get("/forecast", headers=GZIP) @task(2) def heavy_history(self): self.client.get( "/measurements/history/P.1?hours=8760", headers=GZIP, name="/measurements/history/P.1 [heavy]", ) @task(1) def health(self): self.client.get("/health", headers=GZIP)