vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Add a load harness behind the load tag
0448a9783b69753a26bd7478e9eb70c510a8e487 · Keenan Nemetz · 10d ago · parent 14b5c648
Add a load harness behind the load tag
Every limit in the operations guide was chosen from reasoning. The harness
is what replaces that with a number: a real server on a loopback port with
a temporary data directory, seeded with a repository that has history,
files at several sizes, an issue and a change with comments and an
approval, and a release artifact in the LFS store.
One scenario proves it end to end: anonymous shallow clones over HTTP.
Each iteration probes `info/refs` so a refusal is reported as the status
it was refused with, then clones. Workers run until the configured
duration, and the report gives requests, throughput, p50/p90/p99 latency,
statuses, failure reasons, and the peak of RSS, file descriptors,
goroutines and the server's git child processes.
It is behind `//go:build load`, so `go test ./...` and the verify workflow
never run it, and nothing in it ships in the binary. The other read-path
scenarios in #100 add their own step functions to the same runner.
Run it:
VOLE_LOAD_SCENARIO=clone VOLE_LOAD_CONCURRENCY=8 VOLE_LOAD_DURATION=30s \
go test -tags load,sqlite_fts5 -run '^TestLoad$' -v ./server
docs/load-testing.md +66
| @@ -0,0 +1,66 @@ | |||
| 1 | # Load testing | ||
| 2 | |||
| 3 | Vole ships a load harness that drives a real instance on a loopback port with a | ||
| 4 | temporary data directory. It runs the same server code an operator runs, behind | ||
| 5 | the `load` build tag so `go test ./...` and the verify workflow never execute it. | ||
| 6 | |||
| 7 | Use it to answer a question with a number: did this change make a route faster, | ||
| 8 | and where does it stop? The limits in [Configuration](configuration.md) were | ||
| 9 | chosen from reasoning first; a run here is what replaces that with measurement. | ||
| 10 | |||
| 11 | ## One command | ||
| 12 | |||
| 13 | ```sh | ||
| 14 | VOLE_LOAD_SCENARIO=clone VOLE_LOAD_CONCURRENCY=8 VOLE_LOAD_DURATION=30s \ | ||
| 15 | go test -tags load,sqlite_fts5 -run '^TestLoad$' -v ./server | ||
| 16 | ``` | ||
| 17 | |||
| 18 | The build needs `sqlite_fts5` beside `libsqlite3` (see | ||
| 19 | [Getting started](getting-started.md)); `-tags load` on the command line replaces | ||
| 20 | `GOFLAGS`, so both belong on it. | ||
| 21 | |||
| 22 | ## Knobs | ||
| 23 | |||
| 24 | | Variable | Default | What it does | | ||
| 25 | |---|---|---| | ||
| 26 | | `VOLE_LOAD_SCENARIO` | `clone` | The scenario to run. Currently `clone`: anonymous shallow clones over HTTP. | | ||
| 27 | | `VOLE_LOAD_CONCURRENCY` | `4` | Workers driving the scenario in parallel. | | ||
| 28 | | `VOLE_LOAD_DURATION` | `10s` | How long the workers keep going. | | ||
| 29 | |||
| 30 | ## What it seeds | ||
| 31 | |||
| 32 | One repository with a shape worth measuring: | ||
| 33 | |||
| 34 | - 300 commits, so history walks and the commit count cost something; | ||
| 35 | - files at several sizes: a small readme, a 1 KB text file, a 100 KB source | ||
| 36 | file, and a 1 MiB binary; | ||
| 37 | - an issue with five comments, and a change request with five review comments | ||
| 38 | and an approval by a second account; | ||
| 39 | - a release tag with one artifact in the LFS store. | ||
| 40 | |||
| 41 | The repository is read once before the run so its index is built outside the | ||
| 42 | measurement. | ||
| 43 | |||
| 44 | ## What it reports | ||
| 45 | |||
| 46 | Per scenario: requests and throughput, latency at p50/p90/p99, HTTP statuses, | ||
| 47 | failure reasons, and the peak of process RSS, open file descriptors, goroutines, | ||
| 48 | and git child processes during the run. | ||
| 49 | |||
| 50 | ## What it does not tell you | ||
| 51 | |||
| 52 | - The server runs inside the test process, so RSS and goroutines include the | ||
| 53 | harness and its clients. | ||
| 54 | - Child processes are counted by name (`http-backend`, `upload-pack`, | ||
| 55 | `receive-pack`). The harness's own `git clone` processes are excluded. | ||
| 56 | - Loopback has no network cost and no TLS. The numbers are a floor and a way to | ||
| 57 | compare changes, not a prediction of a real deployment. | ||
| 58 | - A warm cache is not simulated beyond the index build: the first run after a | ||
| 59 | restart will be slower. | ||
| 60 | |||
| 61 | ## Adding a scenario | ||
| 62 | |||
| 63 | A scenario is a step function and a deadline: each worker calls the step in a | ||
| 64 | loop until `VOLE_LOAD_DURATION` expires, and the step reports the HTTP status it | ||
| 65 | saw and any failure. The seeding, sampling and reporting in | ||
| 66 | `server/load_test.go` are shared, so a new route is a `case` and a function. | ||