Why a dashboard?
CloudPeek is about your metrics: latency, error rates, queue depth, spend, the custom metrics your services publish, and the alarms that watch them. It doesn't ask you to pick those metrics on your phone. Finding the right namespace and dimensions, writing metric math and setting thresholds is much easier with a keyboard and a big screen, and CloudWatch already has the tool for it.
So a CloudWatch dashboard is simply your list of what to watch:
- Each widget is something you can pin. A widget groups one or more metrics: three instances' CPU on one line chart, your five biggest costs in one bar chart, an error rate worked out from two metrics in one number.
- Each dashboard is a set of widgets that belong together. One per service, one for spend, one for the on-call rotation. You can pin from as many dashboards and accounts as you like.
- A dashboard is plain JSON, so it can be defined in CloudFormation, Terraform, the AWS CDK or anything else that can write a string, and deployed with the services it watches.
Already have dashboards? Pin straight from them. Or make a small one just for your phone, with only the widgets you want to see at a glance.
Design it in the AWS console
- Open the CloudWatch console, choose Dashboards › Create dashboard, and name it.
- Choose a widget type: Line, Stacked area, Number, Gauge, Bar, Pie, Table, Text or Alarm status. All of them work in CloudPeek (see the widget reference).
- On the Browse tab, pick a namespace, such as ApplicationELB or one of your own, then tick the metrics you want on this widget.
- On the Graphed metrics tab, set each metric's statistic, period and label. Choose Add math for an expression such as an error rate, and untick the inputs you don't want drawn.
- On the Options tab, add horizontal annotations for thresholds and set the y-axis limits. A gauge needs both a minimum and a maximum.
- Choose Create widget, give it a short title, and Save the dashboard.
Open the dashboard in CloudPeek and every widget is drawn as it will look on your Home Screen. Pin the ones you want.
Make it read well on a phone
A widget on your Home Screen is a few centimetres across. A few habits make it much easier to read:
- One question per widget. “Is the API healthy?” reads better as an error-rate number and a latency gauge than as one graph with eight lines.
- Few series per graph. A medium widget's legend lists two series and a large one four, then “+N more”. A small widget has no legend at all.
- Short titles. The Lock Screen's inline widget keeps about 18 characters of the title, and the circular one about 8.
- Pick the view for the glance. A number with
"sparkline": trueshows the latest value and its trend; a gauge shows it against a range you set; a bar chart ranks things. See what each type looks like. - Set the range you care about. A widget's own
startoverrides the dashboard's, and with neither CloudPeek shows the last 3 hours. Every widget says which window it covers. - Labels over IDs. Give metrics a
label, or a dynamic one like${PROP('Dim.ServiceName')}, so the legend says “checkout” rather than “i-0a1b2c3d”.
Example: an API health dashboard
Three widgets for an Application Load Balancer: the error rate as a number, p99 latency as a gauge, and requests against errors on a line chart. In CloudPeek they look like this:
{
"start": "-PT3H",
"widgets": [
{
"type": "metric",
"x": 0,
"y": 0,
"width": 6,
"height": 6,
"properties": {
"title": "5xx error rate",
"view": "singleValue",
"region": "us-east-1",
"period": 300,
"sparkline": true,
"metrics": [
[ { "expression": "100 * FILL(m2, 0) / m1", "label": "Errors (%)", "id": "e1" } ],
[ "AWS/ApplicationELB", "RequestCount", "LoadBalancer", "app/checkout-api/50dc6c495c0c9188", { "stat": "Sum", "id": "m1", "visible": false } ],
[ ".", "HTTPCode_Target_5XX_Count", ".", ".", { "stat": "Sum", "id": "m2", "visible": false } ]
]
}
},
{
"type": "metric",
"x": 6,
"y": 0,
"width": 6,
"height": 6,
"properties": {
"title": "Latency p99",
"view": "gauge",
"region": "us-east-1",
"stat": "p99",
"period": 300,
"yAxis": { "left": { "min": 0, "max": 2 } },
"metrics": [
[ "AWS/ApplicationELB", "TargetResponseTime", "LoadBalancer", "app/checkout-api/50dc6c495c0c9188", { "label": "p99 (s)" } ]
]
}
},
{
"type": "metric",
"x": 12,
"y": 0,
"width": 12,
"height": 6,
"properties": {
"title": "Requests and errors",
"view": "timeSeries",
"region": "us-east-1",
"stat": "Sum",
"period": 300,
"metrics": [
[ "AWS/ApplicationELB", "RequestCount", "LoadBalancer", "app/checkout-api/50dc6c495c0c9188", { "label": "Requests" } ],
[ ".", "HTTPCode_Target_5XX_Count", ".", ".", { "label": "5xx", "yAxis": "right", "color": "#d62728" } ]
]
}
}
]
}
- Error rate: the expression
100 * FILL(m2, 0) / m1divides 5xx responses by requests.FILLcounts a period with no errors as zero rather than leaving a gap. Both inputs have"visible": false, so only the rate is shown. - Latency: a gauge uses the y-axis
minandmaxas its range (0 to 2 seconds here). Without them, CloudPeek assumes 0 to 100. - Requests and errors:
"yAxis": "right"gives the 5xx count its own scale, so a handful of errors isn't flattened against thousands of requests."."repeats the value in the same place on the row above. - Replace
app/checkout-api/50dc6c495c0c9188with your load balancer's name from its ARN, afterloadbalancer/, or let CloudFormation or Terraform fill it in, as below.
Manage it as code
Design a dashboard in the console until it looks right, then copy its JSON from Actions › View/edit source into your infrastructure code. From then on it's reviewed, versioned and deployed with the services it watches, and it's the same in every account and region you deploy to.
You don't have to put dashboards in code. A dashboard you edit in the console is picked up by CloudPeek on its next refresh, just the same.
If a dashboard is managed by CloudFormation or Terraform, treat the console as a sketchpad: changes you save there are overwritten on the next deploy. Try an idea in the console, then copy the source back into your code (Actions › View/edit source).
Keep the JSON in version control and deploy it with put-dashboard. To start from a dashboard you designed in the console:
aws cloudwatch get-dashboard \
--dashboard-name Checkout-API \
--query DashboardBody --output text > api-health.json
aws cloudwatch put-dashboard \
--dashboard-name Checkout-API \
--dashboard-body file://api-health.json
put-dashboard replaces the whole dashboard, so the file is always the complete truth.
An AWS::CloudWatch::Dashboard takes the JSON as a string. Wrap it in !Sub and you can fill in values from the same stack, such as the load balancer's dimension, instead of pasting IDs:
CheckoutApiDashboard:
Type: AWS::CloudWatch::Dashboard
Properties:
DashboardName: Checkout-API
DashboardBody: !Sub
- |
{
"start": "-PT3H",
"widgets": [
{
"type": "metric", "x": 0, "y": 0, "width": 6, "height": 6,
"properties": {
"title": "5xx error rate",
"view": "singleValue",
"region": "${AWS::Region}",
"period": 300,
"sparkline": true,
"metrics": [
[ { "expression": "100 * FILL(m2, 0) / m1", "label": "Errors (%)", "id": "e1" } ],
[ "AWS/ApplicationELB", "RequestCount", "LoadBalancer", "${Alb}", { "stat": "Sum", "id": "m1", "visible": false } ],
[ ".", "HTTPCode_Target_5XX_Count", ".", ".", { "stat": "Sum", "id": "m2", "visible": false } ]
]
}
},
{
"type": "metric", "x": 6, "y": 0, "width": 6, "height": 6,
"properties": {
"title": "Latency p99",
"view": "gauge",
"region": "${AWS::Region}",
"stat": "p99",
"period": 300,
"yAxis": { "left": { "min": 0, "max": 2 } },
"metrics": [
[ "AWS/ApplicationELB", "TargetResponseTime", "LoadBalancer", "${Alb}", { "label": "p99 (s)" } ]
]
}
},
{
"type": "metric", "x": 12, "y": 0, "width": 12, "height": 6,
"properties": {
"title": "Requests and errors",
"view": "timeSeries",
"region": "${AWS::Region}",
"stat": "Sum",
"period": 300,
"metrics": [
[ "AWS/ApplicationELB", "RequestCount", "LoadBalancer", "${Alb}", { "label": "Requests" } ],
[ ".", "HTTPCode_Target_5XX_Count", ".", ".", { "label": "5xx", "yAxis": "right", "color": "#d62728" } ]
]
}
}
]
}
- Alb: !GetAtt LoadBalancer.LoadBalancerFullName
LoadBalanceris theAWS::ElasticLoadBalancingV2::LoadBalancerin the same template; itsLoadBalancerFullNameis exactly the value CloudWatch'sLoadBalancerdimension wants.- Inside
!Sub, CloudFormation reads every${…}as one of its own variables. Write a CloudWatch dynamic label such as${LAST}as${!LAST}. - Dashboards are global, but each widget names its region.
${AWS::Region}uses the stack's.
An aws_cloudwatch_dashboard takes the JSON as a string. Write it in HCL and wrap it in jsonencode, and you can use references, variables and for expressions instead of pasting IDs:
resource "aws_lb" "checkout" {
name = "checkout-api"
subnets = ["subnet-11111111", "subnet-22222222"]
}
locals {
alb = aws_lb.checkout.arn_suffix # the LoadBalancer dimension: app/checkout-api/…
region = "us-east-1" # where the load balancer is
}
resource "aws_cloudwatch_dashboard" "checkout_api" {
dashboard_name = "Checkout-API"
dashboard_body = jsonencode({
start = "-PT3H"
widgets = [
{
type = "metric", x = 0, y = 0, width = 6, height = 6
properties = {
title = "5xx error rate"
view = "singleValue"
region = local.region
period = 300
sparkline = true
metrics = [
[{ expression = "100 * FILL(m2, 0) / m1", label = "Errors (%)", id = "e1" }],
["AWS/ApplicationELB", "RequestCount", "LoadBalancer", local.alb, { stat = "Sum", id = "m1", visible = false }],
[".", "HTTPCode_Target_5XX_Count", ".", ".", { stat = "Sum", id = "m2", visible = false }],
]
}
},
{
type = "metric", x = 6, y = 0, width = 6, height = 6
properties = {
title = "Latency p99"
view = "gauge"
region = local.region
stat = "p99"
period = 300
yAxis = { left = { min = 0, max = 2 } }
metrics = [
["AWS/ApplicationELB", "TargetResponseTime", "LoadBalancer", local.alb, { label = "p99 (s)" }],
]
}
},
{
type = "metric", x = 12, y = 0, width = 12, height = 6
properties = {
title = "Requests and errors"
view = "timeSeries"
region = local.region
stat = "Sum"
period = 300
metrics = [
["AWS/ApplicationELB", "RequestCount", "LoadBalancer", local.alb, { label = "Requests" }],
[".", "HTTPCode_Target_5XX_Count", ".", ".", { label = "5xx", yAxis = "right", color = "#d62728" }],
]
}
},
]
})
}
arn_suffixon anaws_lbis exactly the value CloudWatch'sLoadBalancerdimension wants.- Terraform reads
${…}in a string as an interpolation. Write a CloudWatch dynamic label such as${LAST}as$${LAST}. - Prefer to keep the JSON as a file?
dashboard_body = file("api-health.json")works too, ortemplatefile()to fill in values. - To bring a dashboard you made in the console under Terraform, add an
importblock with its name as theid, or runterraform import aws_cloudwatch_dashboard.checkout_api Checkout-API.
The AWS CDK, Pulumi and other tools work the same way: anything that can set a dashboard's body can define what CloudPeek shows.
How changes reach your phone
- Each time a pinned widget refreshes from AWS, CloudPeek reads the dashboard again, so a change you save in the console or deploy from code shows on the next refresh. To see it at once, tap the widget's refresh button.
- Pinned widgets follow their widget when you move it, resize it, retitle it or change its metrics. If CloudPeek can't tell which widget it was, it keeps showing the version you pinned.
- If you delete the dashboard, the widget keeps its last data and says Dashboard deleted.
- Pinning the same widget again reconnects any Home Screen widgets that showed it.