Untitled
unknown
yaml
3 years ago
9.3 kB
6
Indexable
---
AWSTemplateFormatVersion: '2010-09-09'
Description: Template to create demo Custom Widget Lambda function. Change the stack name to set the name of the Lambda function. Once your stack is created, go to the CloudWatch Console Add widget modal to continue with your custom widget creation.
Parameters:
DoCreateExampleDashboard:
Description: Do you want an example dashboard created that shows how the custom widget works?
Type: String
AllowedValues: [ 'Yes', 'No']
Default: 'No'
Conditions:
CreateExampleDashboard: !Equals ["Yes", !Ref DoCreateExampleDashboard]
Resources:
lambdaFunction:
Type: AWS::Lambda::Function
Properties:
Code:
ZipFile: |
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0
// CloudWatch Custom Widget sample: display sample Cost Explorer report
const aws = require('aws-sdk');
const DOCS = `## Display Cost Explorer report
Displays a report on cost of each AWS service for the selected time range.`;
const PALETTE = ['#1f77b4','#ff7f0e','#2ca02c','#d62728','#9467bd','#8c564b','#e377c2','#7f7f7f','#bcbd22','#17becf','#aec7e8','#ffbb78','#98df8a','#ff9896','#c5b0d5','#c49c94','#f7b6d2','#c7c7c7','#dbdb8d','#9edae5' ];
const CSS = `<style>table{width:100%}td,th{font-family:Amazon Ember,Helvetica Neue,Roboto,Arial,sans-serif;font-size:14px;white-space:nowrap;text-align:center;padding:3px;border-bottom:1px solid #f2f2f2}td:first-child{text-align:right;width:1px}td:nth-child(2){width:1px;font-weight:700}td div{border-radius:6px;height:18px}tr:hover{background:#fbf8e9!important;transition:all .1s ease-in-out}th{text-align:center;border-bottom:1px solid #ccc;background-color:#fafafa;height:40px}.cwdb-theme-dark td,.cwdb-theme-dark th{color:white;background-color:#2A2E33;}</style>`;
const getCostResults = async (start, end) => {
const ce = new aws.CostExplorer({ region: 'us-east-1' });
let NextPageToken = null;
let costs = [];
do { // paginate until no next page token
const params = { TimePeriod: { Start: start, End: end }, Granularity: 'DAILY', GroupBy: [ { Type: 'DIMENSION', Key: 'SERVICE' } ], Metrics: [ 'UnblendedCost' ], NextPageToken };
const response = await ce.getCostAndUsage(params).promise();
costs = costs.concat(response.ResultsByTime);
NextPageToken = response.NextPageToken;
} while (NextPageToken);
return costs;
};
const tableStart = (totalCost, start, end) => {
return `<table class="cwdb-no-default-styles"><thead><tr><th>Service</th><th colspan=2><a href="/cost-management/home?region=us-east-1#/dashboard" target=_blank>Total Cost: $${totalCost.toLocaleString(undefined, {maximumFractionDigits: 0 })} (${start} - ${end})</a></td></tr></thead>`;
};
const collate = costResults => {
const scs = {};
let totalCost = 0;
costResults.forEach(result => {
result.Groups.forEach(group => {
const serviceName = group.Keys[0];
let serviceCost = scs[serviceName] || 0;
let currentDayCost = parseFloat(group.Metrics.UnblendedCost.Amount) || 0;
totalCost += currentDayCost;
serviceCost += parseFloat(group.Metrics.UnblendedCost.Amount);
scs[serviceName] = serviceCost;
});
});
const sortedScs = Object.entries(scs).sort(([,a],[,b]) => b-a);
const maxServiceCost = Math.max(...sortedScs.map(cost => cost[1]));
return { totalCost, serviceCosts: sortedScs, maxServiceCost };
};
const getCostResultsHtml = (costResults, start, end) => {
costResults.sort((a, b) => a.TimePeriod.Start.localeCompare(b.TimePeriod.Start));
const { totalCost, serviceCosts, maxServiceCost } = collate(costResults);
let html = tableStart(totalCost, start, end);
serviceCosts.forEach((serviceEntry, i) => {
const [ serviceName, serviceCost ] = serviceEntry;
const percent = (serviceCost / totalCost * 100).toFixed(2);
const maxPercent = (serviceCost / maxServiceCost * 100).toFixed(2);
const color = PALETTE[i % PALETTE.length];
html += `<tr><td>${serviceName}</td><td title="${percent}%">$${serviceCost.toLocaleString(undefined, {maximumFractionDigits: 2 })}</td><td title="${percent}%"><div style="background-color: ${color}; width: ${maxPercent}%;"></div></td></tr>`;
});
return `${html}</table>`;
};
exports.handler = async (event) => {
if (event.describe) {
return DOCS;
}
const widgetContext = event.widgetContext;
const timeRange = widgetContext.timeRange.zoom || widgetContext.timeRange;
const start = timeRange.start;
const end = timeRange.end;
const minTimeDiff = Math.max(end - start, 24 * 60 * 60 * 1000);
const newStart = end - minTimeDiff;
const startStr = new Date(newStart).toISOString().split('T')[0];
const endStr = new Date(end).toISOString().split('T')[0];
const dailyCosts = await getCostResults(startStr, endStr);
return CSS + getCostResultsHtml(dailyCosts, startStr, endStr);
};
Description: "Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved."
FunctionName: !Sub ${AWS::StackName}
Handler: index.handler
MemorySize: 128
Role: !GetAtt lambdaIAMRole.Arn
Runtime: nodejs12.x
Timeout: 60
Tags:
- Key: cw-custom-widget
Value: describe:readOnly
lambdaIAMRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Policies:
- PolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Effect: Allow
Resource:
- !Sub arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${AWS::StackName}:*
PolicyName: lambda
- PolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- ce:GetCostAndUsage
Effect: Allow
Resource:
- "*"
PolicyName: costExplorerGetCostAndUsage
lambdaLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /aws/lambda/${AWS::StackName}
RetentionInDays: 7
demoDashboard:
Type: AWS::CloudWatch::Dashboard
Condition: CreateExampleDashboard
Properties:
DashboardName: !Sub ${AWS::StackName}-${AWS::Region}
DashboardBody:
!Sub
>
{
"start": "-P30D",
"widgets": [
{
"type": "custom",
"width": 24,
"height": 8,
"properties": {
"endpoint": "${lambdaFunction.Arn}",
"updateOn": {
"refresh": true,
"timeRange": true
},
"title": "Cost Explorer Report - change time range to see report change"
}
},
{
"type": "metric",
"width": 24,
"height": 6,
"properties": {
"metrics": [
[ { "expression": "SEARCH('{AWS/Billing,Currency,ServiceName} MetricName=\"EstimatedCharges\"', 'Average', 86400)", "id": "e1", "region": "us-east-1", "visible": false } ],
[ { "expression": "SORT(e1, MAX, DESC)", "label": "[${!MAX}]", "id": "e2", "region": "us-east-1" } ]
],
"view": "timeSeries",
"stacked": false,
"region": "us-east-1",
"stat": "Average",
"period": 300,
"title": "Max daily cost - over time",
"yAxis": {
"left": {
"label": "$",
"showUnits": false
}
}
}
}
]
}
Editor is loading...