skills/grafana-dashboard-builder/SKILL.md
Use when building Grafana dashboards backed by Prometheus, Loki, or Tempo, designing PromQL/LogQL queries, wiring template variables, setting alert rules, building SLO dashboards, or maintaining dashboards as code. Triggers: rate() vs increase() confusion, irate vs rate, label_replace, recording rules, alerting rule expressions, multi-dimensional template variables, ad-hoc filters, dashboard JSON model, provisioning via Terraform/grafonnet, p99 / histogram_quantile usage. NOT for Datadog/New Relic dashboards (vendor-specific), Grafana plugin development, or Loki ingestion pipeline tuning.
npx skillsauth add curiositech/windags-skills grafana-dashboard-builderInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
A good dashboard answers one question per panel and one big question per dashboard. PromQL is more expressive than most engineers use; the recurring traps are rate() vs increase(), label cardinality, and histogram quantile math.
Jump to your fire:
rate vs increase vs iratele# Per-second request rate over 5min window.
rate(http_requests_total[5m])
# Total requests over 5min.
increase(http_requests_total[5m])
# By status code.
sum by (status) (rate(http_requests_total[5m]))
# Error rate (ratio).
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
# Latency p99 from a histogram metric.
histogram_quantile(0.99,
sum by (le) (rate(http_request_duration_seconds_bucket[5m]))
)
rate vs increase vs irate| Function | Returns | Use for |
|----------|---------|---------|
| rate(m[5m]) | Avg per-second rate over window | Most graphs and alerts. Smooth. |
| irate(m[5m]) | Instantaneous rate from last 2 samples | Sparkline-style live views. Spiky. |
| increase(m[5m]) | Total delta over window | "How many requests in 5min." Same as rate * window_seconds. |
For alerts, prefer rate over irate — irate over a noisy counter triggers on every blip.
leHistogram metrics emit _bucket{le="..."}, _sum, _count. To compute quantiles:
histogram_quantile(0.99,
sum by (le, route) (rate(http_request_duration_seconds_bucket[5m]))
)
Aggregate by le AND any dimensions you want to keep in the result. Forgetting le returns NaN.
For p99 of all requests across routes:
histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))
label_replace and renaming# Add a `service` label derived from `job`.
label_replace(up, "service", "$1", "job", "(.+)")
# Drop high-cardinality labels for graphing.
sum without (instance, pod) (rate(http_requests_total[5m]))
without is the cleaner inverse of by — sums everything except the listed labels.
For expensive queries used on many dashboards, pre-compute:
# /etc/prometheus/rules/recording.yml
groups:
- name: orders-api
interval: 30s
rules:
- record: job:http_requests:rate5m
expr: sum by (job, status) (rate(http_requests_total[5m]))
- record: job:http_request_duration:p99
expr: histogram_quantile(0.99, sum by (job, le) (rate(http_request_duration_seconds_bucket[5m])))
Now dashboards query job:http_request_duration:p99 instead of recomputing. Cuts dashboard load time and Prometheus CPU.
groups:
- name: orders-api-alerts
rules:
- alert: HighErrorRate
expr: |
sum(rate(http_requests_total{job="orders-api",status=~"5.."}[5m]))
/
sum(rate(http_requests_total{job="orders-api"}[5m]))
> 0.05
for: 10m
labels: { severity: page, team: orders }
annotations:
summary: "Error rate >5% on orders-api"
runbook: "https://runbooks/orders-api/high-error-rate"
for: 10m is the dwell time — alert only fires after the condition is true for 10 contiguous minutes. Without it, every transient blip pages.
# Last 5 minutes of error logs.
{service="orders-api"} |= "level=error"
# Parse and filter on a JSON field.
{service="orders-api"} | json | status >= 500
# Rate of errors.
sum by (service) (rate({service="orders-api"} |= "level=error" [5m]))
# Latency from a structured field.
{service="orders-api"} | json | unwrap duration_ms | quantile_over_time(0.99, [5m])
LogQL extends PromQL with |= (contains), !=, |~ (regex), !~, | json, | logfmt, | unwrap.
Variable: service
Type: query
Query: label_values(up, job)
Variable: instance
Type: query
Query: label_values(up{job="$service"}, instance)
$service lets the user pick. Multi-value dropdowns + Include All cover the common cases.
For ad-hoc filters, use Grafana's "Ad hoc filters" variable type — adds a label filter applied to every panel.
A SLO dashboard typically has:
Avoid: pie charts (always wrong), gauges with no comparison, tables of 50+ rows. One question per panel.
Terraform:
resource "grafana_dashboard" "orders_api" {
config_json = file("${path.module}/dashboards/orders-api.json")
folder = grafana_folder.api.id
overwrite = true
}
Or grafonnet (Jsonnet) for templated dashboards across services. The point is: dashboards are reviewable, diff-able, and recoverable.
Mark deploys, incidents, and feature flags on graphs:
# Annotation query — events from a Prometheus metric.
deployment_event{service="orders-api"}
Or use Grafana's annotation API to push events from CI.
rate() over a non-counterSymptom: Negative rates, weird step changes.
Diagnosis: rate() only makes sense on monotonically-increasing counters. Applying to a gauge gives garbage.
Fix: delta() for gauges, rate() for counters. Use the right one.
irate in alertsSymptom: Pager fires from a single noisy blip every few hours.
Diagnosis: irate reflects the last two samples; one bad sample triggers.
Fix: rate(...)[Nm] smoothed over minutes; combine with for: Xm.
by (le)Symptom: Panel shows NaN.
Diagnosis: histogram_quantile needs the le label preserved through aggregation.
Fix: sum by (le, …) (rate(..._bucket[5m])).
Symptom: "Include All" on a 5000-instance variable returns 5000 series.
Diagnosis: Multi-value variables with too-broad allowance.
Fix: Limit values, scope by another variable, or use regex to whittle. Aggregate before display.
for: too shortSymptom: Pager fatigue from intermittent network hiccups.
Diagnosis: for: 1m fires on any blip.
Fix: for: 5m or for: 10m for SLO-tier alerts. Use for: 0 only for hard-failure metrics ("service down").
Symptom: Slow load, no one reads past the top row. Diagnosis: "Add panel" reflex. Fix: Divide into multiple focused dashboards: SLO, saturation, dependencies, debugging. Cross-link.
for: dwell time and a runbook URL.rate vs increase vs irate correctness.le.structured-logging-design for the producer-side schema.opentelemetry-instrumentation for span/trace generation.opentelemetry-instrumentation for instrumentation patterns.data-ai
license: Apache-2.0 NOT for unrelated tasks outside this domain.
development
Use when designing caching strategies (cache-aside, write-through, write-behind), implementing distributed locks, building rate limiters, leaderboards, real-time streams (XADD/consumer groups), pub/sub, or tuning eviction policies. Triggers: thundering-herd on cache miss, dogpile on key expiry, Redlock vs SET-NX-PX choice, sliding-window rate limiter, hot-key on a single cluster slot, big-key blowup, MULTI/EXEC across slots, KEYS in production. NOT for Redis Cluster operations/admin (different domain), embedded KV (SQLite, leveldb), in-process LRU caches, or Memcached.
tools
Drawing the `'use client'` boundary correctly in React Server Components apps (Next.js App Router, RSC frameworks) — leaf-pushing, slot composition, serialization rules, and environment poisoning prevention. Grounded in react.dev and Next.js 16 docs.
development
Use when designing rate limiting for an API, choosing between token bucket / sliding window / leaky bucket / fixed window, implementing it in Redis, deciding edge (Cloudflare/Upstash) vs origin enforcement, sizing per-user vs per-IP vs per-endpoint quotas, returning the right 429 response with Retry-After, or fixing the boundary-burst bug in fixed-window limiters. Triggers: 429 too many requests, INCR + EXPIRE, ZADD + ZREMRANGEBYSCORE + ZCARD, X-RateLimit-Remaining header, Cloudflare WAF rate limiting rules, Upstash @upstash/ratelimit, leaky bucket shaping vs policing, distributed rate limiter consistency. NOT for DDoS mitigation specifically (different scale), CAPTCHA / bot management, full WAF design, or per-user quota billing.