UNPKG

@cloud-carbon-footprint/aws

Version:

The core logic to get cloud usage data and estimate energy and carbon emissions from Amazon Web Services.

151 lines 5.98 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const core_1 = require("@cloud-carbon-footprint/core"); const CostMapper_1 = require("./CostMapper"); const ramda_1 = require("ramda"); const domain_1 = require("../domain/"); class Lambda { TIMEOUT; POLL_INTERVAL; serviceWrapper; LOG_GROUP_SIZE_REQUEST_LIMIT = 20; MAX_CONCURRENT_LOG_QUERIES = 10; serviceName = 'Lambda'; constructor(TIMEOUT = 60000, POLL_INTERVAL = 1000, serviceWrapper) { this.TIMEOUT = TIMEOUT; this.POLL_INTERVAL = POLL_INTERVAL; this.serviceWrapper = serviceWrapper; } async getEstimates(start, end, region) { const groupNames = await this.getLambdaLogGroupNames(); if ((0, ramda_1.isEmpty)(groupNames)) { return []; } const queryIdsArray = await this.getQueryIdsArray(groupNames, start, end); let usage = []; for (const queryId of queryIdsArray) { usage = usage.concat(await Promise.all(queryId.map((id) => this.getResults(id.toString())))); } const filteredResults = [ ...usage.reduce((combinedArr, { results }) => [...combinedArr, ...results], []), ]; return filteredResults.map((resultByDate) => { const timestampField = resultByDate[0]; const wattsField = resultByDate[1]; const timestamp = new Date(timestampField.value.substr(0, 10)); const kilowattHours = (Number.parseFloat(wattsField.value) * domain_1.AWS_CLOUD_CONSTANTS.getPUE()) / 1000; const co2e = (0, core_1.estimateCo2)(kilowattHours, region, domain_1.AWS_EMISSIONS_FACTORS_METRIC_TON_PER_KWH); return { timestamp, kilowattHours: kilowattHours, co2e, }; }); } async getQueryIdsArray(groupNames, start, end) { const queryIdsArray = []; for (const logGroup of groupNames) { const queryIds = await Promise.all(await this.serviceWrapper.getQueryByInterval(60, this.runQuery, start, end, logGroup)); queryIdsArray.push(queryIds); } return queryIdsArray; } async getLambdaLogGroupNames() { const params = { logGroupNamePrefix: '/aws/lambda', }; const logGroupData = await this.serviceWrapper.describeLogGroups(params); const extractedLogGroupNames = logGroupData.logGroups.map(({ logGroupName }) => logGroupName); const logGroupsInIntervalsOfTwenty = []; while (extractedLogGroupNames.length) { logGroupsInIntervalsOfTwenty.push(extractedLogGroupNames.splice(0, this.LOG_GROUP_SIZE_REQUEST_LIMIT)); } return logGroupsInIntervalsOfTwenty; } runQuery = async (start, end, groupNames) => { const averageWatts = domain_1.AWS_CLOUD_CONSTANTS.MIN_WATTS_AVG + (domain_1.AWS_CLOUD_CONSTANTS.AVG_CPU_UTILIZATION_2020 / 100) * (domain_1.AWS_CLOUD_CONSTANTS.MAX_WATTS_AVG - domain_1.AWS_CLOUD_CONSTANTS.MIN_WATTS_AVG); const query = ` filter @type = "REPORT" | fields datefloor(@timestamp, 1d) as Date, @duration/1000 as DurationInS, @memorySize/1000000 as MemorySetInMB, ${averageWatts} * DurationInS/3600 * MemorySetInMB/1792 as wattsPerFunction | stats sum(wattsPerFunction) as Watts by Date | sort Date asc`; const params = { startTime: start.getTime(), endTime: end.getTime(), queryString: query, logGroupNames: groupNames, }; while (true) { const runningQueries = await this.serviceWrapper.describeCloudWatchLogsQueries({ status: 'Running', }); if (runningQueries.queries.length < this.MAX_CONCURRENT_LOG_QUERIES) break; await wait(this.POLL_INTERVAL); } const queryData = await this.serviceWrapper.startCloudWatchLogsQuery(params); return queryData.queryId; }; async getResults(queryId) { const params = { queryId: queryId, }; let cwResultsData; const startTime = Date.now(); while (true) { cwResultsData = await this.serviceWrapper.getCloudWatchLogQueryResults(params); if (cwResultsData.status !== 'Running' && cwResultsData.status !== 'Scheduled') break; if (Date.now() - startTime > this.TIMEOUT) { throw new Error(`CloudWatchLog request failed, status: ${cwResultsData.status}`); } await wait(this.POLL_INTERVAL); } return cwResultsData; } async getCosts(start, end, region) { const params = { TimePeriod: { Start: start.toISOString().substr(0, 10), End: end.toISOString().substr(0, 10), }, Filter: { And: [ { Dimensions: { Key: 'REGION', Values: [region], }, }, { Dimensions: { Key: 'SERVICE', Values: ['AWS Lambda'], }, }, ], }, Granularity: 'DAILY', GroupBy: [ { Key: 'USAGE_TYPE', Type: 'DIMENSION', }, ], Metrics: ['AmortizedCost'], }; return (0, CostMapper_1.getCostFromCostExplorer)(params, this.serviceWrapper); } } exports.default = Lambda; async function wait(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); } //# sourceMappingURL=Lambda.js.map