Skip to content

feat(stats): usage.get and simlock stats from the projections - #711

Merged
V3RON merged 0 commit into
task/630from
task/631
Oct 19, 2026
Merged

V3RON merged 0 commit into
task/630from
task/631

Conversation

@V3RON

@V3RON V3RON commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Closes #631

Status

Implement: done (all spec tests green, audit 6/6) Review: round 2, 0 open Hardware: n/a Gate: waiting for the stack

Done when

  • After a scripted run against the fake driver, simlock stats --since 1h prints requests and grants by source; wait, held, turnaround, provisioning and boot p50/p95/max; rejections by reason and incidents; use per platform, worker and requester. --json prints the same figures, and the counts match simlock events --since 1h counted by hand.
  • simlock stats --from <before the run> --to <after it>, with --to before the stop, prints the same output, whole, after simlock daemon stop and simlock daemon start.
  • On a gateway with two workers, the output has fleet totals and one row per worker; each worker's own output has one row, itself.
  • simlock stats --since 30d on a fresh install prints Figures cover from <time> above the figures; a window ending before the oldest row exits 12 with the HISTORY_NOT_KEPT message.
  • simlock stats while history.db could not be opened exits 1 with HISTORY_UNAVAILABLE.
  • docs/CLI.md describes simlock stats, its flags, the partial note and exits 12 and 1; docs/CLIENT.md describes usage.

Assumptions

  • Assumption: where the figure tests drive the code — the counting, sampling, window, series and coverage claims drive createUsageReader over a real history.db and the real read thread (the module surface both handlers call); the claims that belong to a handler (labels, the worker's own entry, HISTORY_NOT_KEPT and HISTORY_UNAVAILABLE text, the read thread's dedupe and crash) drive the worker and gateway dispatchers. This avoids repeating one figure test in two dispatcher harnesses.

  • Assumption: how a dispatcher learns the history file — both dispatchers take an optional historyPath and read eventHistory.status().opened (both already hold eventHistory), and build their own reader; green makes historyPath required and dispose() stop the reader.

  • Assumption: the shared test rig (usageRig and the event builders) lives in src/history/testing.ts, the one place dependency rules let the daemon, gateway and core tests import it from.

  • Assumption: historyPath stays optional on both dispatchers (the red-stage note said green would make it required) — many existing harnesses build a dispatcher without a history file; with none, usage.get answers HISTORY_UNAVAILABLE. main.ts passes it in both modes. DaemonServer gained the same optional historyPath, and createHarness in server.test.ts gained a withHistory option so the routing test runs over a real open history.db.

  • Assumption: minute figures (slots, RAM, queue, series) read complete minutes only — the minute still running at to is left out, because that row is stored as if its last value held to the minute's end and is cut at a stop, so the e2e's "same output after stop and start" cannot hold with it in. A window shorter than one minute has no slot, RAM or queue figure.

  • Assumption: a mean is the sum of integrals over the sum of knownMs of the buckets that hold that field — equal to the spec's wording when every bucket has it, and not pulled down by buckets that lack RAM or queue.

  • Assumption: bySource.unknown is counted on a gateway only (a grant with no source), as PR feat(usage): usage.get and simlock stats, the figures from the event history #356 did; a worker counts only warm, booted and provisioned.

  • Assumption: src/gateway/boundary.test.ts counts republish callers outside testing.ts helpers, since the rig in src/history/testing.ts republishes relayed events; and .oxlintrc.json lets the gateway import createUsageReader and UsageReader from core, the one place the dispatcher builds its reader.

  • Assumption: a failed read (thread exit, SQLite error, no answer) reaches the caller as a DispatchError with code INTERNAL, built in usageAnswer, so the code is on the error and the operation line logs it at error.

  • Assumption: under vitest the read thread runs read-thread.ts itself, with --experimental-transform-types and a resolve hook that maps .js imports to .ts; built, it runs read-thread.js.

  • Assumption: a time with a day the month lacks (2026-02-31) or the hour 24 is a bad time — Date.parse rolls both over to another instant, and a typo must not ask for another window; the smallest strict reading of ISO 8601.

  • Assumption: the 500 ms read bound times a running read thread — the timing test makes one small read first, because a source run (CI measured 574 ms, 140 ms of it thread start-up) transpiles the thread's modules, which a built daemon does not; the 7-day read itself took about 70 ms.

