Most monitoring dashboards are useless. Walls of graphs nobody looks at until something breaks — then nobody knows which graph matters. Here’s how to build dashboards that actually help.
The Dashboard Hierarchy# L L L e e e v v v e e e l l l 1 2 3 : : : E " S " D " x I e W e W e s r h e h c v a p y u e i t t v ↓ c ' ↓ D i i e e s i s v r v e y H b e i t e r t O h a o ( v i l k p b e n t e e r r g h n r o v ? k i O ( " c e e K p o n w ? e m ? " r p " ( o 1 s n e e d r n a v t s i ) h c b e o ) a r d )
Start at level 1, drill down when needed.
The Four Golden Signals# Google’s SRE book nailed it. Monitor these:
1. Latency# 1
2
3
4
# Request duration percentiles
histogram_quantile ( 0.50 , rate ( http_request_duration_seconds_bucket [ 5m ] ))
histogram_quantile ( 0.95 , rate ( http_request_duration_seconds_bucket [ 5m ] ))
histogram_quantile ( 0.99 , rate ( http_request_duration_seconds_bucket [ 5m ] ))
Dashboard panel: Line graph showing p50, p95, p99 over time
2. Traffic# 1
2
3
4
5
# Requests per second
sum ( rate ( http_requests_total [ 5m ] ))
# By endpoint
sum ( rate ( http_requests_total [ 5m ] )) by ( endpoint )
Dashboard panel: Stacked area chart by endpoint
3. Errors# 1
2
3
4
5
6
# Error rate percentage
sum ( rate ( http_requests_total { status =~ " 5.. "}[ 5m ] ))
/ sum ( rate ( http_requests_total [ 5m ] )) * 100
# By error type
sum ( rate ( http_requests_total { status =~ " 5.. "}[ 5m ] )) by ( status )
Dashboard panel: Single stat (big number) + time series
4. Saturation# 1
2
3
4
5
6
7
8
# CPU usage
100 - ( avg ( irate ( node_cpu_seconds_total { mode = " idle "}[ 5m ] )) * 100 )
# Memory usage
( 1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes ) * 100
# Disk usage
100 - ( node_filesystem_avail_bytes / node_filesystem_size_bytes * 100 )
Dashboard panel: Gauge showing current %, alert thresholds marked
Executive Dashboard# One screen, whole system health:
┌ │ ├ │ │ ├ │ │ │ │ │ │ │ │ │ │ ├ │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ S ─ U ─ [ A [ P [ S ─ A [ ─ ─ Y ─ 9 p ─ ═ P ═ a ═ e ─ c W ─ ─ S ─ 9 t ─ ═ I ═ y ═ a ─ t A ─ ─ T ─ . i ─ ═ ═ m ═ r ─ i R ─ ─ E ─ 9 m ─ ═ S ═ e ═ c ─ v N ─ ─ M ─ % e ─ ═ e ═ n ═ h ─ e ] ─ ─ ─ ─ ═ r ═ t ═ ─ ─ ─ H ─ ─ ═ v ═ ═ S ─ I S ─ ─ E ─ ─ ═ i ═ S ═ e ─ n e ─ ─ A ─ ─ ═ c ═ e ═ r ─ c a ─ ─ L ─ ─ ═ e ═ r ═ v ─ i r ─ ─ T ┬ │ │ ┴ ═ ═ v ═ i ─ d c ─ ─ H ─ ─ ═ ═ i ═ c ─ e h ─ ─ ─ ─ ═ ═ c ═ e ─ n ─ ─ ─ ─ ═ ═ e ═ ─ t l ─ ─ ─ L ─ ═ ═ ═ ─ s a ─ ─ ─ 4 a ─ ═ ═ ═ ─ : t ─ ─ ─ 2 t ─ ═ ═ ═ ─ e ─ ─ ─ m e ─ ═ ═ ═ ─ 1 n ─ ─ ─ s n ─ ═ ═ ═ ─ c ─ ─ ─ c ─ ═ ═ ═ ─ y ─ ─ ─ y ─ ═ ═ ═ ─ ─ ─ ─ ─ ═ ═ ═ ─ e ─ ─ ─ ─ ═ ═ ═ ─ l ─ ─ ─ ─ ═ ═ ═ ─ e ─ ─ ┬ │ │ ┴ ═ ═ ═ ─ v ─ ─ ─ ─ ═ ═ ═ ─ a ─ ─ ─ ─ ═ ═ ═ ─ t ─ ─ ─ ─ ═ ═ ═ ─ e ─ ─ ─ 1 T ─ ═ ═ ═ ─ d ─ ─ ─ . r ─ ═ ═ ═ ─ ─ ─ ─ 2 a ─ ═ ═ ═ ─ - ─ ─ ─ k f ─ ═ ═ ═ ─ ─ ─ ─ / f ─ ═ ═ ═ ─ i ─ ─ ─ s i ─ ═ ═ ═ ─ n ─ ─ ─ c ─ ═ ═ ═ ─ v ─ ─ ─ ─ ═ ═ ═ ─ e ─ ─ ─ ─ ═ ═ ═ ─ s ─ ─ ─ ─ ═ ═ ═ ─ t ─ ─ ┬ │ │ ┴ ═ ═ ═ ─ i ─ ─ ─ ─ ═ ═ ] ─ g ─ ─ ─ ─ ═ ═ ─ a ─ ─ ─ ─ ═ ═ 9 ─ t ─ ─ L ─ E ─ ═ ═ 5 ─ i ─ ─ a ─ r ─ ═ ] . ─ n ─ ─ s ─ 0 r ─ ═ 1 ─ g ─ ─ t ─ . o ─ ═ 9 % ─ ─ ─ ─ 0 r ─ ═ ✅ 8 ✅ ⚠ ─ ─ ─ 2 ─ 2 ─ ] . ️ ─ ─ ─ 4 ─ % R ─ H 2 H ─ ─ ─ ─ a ─ 9 e % e D ─ ─ ─ h ─ t ─ 9 a a e ─ ─ ─ o ─ e ─ . l l g ─ ─ ─ u ─ ─ 9 t t r ─ ─ ─ r ─ ─ % h h a ─ ─ ─ s ─ ─ y y d ─ ─ ─ ─ ─ e ─ ─ ─ ┤ │ │ ┤ │ │ │ │ │ d ┤ ┘ ─ │ │ │ │ │ │ │ │ ┐
Grafana JSON# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
{
"panels" : [
{
"title" : "System Uptime" ,
"type" : "stat" ,
"targets" : [{
"expr" : "avg(up) * 100" ,
"legendFormat" : "Uptime %"
}],
"fieldConfig" : {
"defaults" : {
"thresholds" : {
"steps" : [
{ "color" : "red" , "value" : 0 },
{ "color" : "yellow" , "value" : 99 },
{ "color" : "green" , "value" : 99.9 }
]
},
"unit" : "percent"
}
}
},
{
"title" : "Error Rate" ,
"type" : "stat" ,
"targets" : [{
"expr" : "sum(rate(http_requests_total{status=~\"5..\"}[5m])) / sum(rate(http_requests_total[5m])) * 100"
}],
"fieldConfig" : {
"defaults" : {
"thresholds" : {
"steps" : [
{ "color" : "green" , "value" : 0 },
{ "color" : "yellow" , "value" : 1 },
{ "color" : "red" , "value" : 5 }
]
},
"unit" : "percent"
}
}
}
]
}
Service Dashboard Template# Every service gets one:
┌ │ ├ │ │ │ │ ├ │ │ │ │ ├ │ │ │ │ ├ │ │ │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ A ─ R ▃ C P ─ E C T ─ R C M D ─ E G P G ─ ─ P ─ e ▄ u e ─ r u h ─ e P E I ─ n E O E ─ ─ I ─ q ▅ r a ─ r r r ─ s U M S ─ d T S T ─ ─ ─ u ▆ r k ─ o r e ─ o : : K ─ p T ─ ─ S ─ e ▇ e : ─ r ▂ e s ─ u : ─ o ─ ─ E ─ s █ n ─ n h ─ r [ [ [ ─ i / / / ─ ─ R ─ t ▇ t 2 ─ R t o ─ c █ █ █ ─ n a a a ─ ─ V ─ ▆ : . ─ a : l ─ e █ █ █ ─ t p p p ─ ─ I ─ R ▅ 1 ─ t d ─ █ █ █ ─ s i i i ─ ─ C ─ a ▄ 1 k ─ e 0 : ─ U █ █ █ ─ / / ─ ─ E ─ t ▃ . / ─ . ─ s █ █ ─ u s ─ ─ ─ e ▄ 2 s ─ 0 1 ─ a █ █ ─ s r e ─ ─ ─ ▅ k ─ 2 % ─ g █ █ ─ e d a ─ ─ ─ ▆ / ─ % ─ e █ █ ─ r e r ─ ─ ─ s ─ ─ █ ─ s r c ─ ─ ─ ─ ─ █ ─ s h ─ ─ ─ ─ ─ █ ─ ─ ─ ─ ─ ─ █ ─ ─ ─ ─ ─ ─ █ ─ ─ ─ ─ ─ ─ █ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ 1 3 8 ─ ─ ─ ─ ─ ─ . 4 9 ─ ─ ─ ─ ─ ─ 2 0 0 ─ ─ ─ │ ─ │ ─ ─ k / ─ ─ ┬ │ │ │ ┼ │ │ │ ┴ ─ s s ─ ─ ─ ─ ─ ] ] ] ─ s ─ ─ ─ p ─ 5 ─ ─ ─ ─ ─ L 5 █ █ ─ T 0 5 ─ 4 6 2 ─ ─ ─ ─ a 0 █ █ ─ o 0 0 ─ 2 8 1 ─ 1 4 2 ─ ─ ─ t : █ █ ─ p : 3 ─ % % % ─ 2 5 3 ─ ─ ─ e █ █ ─ : ─ ─ m m 0 ─ ─ ─ n 1 █ █ ─ E D ─ ─ s s m ─ ─ ─ c 2 █ █ ─ r a S ─ ─ s ─ ─ ─ y m █ █ ─ r t e ─ ─ ─ ─ ─ s █ █ ─ o a r ─ ─ ─ ─ ─ D █ ─ r b v ─ ─ 0 0 0 ─ ─ ─ i | █ ─ s a i ─ ─ . . . ─ ─ ─ s █ ─ s c ─ ─ 0 0 1 ─ ─ ─ t p 1 █ ─ e e ─ ─ 1 5 2 ─ ─ ─ r 9 2 █ ─ ─ ─ % % % ─ ─ ─ i 5 m █ ─ t u ─ ─ ─ ─ ─ b : s ─ i n ─ ─ ─ ─ ─ u ─ m a ─ ─ ─ ─ ─ t 4 4 ─ e v ─ ─ ✅ ✅ ⚠ ─ ─ ─ i 5 5 ─ o a ─ ─ ️ ─ ─ ─ o m m ─ u i ─ ─ ─ ─ [ ─ n s s ─ t l ─ ─ ─ ─ 2 ─ ─ a ─ ─ ─ ─ 4 ─ | ─ ( b ─ ─ ─ ─ h ─ ─ 3 l ─ ─ ─ ─ ─ p ─ ) e ─ ─ [ ─ ─ ▾ ─ 9 ─ ─ ─ 5 ─ ─ ] ─ 9 ─ ( ─ ─ m ─ ─ ─ : ─ 1 ─ ─ ] ─ ─ ─ ─ ) ─ ─ ─ ─ ─ 1 ─ ─ ─ ─ ─ ─ 2 ─ ─ ─ │ │ ─ ─ ─ 0 ─ │ │ ─ │ │ │ ─ │ ─ ┐ │ ┤ │ m │ │ ┤ │ │ ┤ │ ┤ │ ┘ s Database Dashboard# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Connections
pg_stat_activity_count
# Connection pool usage
pg_stat_activity_count / pg_settings_max_connections * 100
# Query duration
histogram_quantile ( 0.95 , rate ( pg_stat_statements_seconds_bucket [ 5m ] ))
# Cache hit ratio
pg_stat_database_blks_hit / ( pg_stat_database_blks_hit + pg_stat_database_blks_read ) * 100
# Replication lag
pg_replication_lag_seconds
# Dead tuples (needs vacuum)
pg_stat_user_tables_n_dead_tup
Key Panels# ┌ │ ├ │ │ ├ │ │ │ ├ │ │ │ └ ─ ─ ─ ─ ─ ─ ─ C ─ ─ ─ ─ P ─ o ─ S S U ─ T o u ─ ─ O ─ n ─ l E P ─ a r s ─ ─ S ─ 4 n ─ o L D ─ b d e ─ ─ T ─ 5 e ─ w E A ─ l e r ─ ─ G ─ / c ─ C T ─ e r s ─ ─ R ─ 1 t ─ Q T E ─ s : ─ ─ E ─ 0 i ─ u ─ H : ─ ─ S ─ 0 o ─ e u ─ e ─ ─ Q ─ n ─ r s ─ a ─ ─ L ─ s ─ i F e ─ l l l ─ ─ ─ ─ e R r ─ t i i ─ ─ ┬ │ │ ┴ s O s ─ h v v ─ ─ ─ ─ M ─ e e ─ ─ ─ ─ S ─ : : ─ ─ ─ C ─ > o E ─ ─ ─ ─ 9 a ─ 1 r T ─ 1 3 ─ ─ ─ 9 c ─ 0 d ─ . 4 ─ ─ ─ . h ─ 0 e l ─ 2 0 ─ ─ ─ 2 e ─ m r a ─ M k ─ ─ ─ % ─ s s s ─ ─ ─ ─ H ─ ) t ─ ─ ─ ─ i ─ W _ ─ d d ─ ─ ─ t ─ H l ─ e e ─ ─ ─ ─ E o ─ a a ─ ─ ─ ─ R g ─ d d ─ ─ ┬ │ │ ┴ E i ─ : : ─ ─ ─ ─ . n ─ ─ ─ ─ ─ . . ─ 4 1 ─ ─ ─ R ─ . . ─ 5 2 ─ ─ ─ e ─ . ─ k 0 ─ ─ ─ 0 p ─ ( ─ k ─ ─ ─ . ─ a ( ─ ─ ─ ─ 3 L ─ v a ─ ─ ─ ─ s a ─ g v ─ b b ─ ─ ─ g ─ : g ─ l l ─ ─ ─ ─ : ─ o o ─ ─ ─ ─ 3 ─ a a ─ ─ ─ ─ 4 1 ─ t t ─ ─ ─ ─ 0 8 ─ : : ─ ─ ┬ │ │ ┴ m 0 ─ ─ ─ ─ ─ s m ─ 3 2 ─ ─ ─ ─ , s ─ % 6 ─ ─ ─ Q ─ , ─ % ─ ─ ─ u ─ c ─ ─ ─ ─ e ─ a c ─ ─ ─ ─ r ─ l a ─ ─ ─ ─ 1 y ─ l l ─ ✅ ⚠ ─ ─ ─ 2 ─ s l ─ ️ ─ ─ ─ m p ─ : s ─ ─ ─ ─ s 9 ─ : ─ v ─ ─ ─ 5 ─ 1 ─ a ─ ─ ─ ─ 2 8 ─ c ─ ─ ─ ─ 4 9 ─ u ─ ─ ─ ─ ) ) ─ u ─ ─ ─ ─ ─ m ─ ─ ─ ─ ─ ! ─ ─ ─ ─ ─ ─ ─ ┤ │ │ ┤ ─ │ ─ ─ │ │ │ ─ │ ─ ┐ │ ┤ │ ┘
Infrastructure Dashboard# 1
2
3
4
5
6
7
8
9
10
11
12
13
# Node health
up { job = " node "}
# Load average
node_load1 / count ( node_cpu_seconds_total { mode = " idle "} ) without ( cpu , mode )
# Network I/O
rate ( node_network_receive_bytes_total [ 5m ] )
rate ( node_network_transmit_bytes_total [ 5m ] )
# Disk I/O
rate ( node_disk_read_bytes_total [ 5m ] )
rate ( node_disk_written_bytes_total [ 5m ] )
K8s Dashboard# 1
2
3
4
5
6
7
8
9
10
11
# Pod restarts (bad sign)
increase ( kube_pod_container_status_restarts_total [ 1h ] )
# Pod status
kube_pod_status_phase
# Resource requests vs usage
container_memory_working_set_bytes / kube_pod_container_resource_requests { resource = " memory "}
# Pending pods (scheduling issues)
kube_pod_status_phase { phase = " Pending "}
Alert-Dashboard Alignment# Every alert should have a dashboard panel:
1
2
3
4
5
6
7
8
# Alert rule
- alert : HighErrorRate
expr : |
sum(rate(http_requests_total{status=~"5.."}[5m]))
/ sum(rate(http_requests_total[5m])) > 0.05
annotations :
dashboard : "https://grafana.example.com/d/api-service?var-timerange=1h"
panel : "Error Rate"
When alert fires → click link → see the problem visualized.
Dashboard Variables# Make dashboards reusable:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
"templating" : {
"list" : [
{
"name" : "service" ,
"type" : "query" ,
"query" : "label_values(up, service)" ,
"multi" : false
},
{
"name" : "instance" ,
"type" : "query" ,
"query" : "label_values(up{service=\"$service\"}, instance)" ,
"multi" : true
},
{
"name" : "interval" ,
"type" : "interval" ,
"options" : [
{ "text" : "1m" , "value" : "1m" },
{ "text" : "5m" , "value" : "5m" },
{ "text" : "1h" , "value" : "1h" }
]
}
]
}
}
Use in queries:
1
rate ( http_requests_total { service = " $service ", instance =~ " $instance "}[ $interval ] )
Dashboard as Code# 1
2
3
4
5
6
7
8
resource "grafana_dashboard" "api_service" {
config_json = file ( "dashboards/api-service.json" )
folder = grafana_folder . services . id
}
resource "grafana_folder" "services" {
title = "Service Dashboards"
}
Grafana + Jsonnet# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
local grafana = import 'grafonnet/grafana.libsonnet' ;
local dashboard = grafana . dashboard ;
local prometheus = grafana . prometheus ;
dashboard . new (
'API Service' ,
tags = [ 'api' , 'production' ],
)
. addPanel (
grafana . graphPanel . new (
'Request Rate' ,
datasource = 'Prometheus' ,
). addTarget (
prometheus . target (
'sum(rate(http_requests_total[5m]))' ,
legendFormat = 'Requests/s' ,
)
),
gridPos = { x : 0 , y : 0 , w : 12 , h : 8 },
)
Anti-Patterns# Too Many Graphs# ❌ ✅ 5 8 0 N - E o 1 a p b 2 c a o h n d f e y o a l c n s k u s n s w s o e e h w d r o s s w p i w a a n h n g e e s r l p e e s e v c e t i r o f y i t l c h o i o q n k u g e s t i o n
No Context# ❌ ✅ G G r N r G a o a r p p e h i h e d n s e w / h a i y o t e w i h l s f l t o s i h w p t r / i ' e r k s s e e h d g o o l z o d o d s n e o s r m b a a r d k e d
Stale Dashboards# ❌ ✅ D D a M a P s e s a h t h r b r b t o i o a c a o r s r f d d n s f o r e r e r o l v v m o i i n e c 2 g w e e y r q o e u w a e a n r x r e s i t r s e s a t r h g l i o y p
The Checklist# Start Here# Today: Create executive overview with 4 key metricsThis week: Add service dashboard for critical serviceThis month: Link all alerts to relevant dashboardsThis quarter: Implement dashboard as codeThe best dashboard is the one that tells you what’s wrong before users notice.
Dashboards are for humans. Design for the 3am on-call engineer who just woke up.