Add script/loadtest to measure throughput, latency and memory (closes #81) #209
@@ -31,6 +31,21 @@ P2: security: per-IP rate limiting on the image routes
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-10-04 load test (closes #81): `script/loadtest [duration [clients]]`
|
||||||
|
(`make loadtest`, defaults `10s` and `4`), a benchmark that `script/check`
|
||||||
|
does not run, measures three scenarios, each against a new pixad container and
|
||||||
|
a new upstream host, `cmd/loadtest-origin`: `hit` (one cached image), `miss`
|
||||||
|
(a new source image every request) and `herd` (each new source image asked for
|
||||||
|
by all clients at once). For each it prints vegeta's report (requests per
|
||||||
|
second, latency percentiles, status codes), pixad's peak resident memory and
|
||||||
|
the requests that reached the origin. `README.md` says how to run it and read
|
||||||
|
it, and keeps 1-5k r/s as a target not yet measured. First measurement, with
|
||||||
|
the defaults on a shared 48-CPU machine with other work running: a baseline
|
||||||
|
for later changes, not a test of the target. `hit` 1413 r/s, p50 0.7 ms, p95
|
||||||
|
8.7 ms, p99 44 ms, peak 53 MiB (4 clients that each wait for their answer, so
|
||||||
|
not pixad's limit); `miss` 70 r/s, p50 52 ms, p95 91 ms, p99 122 ms, peak 100
|
||||||
|
MiB, one fetch per request; `herd` 74 r/s, p50 52 ms, p95 69 ms, p99 111 ms,
|
||||||
|
peak 60 MiB, 188 fetches for 749 requests.
|
||||||
- 2026-10-04 the CI checkout fetches the tags (closes #208): the checkout step
|
- 2026-10-04 the CI checkout fetches the tags (closes #208): the checkout step
|
||||||
in `.gitea/workflows/check.yml` sets `fetch-depth: 0`, as `REPO_POLICIES.md`
|
in `.gitea/workflows/check.yml` sets `fetch-depth: 0`, as `REPO_POLICIES.md`
|
||||||
asks of a repo that takes its version from the tags, so a CI build of a tagged
|
asks of a repo that takes its version from the tags, so a CI build of a tagged
|
||||||
@@ -56,30 +71,6 @@ P2: security: per-IP rate limiting on the image routes
|
|||||||
comment are copied from the canonical `.gitignore` in `sneak/prompts`,
|
comment are copied from the canonical `.gitignore` in `sneak/prompts`,
|
||||||
unanchored so it matches at every depth. `.dockerignore` already has
|
unanchored so it matches at every depth. `.dockerignore` already has
|
||||||
`.claude`.
|
`.claude`.
|
||||||
- 2026-10-04 load test (closes #81): `script/loadtest [duration [clients]]`
|
|
||||||
(`make loadtest`, defaults `10s` and `4`), a benchmark that `script/check`
|
|
||||||
does not run, measures three scenarios, each against a new pixad container and
|
|
||||||
a new upstream host, `cmd/loadtest-origin`: `hit` (one cached image), `miss`
|
|
||||||
(a new source image every request) and `herd` (each new source image asked for
|
|
||||||
by all clients at once). For each it prints vegeta's report (requests per
|
|
||||||
second, latency percentiles, status codes), pixad's peak resident memory and
|
|
||||||
the requests that reached the origin. `README.md` says how to run it and read
|
|
||||||
it, and keeps 1-5k r/s as a target not yet measured. First measurement, with
|
|
||||||
the defaults on a shared 48-CPU machine with other work running: a baseline
|
|
||||||
for later changes, not a test of the target. `hit` 1413 r/s, p50 0.7 ms, p95
|
|
||||||
8.7 ms, p99 44 ms, peak 53 MiB (4 clients that each wait for their answer, so
|
|
||||||
not pixad's limit); `miss` 70 r/s, p50 52 ms, p95 91 ms, p99 122 ms, peak 100
|
|
||||||
MiB, one fetch per request; `herd` 74 r/s, p50 52 ms, p95 69 ms, p99 111 ms,
|
|
||||||
peak 60 MiB, 188 fetches for 749 requests.
|
|
||||||
- 2026-10-04 `cmd/pixad/main.go` is one call into `internal/` (closes #206):
|
|
||||||
what it did (the command line and its `--config` flag, setting
|
|
||||||
`PIXA_CONFIG_PATH`, ignoring `SIGPIPE`, starting the fx app) is now `Run` in
|
|
||||||
`internal/app`, unchanged, and `main` calls it with `Version`, which the build
|
|
||||||
still sets through `-X main.Version`. That code had no tests to move.
|
|
||||||
- 2026-10-04 `.gitignore` ignores `.claude/` (closes #204): the entry and its
|
|
||||||
comment are copied from the canonical `.gitignore` in `sneak/prompts`,
|
|
||||||
unanchored so it matches at every depth. `.dockerignore` already has
|
|
||||||
`.claude`.
|
|
||||||
- 2026-10-04 `.dockerignore` keeps secrets out at every depth (closes #205): the
|
- 2026-10-04 `.dockerignore` keeps secrets out at every depth (closes #205): the
|
||||||
file is now the standard one from `sneak/prompts`, whose patterns match in
|
file is now the standard one from `sneak/prompts`, whose patterns match in
|
||||||
every directory and, for environment files and private keys, in any letter
|
every directory and, for environment files and private keys, in any letter
|
||||||
|
|||||||
+3
-1
@@ -51,8 +51,10 @@ main() {
|
|||||||
"" | *[!0-9]*) usage ;;
|
"" | *[!0-9]*) usage ;;
|
||||||
esac
|
esac
|
||||||
[ "$number" -gt 0 ] || usage
|
[ "$number" -gt 0 ] || usage
|
||||||
|
# The number of clients is a whole number that does not start with 0,
|
||||||
|
# which also refuses zero: vegeta reads a leading 0 as octal.
|
||||||
case "$clients" in
|
case "$clients" in
|
||||||
*[!0-9]* | 0) usage ;;
|
*[!0-9]* | 0*) usage ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
|
|||||||
Reference in New Issue
Block a user