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:
+289
-131
@@ -65,8 +65,11 @@ paths:
|
||||
get:
|
||||
summary: Health rollup
|
||||
description: |
|
||||
Always 200; `status` is `degraded` when the disk is not ok, no
|
||||
upstream is available, or the query-log writer failed. Always
|
||||
Always 200. `status` is `degraded` when, and only when, one of the five
|
||||
condition objects is in a degrading state: protection `unavailable`,
|
||||
upstreams `unavailable`, query history `losing` or `failed`,
|
||||
diagnostics `unavailable`, or disk `low` or `critical`. A paused
|
||||
protection is an operator's own choice and does not degrade. Always
|
||||
unauthenticated and never rate limited.
|
||||
security: []
|
||||
responses:
|
||||
@@ -465,6 +468,99 @@ paths:
|
||||
"503":
|
||||
$ref: "#/components/responses/Unavailable"
|
||||
|
||||
/api/stats/types:
|
||||
get:
|
||||
summary: Query-type breakdown for a period
|
||||
description: |
|
||||
How many queries of each DNS type the period's window holds, over the
|
||||
same UTC-aligned window `/api/stats` reports for. Rows carry the numeric
|
||||
type only: the type-name table lives in the admin, and a second copy
|
||||
here would drift out of agreement with it. `qtype` is nullable in the
|
||||
query log, so the rows that carry no type group into a row of their own
|
||||
rather than vanishing from a breakdown that claims to add up. Ordered by
|
||||
count descending, then type ascending with the null row last. Types
|
||||
absent from the window are absent from the list.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/Period"
|
||||
responses:
|
||||
"200":
|
||||
description: The type breakdown.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/StatsTypes"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"429":
|
||||
$ref: "#/components/responses/RateLimited"
|
||||
"500":
|
||||
$ref: "#/components/responses/Internal"
|
||||
"503":
|
||||
$ref: "#/components/responses/Unavailable"
|
||||
|
||||
/api/stats/routes:
|
||||
get:
|
||||
summary: How the period's queries were answered
|
||||
description: |
|
||||
A breakdown by answering route over the same window `/api/stats`
|
||||
reports for. `source` is the answering resolver's identity — the
|
||||
upstream url on `upstream` rows, the zone on `forward_zone` rows, null
|
||||
on every other kind and on rows whose identity the log did not record.
|
||||
It is not the blocklist a block came from. Ordered by count descending,
|
||||
then route ascending, then source ascending with nulls last.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/Period"
|
||||
responses:
|
||||
"200":
|
||||
description: The route breakdown.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/StatsRoutes"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"429":
|
||||
$ref: "#/components/responses/RateLimited"
|
||||
"500":
|
||||
$ref: "#/components/responses/Internal"
|
||||
"503":
|
||||
$ref: "#/components/responses/Unavailable"
|
||||
|
||||
/api/stats/clients:
|
||||
get:
|
||||
summary: Per-client bucketed counts for a period
|
||||
description: |
|
||||
One zero-filled series per client, bucketed exactly like
|
||||
`/api/stats/timeseries` so the two charts share an x-axis. The eight
|
||||
clients with the most queries in the window are named, ranked by count
|
||||
descending then address ascending; every other client sums into
|
||||
`other`, which is always present and always holds one entry per bucket
|
||||
in the window — including when `clients` is empty, when no client fell
|
||||
outside the named eight, and when the window holds no queries at all.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/Period"
|
||||
responses:
|
||||
"200":
|
||||
description: The per-client series.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/StatsClients"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"429":
|
||||
$ref: "#/components/responses/RateLimited"
|
||||
"500":
|
||||
$ref: "#/components/responses/Internal"
|
||||
"503":
|
||||
$ref: "#/components/responses/Unavailable"
|
||||
|
||||
/api/lookup:
|
||||
get:
|
||||
summary: Explain a domain
|
||||
@@ -498,35 +594,6 @@ paths:
|
||||
"503":
|
||||
$ref: "#/components/responses/Unavailable"
|
||||
|
||||
/api/upstream/health:
|
||||
get:
|
||||
summary: Upstream pool health for a period
|
||||
description: |
|
||||
Each upstream's live routing state beside its recorded outcomes over
|
||||
the period's window, which is the same UTC-aligned window `/api/stats`
|
||||
reports for that period. The outcome counts come from per-minute
|
||||
history in the query log, not from process-lifetime counters, so they
|
||||
scope to the period and survive a restart.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/Period"
|
||||
responses:
|
||||
"200":
|
||||
description: Per-upstream state and the availability rollup.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/UpstreamHealth"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"429":
|
||||
$ref: "#/components/responses/RateLimited"
|
||||
"500":
|
||||
$ref: "#/components/responses/Internal"
|
||||
"503":
|
||||
$ref: "#/components/responses/Unavailable"
|
||||
|
||||
/api/groups:
|
||||
get:
|
||||
summary: List groups
|
||||
@@ -1822,45 +1889,100 @@ components:
|
||||
|
||||
Health:
|
||||
type: object
|
||||
required: [status, disk, upstreams, diagnostics, queries_dropped, writer_failed, refreshes_gated, snapshot_generation]
|
||||
required: [status, protection, upstreams, query_history, diagnostics, disk]
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
enum: [ok, degraded]
|
||||
diagnostics:
|
||||
type: object
|
||||
required: [state, active_warnings, active_errors]
|
||||
properties:
|
||||
state:
|
||||
type: string
|
||||
enum: [recording, unavailable]
|
||||
description: unavailable when the event store failed to open or its writes are failing; either state degrades health.
|
||||
active_warnings: { type: integer }
|
||||
active_errors: { type: integer }
|
||||
disk:
|
||||
type: object
|
||||
required: [state, free_bytes, db_bytes, log_bytes, sample_failures]
|
||||
properties:
|
||||
state:
|
||||
type: string
|
||||
enum: [ok, warn, critical]
|
||||
free_bytes: { type: integer }
|
||||
db_bytes: { type: integer }
|
||||
log_bytes: { type: integer }
|
||||
sample_failures: { type: integer }
|
||||
protection:
|
||||
$ref: "#/components/schemas/HealthProtection"
|
||||
upstreams:
|
||||
type: object
|
||||
required: [available, total]
|
||||
properties:
|
||||
available: { type: integer }
|
||||
total: { type: integer }
|
||||
queries_dropped: { type: integer }
|
||||
writer_failed: { type: boolean }
|
||||
refreshes_gated: { type: integer }
|
||||
snapshot_generation:
|
||||
$ref: "#/components/schemas/HealthUpstreams"
|
||||
query_history:
|
||||
$ref: "#/components/schemas/HealthQueryHistory"
|
||||
diagnostics:
|
||||
$ref: "#/components/schemas/HealthDiagnostics"
|
||||
disk:
|
||||
$ref: "#/components/schemas/HealthDisk"
|
||||
|
||||
HealthProtection:
|
||||
type: object
|
||||
required: [state, until]
|
||||
properties:
|
||||
state:
|
||||
type: string
|
||||
enum: [active, paused, unavailable]
|
||||
description: >
|
||||
unavailable when no filter snapshot exists for the query path to
|
||||
evaluate against, which outranks any pause and is the only one of
|
||||
the three that degrades health. An expired timed pause is active.
|
||||
until:
|
||||
type: integer
|
||||
nullable: true
|
||||
description: Null until the first filter snapshot is published.
|
||||
description: >
|
||||
The second filtering resumes at. Null for an indefinite pause and
|
||||
for every state other than paused.
|
||||
|
||||
HealthUpstreams:
|
||||
type: object
|
||||
required: [state, available, total]
|
||||
properties:
|
||||
state:
|
||||
type: string
|
||||
enum: [ok, unavailable]
|
||||
description: unavailable exactly when `available` is 0; that degrades health.
|
||||
available: { type: integer }
|
||||
total:
|
||||
type: integer
|
||||
description: Enabled upstreams, which is what the routing pool is built from.
|
||||
|
||||
HealthQueryHistory:
|
||||
type: object
|
||||
required: [state, dropped_total, last_drop_s]
|
||||
properties:
|
||||
state:
|
||||
type: string
|
||||
enum: [recording, losing, failed]
|
||||
description: >
|
||||
failed when the query-log writer never started; losing while the
|
||||
disk gate is holding writes back and has already cost rows in the
|
||||
episode open now. Both degrade health. Drops from an earlier
|
||||
episode do not change the state - they are reported by the two
|
||||
fields below.
|
||||
dropped_total:
|
||||
type: integer
|
||||
description: Query rows lost since this process started, cumulative.
|
||||
last_drop_s:
|
||||
type: integer
|
||||
nullable: true
|
||||
description: >
|
||||
The newest drop, unix seconds; null until one happens. Stamped by a
|
||||
separate atomic from the count, so a non-zero `dropped_total` beside
|
||||
a null here is a legal momentary answer.
|
||||
|
||||
HealthDiagnostics:
|
||||
type: object
|
||||
required: [state, active_warnings, active_errors]
|
||||
properties:
|
||||
state:
|
||||
type: string
|
||||
enum: [recording, unavailable]
|
||||
description: unavailable when the event store failed to open or its writes are failing; either state degrades health.
|
||||
active_warnings: { type: integer }
|
||||
active_errors: { type: integer }
|
||||
|
||||
HealthDisk:
|
||||
type: object
|
||||
required: [state, free_bytes]
|
||||
properties:
|
||||
state:
|
||||
type: string
|
||||
enum: [ok, low, critical]
|
||||
description: >
|
||||
The disk monitor's own states; its `warn` is renamed `low` here,
|
||||
because `warn` reads as a log level rather than as a quantity of
|
||||
disk. Both `low` and `critical` degrade health.
|
||||
free_bytes: { type: integer }
|
||||
|
||||
Version:
|
||||
type: object
|
||||
@@ -2119,6 +2241,9 @@ components:
|
||||
- query_log.write
|
||||
- query_log.maintenance
|
||||
- query_log.recreated
|
||||
# Legacy: nothing emits this any more (milestone 30 deleted the
|
||||
# upstream-minute history subsystem), but stored rows survive and
|
||||
# the list endpoint passes their code through.
|
||||
- upstream_history.write
|
||||
- upstream.exchange
|
||||
- client_names.storage
|
||||
@@ -2187,7 +2312,7 @@ components:
|
||||
|
||||
StatsTotals:
|
||||
type: object
|
||||
required: [period, since, until, queries, blocked, cached, clients, avg_response_time_us, coverage]
|
||||
required: [period, since, until, queries, blocked, clients, avg_response_time_us, coverage]
|
||||
properties:
|
||||
period:
|
||||
type: string
|
||||
@@ -2200,7 +2325,6 @@ components:
|
||||
description: Window end, unix seconds, exclusive.
|
||||
queries: { type: integer }
|
||||
blocked: { type: integer }
|
||||
cached: { type: integer }
|
||||
clients:
|
||||
type: integer
|
||||
description: Distinct client addresses in the window.
|
||||
@@ -2239,6 +2363,106 @@ components:
|
||||
coverage:
|
||||
$ref: "#/components/schemas/Coverage"
|
||||
|
||||
TypeCount:
|
||||
type: object
|
||||
required: [qtype, count]
|
||||
properties:
|
||||
qtype:
|
||||
type: integer
|
||||
nullable: true
|
||||
description: |
|
||||
The numeric DNS type. Null is the group of logged queries that
|
||||
recorded no type, not an absent row.
|
||||
count: { type: integer }
|
||||
|
||||
StatsTypes:
|
||||
type: object
|
||||
required: [period, since, until, types, coverage]
|
||||
properties:
|
||||
period:
|
||||
type: string
|
||||
enum: [1h, 24h, 7d, 30d]
|
||||
since: { type: integer }
|
||||
until: { type: integer }
|
||||
types:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/TypeCount"
|
||||
coverage:
|
||||
$ref: "#/components/schemas/Coverage"
|
||||
|
||||
RouteCount:
|
||||
type: object
|
||||
required: [route, source, count]
|
||||
properties:
|
||||
route:
|
||||
$ref: "#/components/schemas/RouteKind"
|
||||
source:
|
||||
type: string
|
||||
nullable: true
|
||||
description: |
|
||||
The answering upstream's url or the forward zone, and null on every
|
||||
other route kind. Also null when an `upstream` or `forward_zone`
|
||||
row recorded no identity.
|
||||
count: { type: integer }
|
||||
|
||||
StatsRoutes:
|
||||
type: object
|
||||
required: [period, since, until, routes, coverage]
|
||||
properties:
|
||||
period:
|
||||
type: string
|
||||
enum: [1h, 24h, 7d, 30d]
|
||||
since: { type: integer }
|
||||
until: { type: integer }
|
||||
routes:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/RouteCount"
|
||||
coverage:
|
||||
$ref: "#/components/schemas/Coverage"
|
||||
|
||||
ClientSeries:
|
||||
type: object
|
||||
required: [client, buckets]
|
||||
properties:
|
||||
client:
|
||||
type: string
|
||||
description: The client address as the log recorded it, redaction included.
|
||||
buckets:
|
||||
type: array
|
||||
description: |
|
||||
One count per bucket in the window, zero-filled. Every series in a
|
||||
response has this same length, `other` included.
|
||||
items:
|
||||
type: integer
|
||||
|
||||
StatsClients:
|
||||
type: object
|
||||
required: [period, since, until, bucket_seconds, clients, other, coverage]
|
||||
properties:
|
||||
period:
|
||||
type: string
|
||||
enum: [1h, 24h, 7d, 30d]
|
||||
since: { type: integer }
|
||||
until: { type: integer }
|
||||
bucket_seconds: { type: integer }
|
||||
clients:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ClientSeries"
|
||||
other:
|
||||
type: array
|
||||
description: |
|
||||
Every client outside the named eight, summed per bucket. Always
|
||||
present, and always one entry per bucket in the window — including
|
||||
when `clients` is empty, when no client fell outside the named
|
||||
eight, and when the window holds no queries at all.
|
||||
items:
|
||||
type: integer
|
||||
coverage:
|
||||
$ref: "#/components/schemas/Coverage"
|
||||
|
||||
Lookup:
|
||||
type: object
|
||||
required: [domain, group_id, local_records, forward_zone, blocked, reason, matched, source_url, safe_search_rewrite]
|
||||
@@ -2267,72 +2491,6 @@ components:
|
||||
type: string
|
||||
nullable: true
|
||||
|
||||
UpstreamPeriodStats:
|
||||
type: object
|
||||
required: [attempts, successes, failures, success_rate, last_failure_at, last_failure_error]
|
||||
properties:
|
||||
attempts:
|
||||
type: integer
|
||||
description: Exchanges recorded against this upstream inside the window.
|
||||
successes: { type: integer }
|
||||
failures: { type: integer }
|
||||
success_rate:
|
||||
type: number
|
||||
nullable: true
|
||||
description: >
|
||||
`successes / attempts`, from 0 to 1. Null when `attempts` is 0: no
|
||||
observations is not perfect reliability.
|
||||
last_failure_at:
|
||||
type: integer
|
||||
nullable: true
|
||||
description: >
|
||||
The newest failure inside the window, unix seconds. Null when the
|
||||
window holds no failure, even if the upstream failed before it.
|
||||
last_failure_error:
|
||||
type: string
|
||||
nullable: true
|
||||
description: The error name belonging to `last_failure_at`; null exactly when it is.
|
||||
|
||||
UpstreamHealth:
|
||||
type: object
|
||||
required: [period, since, until, available, total, complete, upstreams]
|
||||
properties:
|
||||
period:
|
||||
type: string
|
||||
enum: [1h, 24h, 7d, 30d]
|
||||
since:
|
||||
type: integer
|
||||
description: Window start, unix seconds, inclusive.
|
||||
until:
|
||||
type: integer
|
||||
description: Window end, unix seconds, exclusive.
|
||||
available:
|
||||
type: integer
|
||||
description: How many upstreams the pool would route to right now.
|
||||
total: { type: integer }
|
||||
complete:
|
||||
type: boolean
|
||||
description: >
|
||||
No capacity drops known in this process within the selected window;
|
||||
up to about a minute of the newest outcomes may not have flushed
|
||||
yet, and outcomes lost in an unclean shutdown are not detectable.
|
||||
upstreams:
|
||||
type: array
|
||||
description: The upstreams configured now; a deleted upstream's history is not returned.
|
||||
items:
|
||||
type: object
|
||||
required: [url, enabled, available, period]
|
||||
properties:
|
||||
url: { type: string }
|
||||
enabled:
|
||||
type: boolean
|
||||
description: Live configuration, not history.
|
||||
available:
|
||||
type: boolean
|
||||
description: Live state, not history; false while the upstream is backing off.
|
||||
period:
|
||||
$ref: "#/components/schemas/UpstreamPeriodStats"
|
||||
|
||||
Group:
|
||||
type: object
|
||||
required: [id, name, safe_search]
|
||||
|
||||
Reference in New Issue
Block a user