cassowary: Spec-Friendly HTTP Load in CI
cassowary run -u http://localhost:8080 -c 50 -n 5000 loads an endpoint with 50 concurrent workers over 5000 requests and can write JSON metrics for a CI gate.
cassowary is a small Go load tester built with CI in mind: it takes a URL, concurrency, and request count, and can emit JSON so a workflow step can assert on p95 latency or failed requests.
What it does
cassowary run drives -u (the URL) with -c concurrent users for -n total requests (or -d duration in newer builds), reporting TCP connect, server processing, and total response-time percentiles plus the failed-request count. --json-metrics writes a metrics file for programmatic gating.
Common usage
# 50 users, 5000 requests, JSON metrics for the gate
cassowary run -u http://localhost:8080/ -c 50 -n 5000 \
--json-metrics --json-metrics-file metrics.json
# POST a JSON body with a header
cassowary run -u http://localhost:8080/api -c 20 -n 2000 \
-m POST --data '{"k":"v"}' -H 'Content-Type:application/json'Options
| Flag | What it does |
|---|---|
| -u, --url <url> | Target URL |
| -c, --concurrency N | Concurrent users |
| -n, --requests N | Total requests |
| -d, --duration N | Run for N seconds instead of a count |
| -m, --requests-from-file / --data | POST data or a file of URLs |
| -H, --header <h> | Add a request header |
| --json-metrics | Emit metrics as JSON |
In CI
Use --json-metrics-file and parse the JSON (e.g. with jq) to compare p95 or failedRequests against a budget, then exit 1. cassowary exits 0 on a normal run regardless of latency, so the gate must be explicit. Warm the server with a small run before the measured one.
Common errors in CI
cassowary: command not found means install the release binary or go install github.com/rogerwelin/cassowary/cmd/cassowary@latest. Get "http://localhost:8080/": dial tcp ... connection refused means the target is not up yet. A high Failed requests count with 5xx statuses usually means you overran the server; lower -c. If jq cannot find a field, check the exact metric key names in the emitted JSON, which differ slightly by version.