fix(frank-zealot): serve /metrics so Prometheus can see the endpoint

The scrape has never worked: lastError is "server returned HTTP status 501 Not
Implemented" on http://host.containers.internal:7771/metrics, and the oldest
retained sample of up{job="frank-zealot"} (2026-10-11) is already 0 -- the target
has been red across four payload generations while /health answered 200 the whole
time, keeping the critical LlmEndpointDown alert permanently active. llama.cpp
gates /metrics behind --metrics; halogen serves it natively, which is why the
asymmetry looked like a 3090-only problem.

METRICS=1 in the shipped env, --metrics reaches the argv through EXTRA_ARGS, and
the seam asserts both directions (default ships the flag, METRICS=0 strips it).
Diagnosed via /api/v1/targets lastError rather than the alert list, which is also
the lesson: Alertmanager here has no persistence, so a reboot re-stamps startsAt
and a weeks-old fault looks fresh.
This commit is contained in:
Frank committed 2026-10-18 01:52:19 -07:00
1 parent 34a30d8505
commit a4fe5f6839
6 files changed
+75 -1

No files matched your search

+12 -1
View File
@@ -153,7 +153,9 @@ alertmanager.container`, loopback :9093) → host-side Signal relay
IP, the one bind a container can reach as `host.containers.internal`; IP, the one bind a container can reach as `host.containers.internal`;
iptables admits loopback, the host and the podman bridge subnet only) → signal-cli account 318311. iptables admits loopback, the host and the podman bridge subnet only) → signal-cli account 318311.
Prometheus targets read down for structural reasons (HTML-scraped "probes", Prometheus targets read down for structural reasons (HTML-scraped "probes",
Gitea metrics off); the remedy is a blackbox exporter. See Gitea metrics off); the remedy is a blackbox exporter. `frank-zealot` was down for
a different reason until 2026-10-18: llama.cpp will not serve `/metrics` at all
without `--metrics`. See
`serverAppConfigs-docs/Infrastructure/Observability.md`. `serverAppConfigs-docs/Infrastructure/Observability.md`.
## Testing ## Testing
@@ -1091,6 +1093,15 @@ linked runbooks.
B/token (the `GGML_CUDA_FA_ALL_QUANTS` door), so a build decision must be B/token (the `GGML_CUDA_FA_ALL_QUANTS` door), so a build decision must be
proven by the KV buffer line in the boot ledger, not by the build flags you proven by the KV buffer line in the boot ledger, not by the build flags you
remember passing. remember passing.
- **A llama.cpp endpoint is invisible to Prometheus unless `--metrics` is passed.**
Without it the engine answers **501 Not Implemented** on `/metrics`, so the scrape
fails while `/health` answers 200 and `LlmEndpointDown` fires beside a healthy
service — that is how `frank-zealot` sat red across four payload generations
(the oldest retained sample of `up{job="frank-zealot"}`, 2026-10-11, is already
0). halogen serves `/metrics` natively, so the asymmetry reads as "only the 3090
is broken". Diagnose with `lastError` from `/api/v1/targets`, not the alert list,
and note Alertmanager has no persistence: a reboot re-stamps `startsAt` on an
alert that has been firing for weeks.
**Ops** **Ops**
+9
View File
@@ -70,3 +70,12 @@ NGL=99
# and graph capture land in the journal, which is what the VRAM-fit forensics # and graph capture land in the journal, which is what the VRAM-fit forensics
# read. Levels above 4 flood the journal. # read. Levels above 4 flood the journal.
VERBOSITY=4 VERBOSITY=4
# Serve /metrics. llama.cpp answers 501 without --metrics, and Dankclaw's
# Prometheus scrapes /metrics on every fleet endpoint — a 501 reads as DOWN, so
# this service has held the critical LlmEndpointDown alert for as long as the
# metric has history (verified 2026-10-18: the oldest retained sample of
# up{job="frank-zealot"}, 2026-10-11, is already 0; scrapeUrl
# http://host.containers.internal:7771/metrics, lastError "server returned HTTP
# status 501 Not Implemented"). 0 opts out and re-breaks the scrape.
METRICS=1
+15
View File
@@ -78,6 +78,15 @@ fi
# 4 adds debug detail (KV layout, buffer sizes, graph capture) which is what # 4 adds debug detail (KV layout, buffer sizes, graph capture) which is what
# the VRAM-fit forensics need. Levels above 4 flood the journal. # the VRAM-fit forensics need. Levels above 4 flood the journal.
: "${VERBOSITY:=3}" : "${VERBOSITY:=3}"
# /metrics needs the flag: llama.cpp returns 501 unless the server starts with
# --metrics, and Dankclaw's Prometheus scrapes every fleet endpoint's /metrics —
# a 501 reads as DOWN, which is how the critical LlmEndpointDown alert ended up
# firing for this service ever since the metric has history at all — the oldest
# retained sample of up{job="frank-zealot"} (2026-10-11) is already 0, so this
# target has never been scraped successfully, across every payload era. The
# counters cost nothing on the request path;
# METRICS=0 is the documented opt-out.
: "${METRICS:=1}"
# Vision projector. No colon on purpose: an explicitly empty value means # Vision projector. No colon on purpose: an explicitly empty value means
# "text-only, on purpose" and must NOT be replaced by the default path. # "text-only, on purpose" and must NOT be replaced by the default path.
: "${MMPROJ_FILE=/home/dank/models/Bonsai2-PQ2_0/Ternary-Bonsai-2-27B-mmproj-Q8_0.gguf}" : "${MMPROJ_FILE=/home/dank/models/Bonsai2-PQ2_0/Ternary-Bonsai-2-27B-mmproj-Q8_0.gguf}"
@@ -94,6 +103,12 @@ if [[ -n "$MMPROJ_FILE" ]]; then
EXTRA_ARGS+=(--mmproj "$MMPROJ_FILE") EXTRA_ARGS+=(--mmproj "$MMPROJ_FILE")
fi fi
# Prometheus scraping is opt-IN in llama.cpp: without --metrics the server
# answers 501 on /metrics, and the fleet scrape then reports the endpoint DOWN.
if [[ "$METRICS" = "1" ]]; then
EXTRA_ARGS+=(--metrics)
fi
# Speculative flags move as a set: they reach the argv ONLY when a drafter # Speculative flags move as a set: they reach the argv ONLY when a drafter
# is named. SPEC_TYPE=none (the shipped contract) strips both — a no-spec # is named. SPEC_TYPE=none (the shipped contract) strips both — a no-spec
# baseline must not carry half a spec pair. # baseline must not carry half a spec pair.
@@ -87,6 +87,21 @@ via `host.containers.internal`) → signal-cli. Some targets read down for
structural reasons (HTML-scraped "probes", Gitea metrics off); the remedy is structural reasons (HTML-scraped "probes", Gitea metrics off); the remedy is
the blackbox exporter. See [[Observability]]. the blackbox exporter. See [[Observability]].
**A llama.cpp endpoint is invisible unless it opts in.** `up{job="frank-zealot"}`
has been 0 for the entire retained history of the metric — the oldest sample,
2026-10-11, is already 0 — while the service itself answered `/health` 200 the
whole time. Cause: llama.cpp answers **501 Not Implemented** on `/metrics` unless
the server is started with `--metrics`, so the scrape was refused at the protocol
layer and `LlmEndpointDown` (critical) sat active across four payload generations.
Fixed 2026-10-18 by the `METRICS=1` knob in `scripts/frank-zealot.env`. Two laws
fall out of it: a target can read down beside a healthy service, so read
`lastError` from `/api/v1/targets` before believing an outage; and Alertmanager
runs here without persistence, so every restart re-stamps `startsAt` on a
long-standing alert — a "fresh" alert after a reboot is not evidence of a fresh
fault. Remaining known downs are the structural ones above (HTML-scraped probes,
Gitea metrics, ComfyUI), and halogen serves `/metrics` natively, which is why
`frank-dragoon` has always been green.
## Related ## Related
- [[Observability]] — stack topology and the alert delivery chain - [[Observability]] — stack topology and the alert delivery chain
@@ -143,6 +143,13 @@ ladder above carries to the shipped shape.
`BIND_ADDR`, `NGL`. An invented `KV_CACHE_TYPE` is silently swallowed by the `BIND_ADDR`, `NGL`. An invented `KV_CACHE_TYPE` is silently swallowed by the
script's `: "${CACHE_TYPE_K:=f16}"` default and the arm boots **f16**, which script's `: "${CACHE_TYPE_K:=f16}"` default and the arm boots **f16**, which
for this payload is an OOM that looks like a model problem. for this payload is an OOM that looks like a model problem.
- **`/metrics` needs `--metrics` (fixed 2026-10-18):** the fork answers **501 Not
Implemented** without the flag, and Dankclaw's Prometheus scrapes `/metrics`, so
`up{job="frank-zealot"}` has been 0 for the whole retained metric history — the
oldest sample (2026-10-11) is already 0 — while the endpoint itself answered
`/health` 200 the entire time. `METRICS=1` in `scripts/frank-zealot.env` now
ships the flag and the seam asserts it reaches the argv. halogen serves
`/metrics` natively, which is why that target was always green.
- **Base alignment:** Bonsai-2 is the BASE (censored) Qwen3.8-27B lineage, so a - **Base alignment:** Bonsai-2 is the BASE (censored) Qwen3.8-27B lineage, so a
refusal is possible on a compaction or subagent request. The CRACK re-export refusal is possible on a compaction or subagent request. The CRACK re-export
(`dealignai/Bonsai-2-27B-Ternary-CRACK-GGUF`, already on disk at (`dealignai/Bonsai-2-27B-Ternary-CRACK-GGUF`, already on disk at
+17
View File
@@ -63,6 +63,7 @@ CACHE_TYPE_V=$(sed -nE 's/^CACHE_TYPE_V=(.+)$/\1/p' "$ENV_FILE")
SPEC_TYPE=$(sed -nE 's/^SPEC_TYPE=(.+)$/\1/p' "$ENV_FILE") SPEC_TYPE=$(sed -nE 's/^SPEC_TYPE=(.+)$/\1/p' "$ENV_FILE")
SPEC_DRAFT_N_MAX=$(sed -nE 's/^SPEC_DRAFT_N_MAX=([0-9]+)$/\1/p' "$ENV_FILE") SPEC_DRAFT_N_MAX=$(sed -nE 's/^SPEC_DRAFT_N_MAX=([0-9]+)$/\1/p' "$ENV_FILE")
VERBOSITY=$(sed -nE 's/^VERBOSITY=([0-9]+)$/\1/p' "$ENV_FILE") VERBOSITY=$(sed -nE 's/^VERBOSITY=([0-9]+)$/\1/p' "$ENV_FILE")
METRICS=$(sed -nE 's/^METRICS=([0-9]+)$/\1/p' "$ENV_FILE")
NGL=$(sed -nE 's/^NGL=([0-9]+)$/\1/p' "$ENV_FILE") NGL=$(sed -nE 's/^NGL=([0-9]+)$/\1/p' "$ENV_FILE")
EOF EOF
run env FRANK_ZEALOT_ENV_FILE="$envf" nvidia-modprobe=true bash "$SERVE" run env FRANK_ZEALOT_ENV_FILE="$envf" nvidia-modprobe=true bash "$SERVE"
@@ -176,6 +177,22 @@ run_stage() {
[ "$(argv_value --log-verbosity)" = "3" ] [ "$(argv_value --log-verbosity)" = "3" ]
} }
@test "the Prometheus metrics endpoint is served unless opted out" {
# Output contract, not a config read: what matters to the operator is that
# the engine is exec'd with --metrics, because llama.cpp answers 501 on
# /metrics without it and the fleet scrape then reports the endpoint DOWN
# (the oldest retained sample of this target's scrape, 2026-10-11, is a 0).
serve_argv
[ "$status" -eq 0 ]
grep -qxF -- '--metrics' <<<"$output"
# And the opt-out must be real: METRICS=0 ships no /metrics at all.
sed -i 's/^METRICS=.*/METRICS=0/' "$STAGE/test.env"
run_stage
[ "$status" -eq 0 ]
! grep -qxF -- '--metrics' <<<"$output"
}
@test "the compute-buffer pair follows the env file and stays self-consistent" { @test "the compute-buffer pair follows the env file and stays self-consistent" {
# The contract is: whatever rung the repo carries is the rung the engine # The contract is: whatever rung the repo carries is the rung the engine
# is exec'd with — the number a benchmark attributes a prefill rate to # is exec'd with — the number a benchmark attributes a prefill rate to