milestone 30: overview as a dashboard, explicit health contract, period aggregations
Gates / frontend (push) Successful in 1m32s
Gates / test (push) Successful in 1m54s
Gates / package (push) Successful in 5m28s
Gates / container (push) Successful in 14s
Gates / test-aarch64 (push) Failing after 3h10m0s
CI / gates (push) Failing after 3h11m55s
Gates / frontend (push) Successful in 1m32s
Gates / test (push) Successful in 1m54s
Gates / package (push) Successful in 5m28s
Gates / container (push) Successful in 14s
Gates / test-aarch64 (push) Failing after 3h10m0s
CI / gates (push) Failing after 3h11m55s
This commit is contained in:
@@ -4,7 +4,7 @@ nxdns serves its admin API itself, on `web.bind:web.port` (default port 8080), a
|
||||
|
||||
The machine-readable contract is `src/web/openapi.yaml`, which the running server hands out unauthenticated at `GET /api/openapi.yaml`. Request and response schemas for every operation live there. When this page and the YAML disagree, the YAML wins.
|
||||
|
||||
The route table is `src/web/routes.zig`; the [Operations](#operations) table below carries all 61 of its entries.
|
||||
The route table is `src/web/routes.zig`; the [Operations](#operations) table below carries all 63 of its entries.
|
||||
|
||||
## Conventions
|
||||
|
||||
@@ -108,8 +108,10 @@ Auth `open` means no session is required; `session` means a valid session cookie
|
||||
| GET | `/api/queries/live` | session | exempt | read | Live query stream (server-sent events) |
|
||||
| GET | `/api/stats` | session | counted | read | Totals for a period |
|
||||
| GET | `/api/stats/timeseries` | session | counted | read | Bucketed counts for a period |
|
||||
| GET | `/api/stats/types` | session | counted | read | Query-type breakdown for a period |
|
||||
| GET | `/api/stats/routes` | session | counted | read | How the period's queries were answered |
|
||||
| GET | `/api/stats/clients` | session | counted | read | Per-client bucketed counts for a period |
|
||||
| GET | `/api/lookup` | session | counted | read | Explain a domain |
|
||||
| GET | `/api/upstream/health` | session | counted | read | Upstream pool health |
|
||||
| GET | `/api/diagnostics` | session | counted | read | Operational event log |
|
||||
| DELETE | `/api/diagnostics` | session | counted | runtime action | Purge every resolved event |
|
||||
| GET | `/api/diagnostics/{id}` | session | counted | read | One operational event |
|
||||
@@ -213,4 +215,6 @@ A non-empty `rewrites.cname_target` on a query detail means the decision landed
|
||||
|
||||
### Coverage
|
||||
|
||||
`GET /api/queries`, `GET /api/stats` and `GET /api/stats/timeseries` each answer with a `coverage` object: `available_since` is the oldest instant the query log is still complete for, and `complete` is true only when the window the request asked about starts at or after it. Retention deletes rows and advances the watermark in one transaction, so a client can tell an empty window from a pruned one instead of charting the gap as zero. A request with no lower bound at all asks about the whole of history, and is never complete.
|
||||
Every window-bounded read — `GET /api/queries` and the five `GET /api/stats*` endpoints — answers with a `coverage` object: `available_since` is the oldest instant the query log is still complete for, and `complete` is true only when the window the request asked about starts at or after it. Retention deletes rows and advances the watermark in one transaction, so a client can tell an empty window from a pruned one instead of charting the gap as zero. A request with no lower bound at all asks about the whole of history, and is never complete.
|
||||
|
||||
Each of these responses reads its rows and its watermark inside one SQLite read transaction, so retention cannot prune between the two and hand back pre-prune rows tagged with a post-prune `available_since`. Coherence stops there: two separate requests are two separate reads, and queries logged between them can move the counts.
|
||||
|
||||
@@ -147,7 +147,7 @@ The query-log writer commits one transaction per interval instead of one per que
|
||||
What it costs:
|
||||
|
||||
- **Crash-loss window.** A process that dies takes roughly `interval` seconds of query history with it. That is the normal case, not a guaranteed maximum: a batch the disk monitor is holding back (free space below the critical threshold) or one waiting on a database write lock can be considerably older when the process dies. Power loss can additionally lose recent committed transactions, because `querylog.db` runs with WAL and `synchronous=NORMAL` — that was already true at any interval, and setting `0` does not buy per-query durability. Query history is the least valuable data on this box: nothing else depends on it, and it is deleted by retention anyway.
|
||||
- **Staleness.** Every read backed by the query log — the query-log page, the dashboard totals, the timeseries — lags about `interval` seconds behind, and further behind while writes are gated or slow. The live view does not lag: it is fed from the SSE hub before the queue, so queries appear there the moment they are answered.
|
||||
- **Staleness.** Every read backed by the query log — the query-log page, the Overview totals, the timeseries — lags about `interval` seconds behind, and further behind while writes are gated or slow. The live view does not lag: it is fed from the SSE hub before the queue, so queries appear there the moment they are answered.
|
||||
|
||||
`0` means "do not wait": the writer commits the entry that woke it together with whatever is already queued, up to 100 rows. Use it when you want the query-log page to be current to the second and you do not care what that costs the disk.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user