Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

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.

Code

392 commit(s) on main

Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys keenan · 16m ago 4b8c9e1d
source preview history blame raw 4.4 KiB
1# Load testing
2
3Vole ships a load harness that drives a real instance on a loopback port with a
4temporary data directory. It runs the same server code an operator runs, behind
5the `load` build tag so `go test ./...` and the verify workflow never execute it.
6
7Use it to answer a question with a number: did this change make a route faster,
8and where does it stop? The limits in [Configuration](configuration.md) were
9chosen from reasoning first; a run here is what replaces that with measurement.
10
11## One command
12
13Every route:
14
15```sh
16VOLE_LOAD_SCENARIO=all VOLE_LOAD_CONCURRENCY=4 VOLE_LOAD_DURATION=5s \
17 go test -tags load,sqlite_fts5 -run '^TestLoad$' -v ./server
18```
19
20One route, or clones past the limit:
21
22```sh
23VOLE_LOAD_SCENARIO=clone VOLE_LOAD_CONCURRENCY=16 VOLE_LOAD_DURATION=30s \
24 go test -tags load,sqlite_fts5 -run '^TestLoad$' -v ./server
25```
26
27The build needs `sqlite_fts5` beside `libsqlite3` (see
28[Getting started](getting-started.md)); `-tags load` on the command line replaces
29`GOFLAGS`, so both belong on it.
30
31## Knobs
32
33| Variable | Default | What it does |
34|---|---|---|
35| `VOLE_LOAD_SCENARIO` | `clone` | `all`, or one of `clone`, `clone-large`, `blob-small`, `blob-limit`, `raw-small`, `raw-limit`, `raw-range`, `overview`, `tree`, `commits`, `compare`, `change`, `issue`, `activity`, `instance-activity`, `home`, `search-code`, `search-find`, `artifact`, `artifact-range`, `site`, `comment`. |
36| `VOLE_LOAD_CONCURRENCY` | `4` | Workers driving the scenario in parallel. |
37| `VOLE_LOAD_DURATION` | `10s` | How long the workers keep going. |
38
39## What it seeds
40
41Three repositories and a site:
42
43- `demo`: 300 commits; files at several sizes including one at the configured
44 `max_blob_bytes` (the harness sets 8 MiB, so the file is one a push could
45 deliver); an issue with five comments; a change request whose branch carries a
46 4000-line diff, with five review comments and an approval by a second account;
47 a release tag with one artifact in the LFS store; and a `pages` branch served
48 at `demo.pages.test`.
49- `small`: 50 commits and small files, so a clone is quick.
50- `large`: 3000 commits and one 16 MiB incompressible blob, so a clone is not.
51
52`demo` and `large` are read once before the run so their indexes are built
53outside the measurement.
54
55The clone scenario gives every worker a distinct `X-Forwarded-For` address (the
56fixture trusts the header on loopback), so the global `git_http_max_concurrent`
57is the limit that refuses rather than the per-client one. At the configured
58`git_http_max_concurrent` the run stays at `200`; above it, refusals arrive as
59`503` with `Retry-After`, and a clone caught by one reports the curl failure.
60
61## The one scenario that writes
62
63Every scenario above reads. `comment` posts a comment as the fixture's writer
64account, which is one commit on the metadata branch that every write in the instance
65shares: the issue, the change request, the approval, and the release pointer all move
66the same ref. It is the scenario to run after touching the write path, and the one
67where throughput should stay flat as workers are added while latency grows with the
68queue behind it — a rising error count or a falling throughput means writers are
69fighting over the branch instead of taking turns.
70
71## What it reports
72
73Per scenario: requests and throughput, latency at p50/p90/p99, HTTP statuses,
74failure reasons, and the peak of process RSS, open file descriptors, goroutines,
75and git child processes during the run.
76
77## What it does not tell you
78
79- The server runs inside the test process, so RSS and goroutines include the
80 harness and its clients.
81- Child processes are counted by name (`http-backend`, `upload-pack`,
82 `receive-pack`). The harness's own `git clone` processes are excluded.
83- Loopback has no network cost and no TLS. The numbers are a floor and a way to
84 compare changes, not a prediction of a real deployment.
85- A warm cache is not simulated beyond the index build: the first run after a
86 restart will be slower.
87- The `raw-range` and `artifact-range` scenarios send a `Range` header and expect
88 `206` with only the requested bytes. Before #119 the raw route answered `200`
89 with the whole file, which is why the scenario exists.
90
91## Adding a scenario
92
93A scenario is a step function and a deadline: each worker calls the step in a
94loop until `VOLE_LOAD_DURATION` expires, and the step reports the HTTP status it
95saw and any failure. The seeding, sampling and reporting in
96`server/load_test.go` are shared, so a new route is a `case` and a function.
97