Review

Spec review: 0 blocking, 0 fixed, 3 notes. Code review: 2 blocking, 2 fixed, 0 notes. Claims review: 3 blocking, 3 fixed, 1 notes.
Rounds: 2. Tokens: 14.7M.

Written by an agent.

@V3RON

V3RON commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

Decision taken

The round 1 spec review asked for three decisions. I kept what the PR already does in each case: it is closest to the code and the easiest to undo.

  1. The minute that is still running.
    • Taken: slot, RAM and queue figures cover whole minutes only. stats for a window that ends mid-minute leaves that minute out.
    • Other option: count the partial minute. The output after daemon stop/start would then differ from before, which breaks Done when 2.
    • To revert: change the cut in src/core/usage/read-thread.ts and relax the e2e test's byte-for-byte check.
  2. What counts as a boot.
  3. The timing bound.
    • Taken: 500 ms covers a 7-day read on a running read thread. The thread's first start is not timed, and from source CI it took about 500 ms of the 574 ms measured.
    • Other option: time a cold first call. That needs a larger bound or a pre-started thread.
    • To revert: remove the warm-up read in usage-read.timing.test.ts.

The issue body records all three as pending the maintainer.

Written by an agent.

@V3RON

V3RON commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

Review notes

Not blocking, not verified. Each is one reviewer's claim.

  • spec: src/core/usage/read-thread.ts:50 the running minute is left out of slot, RAM and queue figures (see Decision taken).
  • spec: e2e/usage-stats.test.ts:92 a boot counts only with bootDuration above 0, so a hand count from simlock events can differ (see Decision taken).
  • spec: src/core/usage/usage-read.timing.test.ts:66 the warm-up read means the 500 ms bound no longer covers a cold first call (see Decision taken).
  • claims: src/gateway/dispatcher.ts:2 the header starts with why: to satisfy the comment lint but describes what the module is.

Written by an agent.

@V3RON
V3RON marked this pull request as ready for review October 19, 2026 10:05
@V3RON
V3RON added this pull request to stack #691 October 19, 2026 10:05
@V3RON
V3RON merged commit d71eb83 into main Oct 19, 2026
@V3RON
V3RON deleted the task/631 branch October 19, 2026 12:29
V3RON added a commit that referenced this pull request Oct 19, 2026
…stats and console figures (#623) (#626)

Delivers #623: the event history moves to `history.db`, with usage
projections, `simlock stats`, `GET /v1/stats` and the console's figures.
It also includes ADR 0032.

This PR carries the whole feature. The stack's PRs were reviewed one by
one, and then a stack sync pushed every branch to one commit. GitHub
then marked them as merged into each other, never into `main`. Their
reviews stay there:

- ADR 0032 (this PR)
- #635 → #690
- #636 → #692
- #637 → #693
- #629 → #694
- #630 → #695
- #631 → #711
- #632 → #724
- #633 → #727
- #634 → #731

After the sync onto `main`, a fix-check reviewed the conflict
resolutions. Its fix makes `history.db` and its WAL/SHM files owner-only
(0600), as #685 did for `events.jsonl`.

Closes #629, closes #630, closes #631, closes #632, closes #633, closes
#634, closes #635, closes #636, closes #637.

### Status
Implement: done Review: per-task PRs passed; sync fix-check passed
Hardware: n/a Gate: running

## Review

Spec review: skipped. Code review: 4 blocking, 4 fixed, 1 notes. Claims
review: 0 blocking, 0 fixed, 0 notes.
Rounds: 1. Tokens: 29.0M.

*Written by an agent.*
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

usage.get and simlock stats from the projections

1 participant