Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Design Grafana dashboards engineers actually use under pressure at 3am, not pretty dashboards that look good in a vendor pitch. Covers USE / RED / Four Golden Signals layouts, panel type selection (time series vs gauge vs stat vs table vs heatmap vs state-timeline), variable templating with cascading and multi-value patterns, drill-down links between dashboards, exemplar trace linking from Prometheus to Tempo, mixed-datasource queries (Prom + Loki + Tempo + Pyroscope), transformation rules, aler
.claude/skills/grafana-panel-engineer/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-13 | ✗→✓ | ▲ Improved | — | — |
| case-02 | ✓→✓ | = Same ✓ | — | — |
| case-09 | ✓→✓ | = Same ✓ | — | — |
| case-05 | ✓→✓ | = Same ✓ | — | — |
| case-21 | ✗→✗ | = Same ✗ | — | — |
Design Grafana dashboards that load fast, answer the right question in under 30 seconds, and survive being looked at by a sleepy on-call engineer on a phone. Acts as a senior SRE who has built and pruned thousands of dashboards across Prometheus, Loki, Tempo, Pyroscope, Mimir, InfluxDB, and CloudWatch — and knows which dashboard layouts the rotation actually opens during incidents (RED for service health, USE for capacity, customer journey for product-tier impact).
This skill builds dashboards. It does not write your detection logic, define your SLOs, or replace your APM. It assumes data is already flowing into a Grafana datasource; the job is panel selection, layout, variables, drill-downs, exemplar linking, and query optimization. Output is dashboard JSON (importable, version-controlled, ideally as Grafana provisioning YAML or Terraform grafana_dashboard), plus a panel-design playbook for new services.
Invoke when:
Basic invocations: > Build a dashboard pack for our new T0 checkout service > Audit our 1,200 dashboards and prune the unused ones > Wire Prometheus exemplars to Tempo traces in our HTTP latency panel > Convert our service's dashboard from USE to RED + Four Golden Signals > Optimize this dashboard — it takes 90s to load and hangs the browser
GET /api/search?type=dash-db)service_name_thing_total, etc.)GET /api/search, GET /api/dashboards/uid/{uid}. Tag each with: last viewed (Grafana usage analytics), data sources used, panel count, owner. Anything not viewed in 90 days and not the only copy of its data is a deletion candidate.Multi-value and Include All carefully — they explode query cardinality. See Variable Templating Patterns.rate() for counters, histogram_quantile() for histograms, irate() only for short-window debug. Set query interval to match Prometheus scrape ($__rate_interval), not the dashboard refresh.${__value.raw} as the trace ID.topk, bottomk), set max points (Max data points panel option), use recording rules for expensive queries, push aggregation to Prometheus where possible. See Performance Optimization.For resources: CPU, memory, disk, network, file handles. Three panels per resource.
[ Utilization % ] [ Saturation (queue depth, runqueue) ] [ Errors / sec ]Layout: one row per resource (CPU row, Memory row, Disk row, Network row). Each row has 3 panels in U/S/E order. Time series, stacked by host or pod, top-10 by topk(10, ...).
When to use: capacity / fleet health dashboards. Not for service health (use RED).
For services: Rate (req/s), Errors (errors/s or %), Duration (p50/p95/p99).
Row 1 — Top-line:
[ Rate (req/s) ] [ Error rate (%) ] [ p99 Duration (ms) ]
Row 2 — Per-endpoint:
[ Rate by endpoint (stacked) ] [ Error rate by endpoint (table top-10) ] [ Duration heatmap ]
Row 3 — Per-status:
[ Status code breakdown (stacked area) ] [ 5xx by endpoint ] [ Slow endpoints (table) ]When to use: any request-driven service (HTTP, gRPC, message queue consumer). Default for T0/T1 services.
Latency, Traffic, Errors, Saturation. RED + saturation.
Row 1: [ Traffic (req/s) ] [ Errors (%) ] [ Latency p99 ] [ Saturation (CPU/mem) ]
Row 2: [ Traffic by version (deploy overlay) ] [ Errors by error class ] [ Latency heatmap with exemplars ] [ Saturation forecast ]
Row 3: [ Throughput vs latency scatter ] [ Top errors (table) ] [ Slow queries (table from logs) ] [ Capacity headroom % ]When to use: full service health for T0 services. Most flexible single dashboard.
For end-to-end product flows (signup, checkout, search-to-purchase). Each panel = one step in the funnel.
Row 1: [ Step 1 success% ] [ Step 2 success% ] [ Step 3 success% ] [ Step N success% ]
Row 2: [ Step 1 latency p99 ] [ Step 2 latency p99 ] ...
Row 3: [ Drop-off Sankey or stacked bar ] [ Bottleneck step over time ]
Row 4: [ Per-segment funnel (enterprise vs free) ] [ Errors at top drop-off step ]When to use: product-tier dashboards, exec dashboards, SRE dashboards for revenue-path services.
Row 1 (top-line): [ Current SLO % ] [ Error budget remaining % ] [ Burn rate now (1h) ] [ Burn rate now (6h) ]
Row 2: [ SLO over time (30d) ] [ Error budget burndown ] [ Bad events (errors+slow) over time ]
Row 3: [ Burn rate alerts (firing now) ] [ SLO violations by reason ] [ Reliability incident annotations ]When to use: dedicated SLO dashboard, one per critical service. Linked from RED dashboard.
Row 1: [ Service health stat ] [ Recent deploys (annotation table) ] [ Active incidents ]
Row 2: [ Logs panel (Loki, last 15m, error level) ]
Row 3: [ Traces panel (Tempo, slowest 10) ] [ Profile flame graph (Pyroscope, last 5m) ]
Row 4: [ Top errors by message (table) ] [ Top slow endpoints (table) ]When to use: opened during an active incident. Refresh = off (you want to study a frozen window). Time range = "last 1 hour" with a quick-pick to "since incident start."
What does the panel show?
├── A value over time
│ ├── Multiple series, comparing trends → Time series (line)
│ ├── Stacked composition (sums to a total) → Time series (stacked area)
│ ├── Distribution at each timestamp → Heatmap
│ └── Discrete state changes → State timeline
│
├── A current value (single number)
│ ├── Just the number, with trend sparkline → Stat
│ ├── % of a maximum (CPU, disk) → Gauge
│ ├── A categorical status (UP/DOWN/DEGRADED) → Stat (with thresholds for color)
│ └── A list of values (status per host) → Bar gauge or stat-list
│
├── Top-N (top errors, slowest queries, biggest pods)
│ ├── With multiple columns → Table
│ ├── With one ranked metric → Bar gauge
│ └── With log lines → Logs panel
│
├── Distribution (histogram of latency, request size)
│ ├── At one moment → Histogram
│ └── Over time → Heatmap (with exemplars if available)
│
├── Geographic (per-region, per-country)
│ └── Geomap
│
├── Logs → Logs panel (Loki)
├── Traces → Traces panel (Tempo)
└── Profiles → Flame graph (Pyroscope)Rules of thumb:
Variables drive reusability. Done badly, they break dashboards or explode cost.
Each variable depends on the one above. Picking cluster filters namespace, picking namespace filters service.
$env = label_values(up, env)
$cluster = label_values(up{env="$env"}, cluster)
$namespace = label_values(up{env="$env",cluster="$cluster"}, namespace)
$service = label_values(up{env="$env",cluster="$cluster",namespace="$namespace"}, service)Tip: set "Refresh = On Time Range Change" so variable values update if the time range moves. Without it, deleted services stay in the dropdown.
Multi-value lets users select multiple services; Include All adds an "all" option. The catch: "all" generates a giant regex that bombs PromQL.
Variable: $service
Type: query
Query: label_values(http_requests_total{env="$env"}, service)
Multi-value: yes
Include All: yes
Custom all value: .*
Sort: Alphabetical (asc)Tip: in the panel query, use service=~"$service" (regex match, not equality). Custom all value .* matches everything without bombing the parser.
If service has 5,000 values, the dropdown is unusable. Bound it:
Variable: $service
Query: topk(50, sum by (service) (rate(http_requests_total[5m])))
Refresh: On time range changeTop 50 by traffic. Engineers care about the busy ones; the long tail goes to a separate "all services" debug dashboard.
Variable: $window
Type: custom
Values: 1m,5m,15m,1h,6h,1d
Default: 5mUsed in queries: rate(http_requests_total[$window]). User toggles via dropdown.
Variable: $datasource
Type: datasource
Query: prometheusLets the dashboard work across multiple Prometheus instances (region-specific). Panel datasource is $datasource. Useful for global dashboards that drill into per-region data.
Variable: $interval
Type: interval
Values: 1m, 5m, 1h, 6h, 1d
Auto Option: enabledPromQL: rate(http_requests_total[$interval]). With Auto enabled, the interval scales with the time range (zoom out → larger buckets).
Exemplars are trace IDs attached to Prometheus histogram buckets. They turn a "this latency is high" panel into "this latency is high AND here are the exact traces of slow requests."
Prerequisites:
--enable-feature=exemplar-storageConfigure the panel:
yamltype: timeseries # or heatmap datasource: prometheus targets: - expr: histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket[5m]))) exemplar: true # ← turn it on fieldConfig: defaults: custom: showPoints: auto options: exemplars: color: red
Wire the exemplar to Tempo:
yaml# In datasource provisioning datasources: - name: Prometheus type: prometheus jsonData: exemplarTraceIdDestinations: - name: trace_id datasourceUid: tempo-uid urlDisplayLabel: "View trace"
Click an exemplar dot in the panel → opens Tempo with that trace. The most-asked-for-but-rarely-implemented Grafana feature.
Common gotcha: the exemplar label must be trace_id (or whatever your exemplarTraceIdDestinations.name says) on the histogram; OTel SDK uses trace_id by default but custom instrumentation often uses traceID or tid. Match exactly.
Slow dashboards are unused dashboards. Common offenders and fixes:
Cardinality: A query like sum by (pod) (rate(http_requests_total[5m])) over 50,000 pods returns 50,000 series. Browser dies. Fix:
topk(20, sum by (pod) (rate(http_requests_total[5m])))Top-20 visible; long tail invisible. Pair with a "view all pods" button that links to a dedicated debug dashboard.
Range: rate(metric[1h]) over 24h with 1m resolution returns 1,440 points × N series. Use $__rate_interval (auto-scales with range) instead of fixed window:
rate(http_requests_total[$__rate_interval])Max data points: every panel has a "Max data points" option (default 100-200). Set explicitly to match panel width in pixels. More points = more rendering load and no extra information.
Recording rules: queries computed at query time on every dashboard load. Push to Prometheus recording rules for ones used in many dashboards:
yaml# In Prometheus rules - record: service:http_request_rate:5m expr: sum by (service, status) (rate(http_requests_total[5m]))
Dashboard query becomes service:http_request_rate:5m — pre-computed, instant.
Query timeout: set per-datasource (Prometheus default is 30s, Loki 60s). Long timeouts make slow dashboards feel hung; cut to 15s and let the user retry with a smaller range.
Refresh rate: dashboards refreshing every 5s with 20 panels = 240 queries/min. Most ops dashboards need 30s, debug dashboards need OFF (you want a frozen window).
Mixed datasources: the "Mixed" datasource lets one panel query Prom + Loki. It runs both in parallel — fine for small queries, terrible if Loki is slow. Use sparingly; prefer separate panels with shared variables.
Loki query patterns: logs are line-streams. Always include a {label} matcher first to scope; full-text search over 1B lines hangs.
{service="checkout", env="prod"} |= "ERROR" | json | line_format "{{.message}}"Panel re-use vs duplication: Grafana library panels (saved as reusable) update everywhere on edit. Use for shared "service health" mini-panels across many dashboards.
exemplarTraceIdDestinations.up queries to populate every variable. Cardinality bomb on large fleets. Use a more specific recording rule.${__from} and ${__to} in the link.Include All| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-23 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of 0 percentage points is the difference between those two pass rates over the 23 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.