Untitled
unknown
yaml
2 years ago
9.3 kB
5
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...