Yesterday I said the whole art of observability is choosing what to record. Today I'm showing my work: the exact dashboard I built, panel by panel, and the reasoning behind each one.
This isn't a 40-panel monster. It's five panels, and each one answers a question you'd ask at 3am.
Panel 1: Is it slow?
TTFT and TBT, p50 and p95, over the last hour. Two time series, one panel. When a user says "it feels slow", this is the first place you look.
histogram_quantile(0.95, rate(vllm:time_to_first_token_seconds_bucket[5m]))
histogram_quantile(0.50, rate(vllm:time_to_first_token_seconds_bucket[5m]))
histogram_quantile(0.95, rate(vllm:time_per_output_token_seconds_bucket[5m]))
If TTFT is high but TBT is fine, it's a prefill problem. If both are high, it's overload. If TBT is jittery, it's scheduling or contention.
Panel 2: Is it overloaded?
Queue depth and KV cache usage, stacked. Queue depth is the leading indicator. KV cache usage is the hard ceiling: when it hits 100%, requests get evicted or rejected.
vllm:num_requests_waiting
vllm:gpu_cache_usage_perc * 100
If queue depth is climbing but cache usage is fine, you have a scheduling problem. If cache usage is pinned at 100%, you have a memory problem.
Panel 3: Is it the GPU?
GPU utilization and memory, from the DCGM exporter. The trap here is pairing them: 90% util with 30% memory means compute-bound. 90% util with 95% memory means you're about to OOM. Same number, different diagnosis.
DCGM_FI_DEV_GPU_UTIL
DCGM_FI_DEV_MEM_COPY_UTIL
Panel 4: Is it the network?
Client-side latency and error rate. This is the panel that saves you from blaming the GPU for a network problem. If the engine is fast but users are slow, it's not the engine.
rate(http_requests_total{status=~"5.."}[5m])
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))
Panel 5: Is it the model?
Token throughput and KV cache hit rate. This is the "is the model doing what we expect" panel. A sudden drop in throughput with steady utilization usually means the model got slower, not the system.
rate(vllm:generation_tokens_total[5m])
vllm:prefix_cache_hit_rate
A good dashboard is like a pilot's instrument panel: five gauges, each with a red zone, and a trained eye that knows which gauge to trust when they disagree.
The workflow
Here's how the dashboard actually gets used in a real incident:
- Alerts page at 2am: TTFT p95 over 2s.
- Panel 1 confirms: TTFT is high, TBT is fine.
- Panel 2: queue depth is climbing, cache is fine.
- Panel 3: GPU util is 95%, memory is 40%.
- Diagnosis: a traffic spike is overloading prefill. The autoscaler is reacting, but it needs 3 minutes to boot a replica.
- Action: nothing to fix, it's a transient spike. But now you know to add a warm replica for this traffic pattern.
Five panels, one diagnosis, no guessing.
The takeaway
A dashboard isn't a collection of charts. It's a decision tool. If a panel doesn't help you decide, it doesn't belong.
Tomorrow: tracing a single request through the whole stack with OpenTelemetry.