chadtmiller.com/knotless
git that lives in your Atmosphere account
1
10
just queue-stats counts the ejections its runners write
docs/design/merge-queue.md+4−4
| @@ -565,15 +565,15 @@ keeper runs no model. | ||
## Measuring it | ||
| ||
`just queue-stats` says how the queue did, by day (UTC), from what it | ||
already writes: the queue records in the owner's PDS, the batch checks and | ||
their logs, and main's log for when each pull landed. | ||
already writes: the queue records in the owner's PDS and its runners', the | ||
batch checks and their logs, and main's log for when each pull landed. | ||
| ||
- **queued** and **landed**: queue records written, and pulls landed. | ||
- **queued → landed**: minutes from a pull's first queue record to its | ||
commit on main, past any ejections, at the median and the 95th | ||
percentile. | ||
- **ejected**: ejections over queue records, and by kind: a conflict, a | ||
failed batch, a branch gone, or other. | ||
- **ejected**: ejections over queue records, and by kind: a failed | ||
pre-flight, a review, a conflict, a failed batch, a branch gone, or other. | ||
- **batches passed**: of the batches that ended, those that passed. | ||
- **batch test**: minutes of a batch's `test` run, the longest of its six. | ||
- **cancelled checks**, by why: a runner restart, a batch dropped as the | ||
harness/queue-stats.mjs+28−11
| @@ -5,7 +5,8 @@ | ||
// node harness/queue-stats.mjs [<handle>/<name>] [--days <n>] [--json] | ||
// | ||
// It reads what the queue already writes: the queue records in the | ||
// owner's PDS, the batch checks the runners file (through `knotless | ||
// owner's PDS and its runners' (a pull is queued by its owner and ejected | ||
// by a runner), the batch checks the runners file (through `knotless | ||
// checks --queue`, so the CLI must be logged in or the repository | ||
// public), their logs, and main's log in this checkout for when each pull | ||
// landed (`git fetch` first). The runner prunes batch checks it no longer | ||
| @@ -117,8 +118,19 @@ for (const c of listed) { | ||
| ||
// Queue records: when each pull was queued, and each ejection and why. | ||
const owner = listed[0]?.repo.uri.split('/')[2] | ||
const queue = owner ? await records(owner, 'dev.knotless.queue') : [] | ||
for (const r of queue) saved.queue = { ...saved.queue, [r.uri]: r.value } | ||
const name = repo.split('/')[1] | ||
const repoUri = owner && `at://${owner}/dev.knotless.repo/${name}` | ||
const repoRecord = async () => { | ||
const q = new URLSearchParams({ repo: owner, collection: 'dev.knotless.repo', rkey: name }) | ||
const res = await get(`${await pds(owner)}/xrpc/com.atproto.repo.getRecord?${q}`) | ||
return (await res.json()).value | ||
} | ||
const runners = owner ? ((await repoRecord()).runners ?? []) : [] | ||
for (const did of owner ? [owner, ...runners.map((r) => r.did)] : []) { | ||
for (const r of await records(did, 'dev.knotless.queue')) { | ||
saved.queue = { ...saved.queue, [r.uri]: r.value } | ||
} | ||
} | ||
| ||
// Landed: the squashed commits on main, each naming its pull. | ||
const landed = {} | ||
| @@ -148,13 +160,17 @@ const pct = (q) => (xs) => | ||
xs.length ? [...xs].sort((a, b) => a - b)[Math.ceil(xs.length * q) - 1] : null | ||
const p95 = pct(0.95) | ||
const kind = (reason = '') => | ||
reason.includes('conflicts') | ||
? 'conflict' | ||
: reason.includes('failed its checks') | ||
? 'failed batch' | ||
: reason.includes('has no branch') | ||
? 'branch gone' | ||
: 'other' | ||
reason.includes('pre-flight') | ||
? 'failed pre-flight' | ||
: reason.includes('not reviewed') || reason.includes('changes asked') | ||
? 'review' | ||
: reason.includes('conflicts') | ||
? 'conflict' | ||
: reason.includes('failed its checks') | ||
? 'failed batch' | ||
: reason.includes('has no branch') | ||
? 'branch gone' | ||
: 'other' | ||
const cancel = (why = '') => | ||
why.includes('runner stopped') | ||
? 'runner restart' | ||
| @@ -182,8 +198,9 @@ const at = (d) => | ||
| ||
// Each landed pull: from the first time it was queued for this landing, | ||
// past any ejections, to the commit on main. | ||
// A runner serves other repositories too, and the owner has others. | ||
const queueRecords = Object.values(saved.queue ?? {}).filter( | ||
(v) => Date.parse(v.createdAt) >= since, | ||
(v) => Date.parse(v.createdAt) >= since && (!repoUri || v.repo?.uri === repoUri), | ||
) | ||
for (const [pull, { at: when }] of Object.entries(landed)) { | ||
const mine = queueRecords | ||