perf: gzip responses; multi-worker serving with single collection leader
Documentation / Validate Documentation (push) Failing after 8s
Documentation / Build Sphinx Documentation (push) Successful in 15s
CI/CD Pipeline - Northern Thailand Ping River Monitor / Test Suite (3.11) (push) Failing after 23s
CI/CD Pipeline - Northern Thailand Ping River Monitor / Build Docker Image (push) Skipped
CI/CD Pipeline - Northern Thailand Ping River Monitor / Integration Test with Services (push) Skipped
CI/CD Pipeline - Northern Thailand Ping River Monitor / Deploy to Staging (push) Skipped
CI/CD Pipeline - Northern Thailand Ping River Monitor / Deploy to Production (push) Skipped
CI/CD Pipeline - Northern Thailand Ping River Monitor / Performance Test (push) Skipped
CI/CD Pipeline - Northern Thailand Ping River Monitor / Code Quality (push) Successful in 12s
Documentation / Generate API Documentation (push) Successful in 9s
Documentation / Documentation Summary (push) Successful in 2s
CI/CD Pipeline - Northern Thailand Ping River Monitor / Cleanup (push) Successful in 0s
Documentation / Validate Documentation (push) Failing after 8s
Documentation / Build Sphinx Documentation (push) Successful in 15s
CI/CD Pipeline - Northern Thailand Ping River Monitor / Test Suite (3.11) (push) Failing after 23s
CI/CD Pipeline - Northern Thailand Ping River Monitor / Build Docker Image (push) Skipped
CI/CD Pipeline - Northern Thailand Ping River Monitor / Integration Test with Services (push) Skipped
CI/CD Pipeline - Northern Thailand Ping River Monitor / Deploy to Staging (push) Skipped
CI/CD Pipeline - Northern Thailand Ping River Monitor / Deploy to Production (push) Skipped
CI/CD Pipeline - Northern Thailand Ping River Monitor / Performance Test (push) Skipped
CI/CD Pipeline - Northern Thailand Ping River Monitor / Code Quality (push) Successful in 12s
Documentation / Generate API Documentation (push) Successful in 9s
Documentation / Documentation Summary (push) Successful in 2s
CI/CD Pipeline - Northern Thailand Ping River Monitor / Cleanup (push) Successful in 0s
GZipMiddleware (min 500 bytes) compresses the dashboard HTML ~4x and station JSON up to ~100x, end-to-end through the Caddy TLS terminator — production load testing showed the deployment is bandwidth-bound once the response caches hit, so compression is the capacity lever. WEB_WORKERS (default 2) runs uvicorn multi-process via the app import string. Every worker executes the lifespan, so a localhost lock port (COLLECTION_LEADER_PORT, default 8901) elects exactly one background-collection leader per machine — RID/HII polling stays once-per-cycle instead of once-per-worker; the lock releases with the process. Locust clients now send Accept-Encoding so future runs measure compressed transfer, as browsers do.
This commit is contained in:
+18
-13
@@ -20,6 +20,9 @@ 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."""
|
||||
@@ -29,23 +32,23 @@ class DashboardVisitor(FastHttpUser):
|
||||
|
||||
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")
|
||||
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")
|
||||
self.client.get("/measurements/latest?limit=500", headers=GZIP)
|
||||
|
||||
@task(2)
|
||||
def poll_forecast(self):
|
||||
self.client.get("/forecast")
|
||||
self.client.get("/forecast", headers=GZIP)
|
||||
|
||||
@task(2)
|
||||
def poll_rain(self):
|
||||
self.client.get("/api/hii/rainfall/latest")
|
||||
self.client.get("/api/hii/rainfall/latest", headers=GZIP)
|
||||
|
||||
@task(1)
|
||||
def view_history(self):
|
||||
@@ -53,12 +56,13 @@ class DashboardVisitor(FastHttpUser):
|
||||
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")
|
||||
self.client.get("/api/stats", headers=GZIP)
|
||||
|
||||
|
||||
class ApiConsumer(FastHttpUser):
|
||||
@@ -69,25 +73,26 @@ class ApiConsumer(FastHttpUser):
|
||||
|
||||
@task(3)
|
||||
def latest(self):
|
||||
self.client.get("/measurements/latest?limit=100")
|
||||
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"]
|
||||
), name="/api/hii/[feed]/latest")
|
||||
), headers=GZIP, name="/api/hii/[feed]/latest")
|
||||
|
||||
@task(2)
|
||||
def forecast(self):
|
||||
self.client.get("/forecast")
|
||||
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")
|
||||
self.client.get("/health", headers=GZIP)
|
||||
|
||||
Reference in New Issue
Block a user