@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
JavaScript
;
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