rogerdemello's picture
Wave 2 infra: release pipeline, Postgres option, LLM metrics, load test
78d9fa0
|
Raw History Blame Contribute Delete
4.52 kB
# Load testing
A [Locust](https://locust.io/) load test for the Autonomous Executive Email
Copilot API. It exercises the hot read paths and a representative mutating flow
so you can measure latency and throughput before a deploy or a capacity change.
Locust is an **optional development dependency** and is deliberately *not* in
`requirements.txt` (it pulls in a web UI and async stack the runtime does not
need). Install it on demand.
## Install
```bash
pip install locust
```
## Run the server under test
In one terminal, start the API the usual way (see the README quickstart):
```bash
uvicorn env.api:app --host 0.0.0.0 --port 8000
```
The same app serves on container port `7860` in Docker; point `--host` at
whatever address/port the server is actually listening on.
## Run the load test
From the repository root:
```bash
# Interactive web UI on http://localhost:8089
locust -f scripts/loadtest/locustfile.py --host http://localhost:8000
```
Open <http://localhost:8089>, set the number of users and spawn rate, and start
the run. For an unattended (headless) run with a fixed shape and duration:
```bash
locust -f scripts/loadtest/locustfile.py --host http://localhost:8000 \
--headless --users 50 --spawn-rate 10 --run-time 2m
```
- `--users` β€” peak number of concurrent simulated clients.
- `--spawn-rate` β€” how many users to add per second until the peak is reached.
- `--run-time` β€” total duration (e.g. `30s`, `2m`, `1h`).
Against a non-local target, set `--host` accordingly, e.g.
`--host http://localhost:7860` (Docker) or a remote base URL.
## What it does
Each simulated user (`EmailCopilotUser`) waits 0.5–2.0s between tasks and picks
a task by weight:
| Task | Endpoint(s) | Weight | Notes |
|------|-------------|-------:|-------|
| `health` | `GET /health` | 10 | Cheapest path; mirrors the healthcheck/probes. |
| `tasks` | `GET /tasks` | 5 | Task metadata + JSON schemas. |
| `state` | `GET /state` | 5 | Current environment snapshot. |
| `metrics` | `GET /metrics` | 3 | Prometheus text exposition. |
| `reset_then_step` | `POST /reset`, `POST /step` | 2 | Representative mutating flow. |
On start, each user resets the environment so `/state` and `/step` have an
episode to act on. The mutating flow resets, reads the returned observation, and
classifies the first email (falling back to a `prioritize` action when no emails
remain). The read paths are weighted far above the writes because that matches
real traffic (probes, scrapers, dashboards) and keeps SQLite β€” the zero-config
default backend β€” from being the dominant factor in the numbers.
> Note: if the server has `API_AUTH_TOKEN` set, the mutating routes (`/reset`,
> `/step`) require a token. Either run the load test against a server with auth
> disabled, or extend the requests in `locustfile.py` to send an
> `Authorization: Bearer <token>` header. Likewise, leave
> `RATE_LIMIT_PER_MINUTE` at `0` (the default) so the limiter does not skew
> results.
## Reading the results
- **Web UI** β€” the *Statistics* tab shows, per named request, the request count,
failure count, median / 95th / 99th percentile latency, and current RPS. The
*Charts* tab plots RPS, response times, and user count over time. The
*Failures* tab lists any non-2xx/transport errors with a reason.
- **Headless** β€” Locust prints an aggregated table on exit. Add
`--csv results/loadtest` to also write
`results/loadtest_stats.csv`, `_stats_history.csv`, and `_failures.csv` for
later analysis or CI artifacts.
Focus on the **p95 / p99 latency** columns (tail latency, what users feel) and
the **failure rate**, not just the mean. A rising p99 while RPS is flat usually
means you have found a bottleneck (often the SQLite write path under the
`/reset` + `/step` flow).
## Suggested target SLOs
These are starting points for a single-instance deployment on modest hardware;
tune them to your environment and re-baseline after infra changes.
| Metric | Target |
|--------|--------|
| `GET /health` p99 latency | < 20 ms |
| `GET /tasks`, `GET /state` p95 latency | < 100 ms |
| `GET /metrics` p95 latency | < 150 ms |
| `POST /reset` + `POST /step` p95 latency | < 400 ms |
| Error rate (all endpoints) | < 0.1% |
| Sustained throughput before p95 SLOs breach | record as the capacity number |
A useful capacity exercise: ramp `--users` until any p95 SLO is breached or the
error rate climbs above the threshold, then record the RPS just below that point
as the safe sustained throughput for one instance.