UNPKG

@cloud-carbon-footprint/aws

Version:

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

240 lines (219 loc) 6.77 kB
/* * © 2021 Thoughtworks, Inc. */ import { AthenaClient } from '@aws-sdk/client-athena' import { CloudWatchClient } from '@aws-sdk/client-cloudwatch' import { CloudWatchLogsClient } from '@aws-sdk/client-cloudwatch-logs' import { CostExplorerClient } from '@aws-sdk/client-cost-explorer' import { GlueClient } from '@aws-sdk/client-glue' import { S3Client } from '@aws-sdk/client-s3' import type { AwsCredentialIdentity, Provider } from '@aws-sdk/types' type AwsClientConfig = { region: string credentials: AwsCredentialIdentity | Provider<AwsCredentialIdentity> } import { CloudProviderAccount, ComputeEstimator, EmbodiedEmissionsEstimator, ICloudService, MemoryEstimator, NetworkingEstimator, Region, StorageEstimator, UnknownEstimator, } from '@cloud-carbon-footprint/core' import { AWS_RECOMMENDATIONS_TARGETS, configLoader, EstimationResult, GroupBy, LookupTableInput, LookupTableOutput, RecommendationResult, } from '@cloud-carbon-footprint/common' import { CostAndUsageReports, EBS, EC2, ElastiCache, Lambda, RDS, RDSComputeService, RDSStorage, S3, ServiceWrapper, } from '../lib' import AWSCredentialsProvider from './AWSCredentialsProvider' import { AWS_CLOUD_CONSTANTS, AWS_EMISSIONS_FACTORS_METRIC_TON_PER_KWH, } from '../domain' import { Recommendations } from '../lib/Recommendations' export default class AWSAccount extends CloudProviderAccount { private readonly credentials: Provider<AwsCredentialIdentity> constructor( public id: string, public name: string, private regions: string[], ) { super() this.credentials = AWSCredentialsProvider.create(id) } async getDataForRegions( startDate: Date, endDate: Date, grouping: GroupBy, ): Promise<EstimationResult[]> { const results: EstimationResult[][] = [] for (const regionId of this.regions) { const regionEstimates: EstimationResult[] = await Promise.all( await this.getDataForRegion(regionId, startDate, endDate, grouping), ) results.push(regionEstimates) } return results.flat() } getDataForRegion( regionId: string, startDate: Date, endDate: Date, grouping: GroupBy, ): Promise<EstimationResult[]> { const awsServices = this.getServices(regionId) const awsConstants = { minWatts: AWS_CLOUD_CONSTANTS.MIN_WATTS_AVG, maxWatts: AWS_CLOUD_CONSTANTS.MAX_WATTS_AVG, powerUsageEffectiveness: AWS_CLOUD_CONSTANTS.getPUE(), } const region = new Region( regionId, awsServices, AWS_EMISSIONS_FACTORS_METRIC_TON_PER_KWH, awsConstants, ) return this.getRegionData( configLoader().AWS.NAME, region, startDate, endDate, grouping, ) } getServices(regionId: string): ICloudService[] { return configLoader().AWS.CURRENT_SERVICES.map(({ key }) => { return this.getService(key, regionId, this.credentials) }) } async getDataForRecommendations( recommendationTarget: AWS_RECOMMENDATIONS_TARGETS, ): Promise<RecommendationResult[]> { const serviceWrapper = this.createServiceWrapper( this.getClientConfig(configLoader().AWS.ATHENA_REGION, this.credentials), ) return await Recommendations.getRecommendations( recommendationTarget, serviceWrapper, ) } async getDataFromCostAndUsageReports( startDate: Date, endDate: Date, grouping: GroupBy, ): Promise<EstimationResult[]> { const costAndUsageReportsService = new CostAndUsageReports( new ComputeEstimator(), new StorageEstimator(AWS_CLOUD_CONSTANTS.SSDCOEFFICIENT), new StorageEstimator(AWS_CLOUD_CONSTANTS.HDDCOEFFICIENT), new NetworkingEstimator(AWS_CLOUD_CONSTANTS.NETWORKING_COEFFICIENT), new MemoryEstimator(AWS_CLOUD_CONSTANTS.MEMORY_COEFFICIENT), new UnknownEstimator(AWS_CLOUD_CONSTANTS.ESTIMATE_UNKNOWN_USAGE_BY), new EmbodiedEmissionsEstimator( AWS_CLOUD_CONSTANTS.SERVER_EXPECTED_LIFESPAN, ), this.createServiceWrapper( this.getClientConfig( configLoader().AWS.ATHENA_REGION, this.credentials, ), ), ) return await costAndUsageReportsService.getEstimates( startDate, endDate, grouping, ) } static async getCostAndUsageReportsDataFromInputData( inputData: LookupTableInput[], ): Promise<LookupTableOutput[]> { const costAndUsageReportsService = new CostAndUsageReports( new ComputeEstimator(), new StorageEstimator(AWS_CLOUD_CONSTANTS.SSDCOEFFICIENT), new StorageEstimator(AWS_CLOUD_CONSTANTS.HDDCOEFFICIENT), new NetworkingEstimator(AWS_CLOUD_CONSTANTS.NETWORKING_COEFFICIENT), new MemoryEstimator(AWS_CLOUD_CONSTANTS.MEMORY_COEFFICIENT), new UnknownEstimator(AWS_CLOUD_CONSTANTS.ESTIMATE_UNKNOWN_USAGE_BY), new EmbodiedEmissionsEstimator( AWS_CLOUD_CONSTANTS.SERVER_EXPECTED_LIFESPAN, ), ) return await costAndUsageReportsService.getEstimatesFromInputData(inputData) } private getService( key: string, region: string, credentials: AwsClientConfig['credentials'], ): ICloudService { if (this.services[key] === undefined) throw new Error('Unsupported service: ' + key) const options = this.getClientConfig(region, credentials) return this.services[key](options) } private getClientConfig( region: string, credentials: AwsClientConfig['credentials'], ): AwsClientConfig { return { region, credentials } } private createServiceWrapper(options: AwsClientConfig) { return new ServiceWrapper( new CloudWatchClient(options), new CloudWatchLogsClient(options), new CostExplorerClient({ region: configLoader().AWS.IS_AWS_GLOBAL ? 'us-east-1' : 'cn-northwest-1', credentials: options.credentials, }), new S3Client(options), new AthenaClient(options), new GlueClient(options), ) } private services: { [id: string]: (options: AwsClientConfig) => ICloudService } = { ebs: (options) => { return new EBS(this.createServiceWrapper(options)) }, s3: (options) => { return new S3(this.createServiceWrapper(options)) }, ec2: (options) => { return new EC2(this.createServiceWrapper(options)) }, elasticache: (options) => { return new ElastiCache(this.createServiceWrapper(options)) }, rds: (options) => { return new RDS( new RDSComputeService(this.createServiceWrapper(options)), new RDSStorage(this.createServiceWrapper(options)), ) }, lambda: (options) => { return new Lambda(120000, 1000, this.createServiceWrapper(options)) }, } }