Part of #4325. Sequence against #4330 (observability stack migration/reset decision) — don't build a new dashboard twice.
Context
The self-host GPU host has zero GPU metrics collection today — no exporter, nothing feeding Prometheus, and no Grafana dashboard among the existing set (claude-usage, codex-usage, github-prs, gittensory, maintainer-reviews, orb-ai-usage, resource-hub) is GPU-specific. Separately, there is no application-level latency metric for embed/vision AI provider calls at all — src/selfhost/metrics.ts's DEFAULT_METRIC_META has no ai_* histogram, and src/selfhost/ai.ts only calls incr() for a couple of counters, never observe(). Without that, a GPU dashboard can only show raw utilization/VRAM — not the thing that actually matters (is a request slow, is it failing).
Requirements
- Exporter: add
utkuozdemir/nvidia_gpu_exporter:1.3.2 (lightweight, shells to nvidia-smi via the NVIDIA Container Toolkit; DCGM is the wrong fit for a consumer/workstation-class GPU) as a new docker-compose.yml service, own --profile gpu, matching the ollama/postgres-exporter service style (<<: *default-logging, expose not ports).
- Prometheus: new
gpu scrape job in prometheus/prometheus.yml, matching the existing profile-conditional job pattern.
- New application metric: add
gittensory_ai_provider_request_duration_seconds (histogram) and gittensory_ai_provider_request_errors_total (counter) to src/selfhost/metrics.ts's DEFAULT_METRIC_META, labeled by provider and request_kind (the existing requestKind() helper in src/selfhost/ai.ts:1065 already classifies each call as "embedding" | "review" — extend it to also cover vision/advisory once those exist). Wrap the call site around withReviewSpan in ai.ts (~line 1086).
- Grafana: new
grafana/dashboards/gpu-metrics.json, auto-provisioned via the existing grafana/provisioning/dashboards/provider.yml file-provider convention. Panels: Summary row (utilization %, VRAM used/total, temp, power as stat panels) + AI request performance row (request rate, p50/p95/p99 latency via histogram_quantile, error rate — all keyed off the new metric above) + a GPU utilization/VRAM/temp/power timeseries row. Match the existing dashboards' JSON panel style (see resource-hub.json/claude-usage.json).
Deliverables
Expected outcome
A durable, always-on view of GPU utilization/VRAM/temp alongside real AI-request latency and error rate — the baseline this project needs before any of the other GPU-optimization work (embedding cache, advisory routing, vision-verify) can be measured for real impact rather than one-off manual benchmarks.
Effort
M
Part of #4325. Sequence against #4330 (observability stack migration/reset decision) — don't build a new dashboard twice.
Context
The self-host GPU host has zero GPU metrics collection today — no exporter, nothing feeding Prometheus, and no Grafana dashboard among the existing set (
claude-usage,codex-usage,github-prs,gittensory,maintainer-reviews,orb-ai-usage,resource-hub) is GPU-specific. Separately, there is no application-level latency metric for embed/vision AI provider calls at all —src/selfhost/metrics.ts'sDEFAULT_METRIC_METAhas noai_*histogram, andsrc/selfhost/ai.tsonly callsincr()for a couple of counters, neverobserve(). Without that, a GPU dashboard can only show raw utilization/VRAM — not the thing that actually matters (is a request slow, is it failing).Requirements
utkuozdemir/nvidia_gpu_exporter:1.3.2(lightweight, shells tonvidia-smivia the NVIDIA Container Toolkit; DCGM is the wrong fit for a consumer/workstation-class GPU) as a newdocker-compose.ymlservice, own--profile gpu, matching theollama/postgres-exporterservice style (<<: *default-logging,exposenotports).gpuscrape job inprometheus/prometheus.yml, matching the existing profile-conditional job pattern.gittensory_ai_provider_request_duration_seconds(histogram) andgittensory_ai_provider_request_errors_total(counter) tosrc/selfhost/metrics.ts'sDEFAULT_METRIC_META, labeled byproviderandrequest_kind(the existingrequestKind()helper insrc/selfhost/ai.ts:1065already classifies each call as"embedding" | "review"— extend it to also cover vision/advisory once those exist). Wrap the call site aroundwithReviewSpaninai.ts(~line 1086).grafana/dashboards/gpu-metrics.json, auto-provisioned via the existinggrafana/provisioning/dashboards/provider.ymlfile-provider convention. Panels: Summary row (utilization %, VRAM used/total, temp, power asstatpanels) + AI request performance row (request rate, p50/p95/p99 latency viahistogram_quantile, error rate — all keyed off the new metric above) + a GPU utilization/VRAM/temp/powertimeseriesrow. Match the existing dashboards' JSON panel style (seeresource-hub.json/claude-usage.json).Deliverables
gpu-exporterservice indocker-compose.yml,--profile gpusrc/selfhost/ai.ts, testedgrafana/dashboards/gpu-metrics.jsonprovisioned and rendering real data.env.exampledocuments any new varsExpected outcome
A durable, always-on view of GPU utilization/VRAM/temp alongside real AI-request latency and error rate — the baseline this project needs before any of the other GPU-optimization work (embedding cache, advisory routing, vision-verify) can be measured for real impact rather than one-off manual benchmarks.
Effort
M