What you'll build
You'll create a CloudWatch dashboard named CloudPeek-Spend with two widgets. The first is a bar chart of the five services you've spent most on so far this month, found automatically, with your account's total. The second is that month-to-date total with a sparkline of the past week. In CloudPeek they look like this:
It takes about 15 minutes in the AWS console, or one deploy with CloudFormation or Terraform. Pick your tool on the tabs in each step.
Before you start
- An AWS account, and a sign-in that can change billing preferences and create CloudWatch dashboards and IAM users and policies, such as an administrator. If your accounts are in AWS Organizations, use the management account to see the whole organisation's spend.
- CloudPeek on your iPhone or iPad.
What it costs. Billing metrics are free. The CloudWatch free tier includes three dashboards; beyond that, each dashboard is $3 a month. CloudPeek's refreshes call GetMetricData, which AWS bills at $0.01 per 1,000 metrics. With both widgets pinned and refreshing every 30 minutes, that's 8 metrics per refresh, about 12¢ a month. See CloudWatch pricing.
1. Turn on billing metrics
AWS only sends your estimated charges to CloudWatch after you opt in. You do this once per account, in the console: CloudFormation, Terraform and the AWS CLI can't change this setting.
- Sign in to the AWS console and open Billing and Cost Management › Billing preferences.
- Under Alert preferences, choose Edit.
- Select Receive CloudWatch billing alerts and save the change.
Estimated charges are published to the US East (N. Virginia) Region, us-east-1, several times a day. They count from the start of the month, so they go back to zero each month. The first data can take a few hours to appear. If you're not signed in as the root user, the account must also allow IAM access to billing information (under Account › IAM user and role access to Billing information).
Until the first data arrives, the dashboard you'll create next says No data available. To check whether billing metrics have started, open CloudWatch in us-east-1 and choose Metrics › All metrics: a Billing namespace appears once they have. Metrics Explorer doesn't list billing metrics, so look under All metrics.
2. Create the dashboard
- Open the CloudWatch console in us-east-1 and choose Create dashboard.
- Name it
CloudPeek-Spendand choose Create dashboard. If the Add widget window opens, close it. - Choose Actions › View/edit source, replace the source with the dashboard JSON below, and choose Update.
- Choose Save.
Download spend-by-service.json (it's shown below), then run:
aws cloudwatch put-dashboard \
--region us-east-1 \
--dashboard-name CloudPeek-Spend \
--dashboard-body file://spend-by-service.json
One template creates the dashboard, and also the read-only policy and IAM user from step 3. Here's the dashboard resource:
Dashboard:
Type: AWS::CloudWatch::Dashboard
Properties:
DashboardName: !Ref DashboardName
DashboardBody: !Sub |
{
"start": "-P7D",
"widgets": [
{
"type": "metric",
"x": 0,
"y": 0,
"width": 12,
"height": 8,
"properties": {
"title": "Top ${TopServices} services by spend",
"view": "bar",
"region": "us-east-1",
"start": "-P1D",
"stat": "Maximum",
"period": 21600,
"metrics": [
[ "AWS/Billing", "EstimatedCharges", "Currency", "${Currency}", { "label": "Total" } ],
[ { "expression": "SORT(SEARCH('{AWS/Billing,Currency,ServiceName} MetricName=\"EstimatedCharges\" Currency=\"${Currency}\"', 'Maximum', 21600), MAX, DESC, ${TopServices})", "label": "${!PROP('Dim.ServiceName')}", "id": "top" } ]
]
}
},
{
"type": "metric",
"x": 12,
"y": 0,
"width": 6,
"height": 8,
"properties": {
"title": "Month-to-date spend",
"view": "singleValue",
"region": "us-east-1",
"stat": "Maximum",
"period": 21600,
"sparkline": true,
"metrics": [
[ "AWS/Billing", "EstimatedCharges", "Currency", "${Currency}", { "label": "Total" } ]
]
}
}
]
}
The body is the dashboard JSON below, with ${TopServices} and ${Currency} filled in from the stack's parameters. ${!PROP(…)} stops CloudFormation from treating the dynamic label as one of its own variables.
aws cloudformation deploy \
--region us-east-1 \
--stack-name CloudPeek \
--template-file cloudpeek-setup.yaml \
--capabilities CAPABILITY_NAMED_IAM
Or, in the CloudFormation console in us-east-1, choose Upload a template file, name the stack CloudPeek, check the parameters, and select I acknowledge that AWS CloudFormation might create IAM resources with custom names before you submit. When the stack reaches CREATE_COMPLETE, its Outputs tab links to the dashboard.
| Parameter | Default | What it's for |
|---|---|---|
DashboardName | CloudPeek-Spend | The dashboard's name. |
TopServices | 5 | How many of your biggest services to chart, from 1 to 20. |
Currency | USD | The currency your bill is in. |
PolicyName | CloudPeekReadOnly | The read-only policy's name. |
CreateUser | true | Set to false if you'll sign in with IAM Identity Center. |
UserName | cloudpeek | The IAM user's name. |
To change them, add --parameter-overrides, such as --parameter-overrides TopServices=10 CreateUser=false.
One configuration creates the dashboard, and also the read-only policy and IAM user from step 3. Here's the dashboard resource:
resource "aws_cloudwatch_dashboard" "spend" {
dashboard_name = var.dashboard_name
dashboard_body = jsonencode({
start = "-P7D"
widgets = [
{
type = "metric"
x = 0
y = 0
width = 12
height = 8
properties = {
title = "Top ${var.top_services} services by spend"
view = "bar"
region = "us-east-1"
start = "-P1D"
stat = "Maximum"
period = 21600
metrics = [
["AWS/Billing", "EstimatedCharges", "Currency", var.currency, { label = "Total" }],
[{
expression = "SORT(SEARCH('{AWS/Billing,Currency,ServiceName} MetricName=\"EstimatedCharges\" Currency=\"${var.currency}\"', 'Maximum', 21600), MAX, DESC, ${var.top_services})"
label = "$${PROP('Dim.ServiceName')}"
id = "top"
}],
]
}
},
{
type = "metric"
x = 12
y = 0
width = 6
height = 8
properties = {
title = "Month-to-date spend"
view = "singleValue"
region = "us-east-1"
stat = "Maximum"
period = 21600
sparkline = true
metrics = [
["AWS/Billing", "EstimatedCharges", "Currency", var.currency, { label = "Total" }],
]
}
},
]
})
}
It builds exactly the dashboard JSON below with jsonencode. $${PROP(…)} stops Terraform from treating the dynamic label as an interpolation. Save the file in an empty directory and run:
terraform init
terraform apply
| Variable | Default | What it's for |
|---|---|---|
dashboard_name | CloudPeek-Spend | The dashboard's name. |
top_services | 5 | How many of your biggest services to chart, from 1 to 20. |
currency | USD | The currency your bill is in. |
policy_name | CloudPeekReadOnly | The read-only policy's name. |
create_user | true | Set to false if you'll sign in with IAM Identity Center. |
user_name | cloudpeek | The IAM user's name. |
To change them, pass -var, such as terraform apply -var top_services=10 -var create_user=false. To add the dashboard to an existing configuration, copy the resources and leave out the terraform and provider blocks.
The dashboard, explained
Whichever tool you use, this is the dashboard you end up with:
{
"start": "-P7D",
"widgets": [
{
"type": "metric",
"x": 0,
"y": 0,
"width": 12,
"height": 8,
"properties": {
"title": "Top 5 services by spend",
"view": "bar",
"region": "us-east-1",
"start": "-P1D",
"stat": "Maximum",
"period": 21600,
"metrics": [
[ "AWS/Billing", "EstimatedCharges", "Currency", "USD", { "label": "Total" } ],
[ { "expression": "SORT(SEARCH('{AWS/Billing,Currency,ServiceName} MetricName=\"EstimatedCharges\" Currency=\"USD\"', 'Maximum', 21600), MAX, DESC, 5)", "label": "${PROP('Dim.ServiceName')}", "id": "top" } ]
]
}
},
{
"type": "metric",
"x": 12,
"y": 0,
"width": 6,
"height": 8,
"properties": {
"title": "Month-to-date spend",
"view": "singleValue",
"region": "us-east-1",
"stat": "Maximum",
"period": 21600,
"sparkline": true,
"metrics": [
[ "AWS/Billing", "EstimatedCharges", "Currency", "USD", { "label": "Total" } ]
]
}
}
]
}
- The expression row finds your top services for you.
SEARCHfinds every service's estimated charges, andSORT(…, MAX, DESC, 5)keeps the five biggest. Services you start using appear on their own. To show more or fewer, change the5at the end of the expression (or theTopServicesparameter, or thetop_servicesvariable). - The label
${PROP('Dim.ServiceName')}names each bar after its service, such asAmazonEC2orAWSLambda. CloudPeek shows the short name (EC2, Lambda), lists them biggest first, and adds up any that don't fit into Other. - The row with only a
Currencydimension is your whole account's charges so far this month. CloudPeek shows it as the widget's Total rather than as a bar, so services outside the top five still count. In the CloudWatch console it appears as one more bar. - Charges count from the start of the month, so the bar chart looks at just the last day (
"start": "-P1D"). That way the ranking follows this month's charges rather than last month's. The month-to-date total uses the dashboard's week for its sparkline. - If your bill isn't in US dollars, change
USDin both rows and in the expression to your currency code. - For a donut of each service's share, change
"view": "bar"to"view": "pie". CloudPeek puts the Total in the middle and shows everything outside the top five as an Other slice.
To see every service's charges, open CloudWatch in us-east-1 and choose All metrics › Billing › By Service.
3. Give CloudPeek read-only access
Follow Give CloudPeek read-only access in the getting started guide. It creates the CloudPeekReadOnly policy and either an IAM user with an access key or an IAM Identity Center permission set.
Follow Give CloudPeek read-only access in the getting started guide, which has the AWS CLI commands for an IAM user or an IAM Identity Center permission set.
The stack from step 2 already created the CloudPeekReadOnly policy and, unless you set CreateUser to false, the cloudpeek user. Create the user's access key. The secret is shown only once:
aws iam create-access-key --user-name cloudpeek
For IAM Identity Center, attach CloudPeekReadOnly to a permission set as a customer managed policy (see Option B).
The configuration from step 2 already created the CloudPeekReadOnly policy and, unless you set create_user to false, the cloudpeek user. Create the user's access key outside Terraform, so the secret stays out of your state. It's shown only once:
aws iam create-access-key --user-name cloudpeek
For IAM Identity Center, attach CloudPeekReadOnly to a permission set as a customer managed policy (see Option B).
4. Sign in and pin your widgets
- Open CloudPeek and add your account on the Accounts tab with the access key or IAM Identity Center (see signing in). Choose
us-east-1as the default region. - On Home, open the CloudPeek-Spend dashboard and tap Pin on Top 5 services by spend and Month-to-date spend.
- Touch and hold an empty area of your Home Screen until the apps jiggle. Tap Edit, then Add Widget. Find CloudPeek, pick a size and tap Add Widget. Medium suits the bar chart; small suits the total.
- New widgets show your first pinned widget. To choose another, touch and hold the widget and tap Edit Widget.
The total also works well on the Lock Screen, where widgets show the latest value.
Removing it
In the CloudWatch console in us-east-1, open Dashboards, select CloudPeek-Spend and choose Delete. To remove CloudPeek's access too, delete the cloudpeek user and the CloudPeekReadOnly policy in the IAM console.
aws cloudwatch delete-dashboards --region us-east-1 --dashboard-names CloudPeek-Spend
CloudFormation can't delete a user that still has an access key, or a policy that something outside the stack still uses. Delete the user's access key and detach the policy from any permission sets first, then delete the stack:
aws iam list-access-keys --user-name cloudpeek
aws iam delete-access-key --user-name cloudpeek --access-key-id <access-key-id>
aws cloudformation delete-stack --region us-east-1 --stack-name CloudPeek
IAM won't delete a user that still has an access key, or a policy that's still attached elsewhere. Delete the user's access key and detach the policy from any permission sets first, then destroy:
aws iam list-access-keys --user-name cloudpeek
aws iam delete-access-key --user-name cloudpeek --access-key-id <access-key-id>
terraform destroy
Troubleshooting
The widgets are empty, or the dashboard says “No data available”
Billing metrics aren't reaching CloudWatch yet. Check that they're turned on (step 1) and give them a few hours. Then look for the Billing namespace under All metrics in us-east-1 (Metrics Explorer doesn't list billing metrics). In AWS Organizations, set this up in the management account, which sees the whole organisation's charges.
A service is missing
It isn't one of your five biggest this month. To chart more, raise the number at the end of the SORT expression (see how it works). Its charges are already included in the Total.
The Total is bigger than the bars added up
That's expected. The Total is your whole bill so far this month, including services outside your top five.
Something else
See Troubleshooting, or email support@cloudpeek.app. Never send us your secret access key.