Created
July 17, 2023 10:47
-
-
Save daverickdunn/c05f5e1ef2493082582c6a41cdee6c85 to your computer and use it in GitHub Desktop.
Get Individual Lambda Costs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
CloudWatchLogsClient, | |
StartQueryCommand, | |
GetQueryResultsCommand, | |
LogGroup, | |
DescribeLogGroupsCommand, | |
ResultField | |
} from '@aws-sdk/client-cloudwatch-logs'; | |
import { writeFileSync } from 'fs'; | |
import { DateTime } from 'luxon'; | |
const client = new CloudWatchLogsClient({}); | |
async function query(logGroupNames: string[], days: number) { | |
const end = DateTime.now().toUnixInteger(); | |
const start = DateTime.now().minus({ days }).toUnixInteger(); | |
const command = new StartQueryCommand({ | |
endTime: end, | |
logGroupNames, | |
queryString: ` | |
filter @type = "REPORT" | |
| stats | |
count(@type) as countInvocations, | |
sum(@billedDuration) / 1000 as allDurationInSeconds, | |
max(@memorySize) / 1024 / 1024 / 1024 as memoryAllocated, | |
allDurationInSeconds * memoryAllocated * 0.0000166667 as totalCost, | |
totalCost / countInvocations as avgCostPerInvocation | |
by @log | |
| sort by totalCost desc | |
`, | |
startTime: start | |
}) | |
const query = await client.send(command) | |
do { | |
const command = new GetQueryResultsCommand({ | |
queryId: query.queryId | |
}); | |
const res = await client.send(command) | |
if (res.status === 'Running') { | |
await new Promise(res => setTimeout(() => res(null), 2000)) | |
} else { | |
return res | |
} | |
} while (true) | |
} | |
// Takes two command line args: | |
// 1 - LogGroup name prefix (required) | |
// 2 - Number of days to calcluate, from today (optional - defaults to 7) | |
// Usage example: | |
// node costs.js "/aws/lambda/prod" 7 | |
async function main() { | |
const args = process.argv.slice(2); | |
if (typeof args[0] !== 'string') { | |
console.log('missing "logGroupNamePrefix" argument') | |
return; | |
} | |
if (args[1] !== undefined && Number.isNaN(parseInt(args[1]))) { | |
console.log('"days" arg must be a number') | |
return; | |
} | |
const logGroupNamePrefix = args[0]; | |
const days = parseInt(args[1]) ?? 7; | |
let groups: LogGroup[] = []; | |
let groupNextToken: string | undefined = undefined; | |
do { | |
const command = new DescribeLogGroupsCommand({ | |
logGroupNamePrefix, | |
nextToken: groupNextToken | |
}) | |
const { logGroups, nextToken } = await client.send(command); | |
groups = groups.concat(logGroups); | |
groupNextToken = nextToken; | |
} while (groupNextToken !== undefined); | |
let queryResults: ResultField[][] = []; | |
let groupNames = groups.map(group => group.logGroupName); | |
do { | |
const logGroupNames = groupNames.splice(0, 50); | |
const { results } = await query(logGroupNames, days); | |
queryResults = queryResults.concat(results); | |
} while (groupNames.length > 0) | |
const result = queryResults.reduce((acc, item) => { | |
const name = item.find(e => e.field === '@log').value; | |
const cost = parseFloat(item.find(e => e.field === 'totalCost').value); | |
acc.totalCost += cost; | |
acc.groupCosts.push({ name, cost }); | |
return acc; | |
}, { totalCost: 0, groupCosts: [] }) | |
result.groupCosts = result.groupCosts.sort((a, b) => a.cost > b.cost ? -1 : 1) | |
writeFileSync('costs.json', JSON.stringify(result)) | |
console.log(result) | |
} | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment