@cloud-carbon-footprint/aws
Version:
The core logic to get cloud usage data and estimate energy and carbon emissions from Amazon Web Services.
242 lines (218 loc) • 6.9 kB
text/typescript
/*
* © 2021 Thoughtworks, Inc.
*/
import {
BillingDataRow,
COMPUTE_PROCESSOR_TYPES,
} from '@cloud-carbon-footprint/core'
import { containsAny, TagCollection } from '@cloud-carbon-footprint/common'
import {
BURSTABLE_INSTANCE_BASELINE_UTILIZATION,
EC2_INSTANCE_TYPES,
GPU_INSTANCES_TYPES,
INSTANCE_TYPE_COMPUTE_PROCESSOR_MAPPING,
INSTANCE_TYPE_GPU_PROCESSOR_MAPPING,
MSK_INSTANCE_TYPES,
REDSHIFT_INSTANCE_TYPES,
} from './AWSInstanceTypes'
import { AWS_CLOUD_CONSTANTS } from '../domain'
import { concat } from 'ramda'
import { AWS_REPLICATION_FACTORS_FOR_SERVICES } from './ReplicationFactors'
import { KNOWN_USAGE_UNITS } from './CostAndUsageTypes'
const GLUE_VCPUS_PER_USAGE = 4
const SIMPLE_DB_VCPUS_PER_USAGE = 1
export default class CostAndUsageReportsRow extends BillingDataRow {
constructor(
timestamp: Date,
accountId: string,
accountName: string,
region: string,
serviceName: string,
usageType: string,
usageUnit: string,
vCpus: number | null,
usageAmount: number,
cost: number,
tags: TagCollection,
) {
super({
timestamp,
accountId,
accountName,
region,
serviceName,
usageType,
usageUnit: cleanUsageUnit(usageUnit, serviceName, usageType),
vCpus,
usageAmount: cleanUsageAmount(usageAmount, usageUnit, serviceName),
cost,
tags,
})
this.vCpuHours = this.getVCpuHours(this.vCpus)
this.gpuHours = this.getGpuHours()
this.cloudProvider = 'AWS'
this.instanceType = this.parseInstanceTypeFromUsageType()
this.replicationFactor = this.getReplicationFactor()
}
private getVCpuHours(vCpuFromReport: number): number {
const instanceType = this.extractInstanceTypeFromUsageType()
// When the service is AWS Glue, 4 virtual CPUs are provisioned (from AWS Docs).
if (this.serviceName === 'AWSGlue')
return GLUE_VCPUS_PER_USAGE * this.usageAmount
if (this.serviceName === 'AmazonSimpleDB')
return SIMPLE_DB_VCPUS_PER_USAGE * this.usageAmount
// 1 ACU is 1/4 of a vCPU
if (
this.usageType.includes('Aurora:ServerlessUsage') ||
this.usageType.includes('Aurora:ServerlessV2Usage') ||
this.usageType.includes('Aurora:ServerlessV2IOOptimizedUsage') ||
this.usageType.includes('Neptune:ServerlessUsage')
)
return this.usageAmount / 4
if (
containsAny(
[
'Fargate-vCPU-Hours',
'Fargate-ARM-vCPU-Hours',
'Fargate-Windows-vCPU-Hours',
'CPUCredits',
'Consumption-based:vCPU',
'FARGATE-vCPUHours',
'AppRunner-vCPU-hours',
'SERVERLESS-vCPUHours',
'DatabaseInsights-vCPU-Hours',
],
this.usageType,
)
)
return this.usageAmount
if (
containsAny(
Object.keys(BURSTABLE_INSTANCE_BASELINE_UTILIZATION),
this.usageType,
)
) {
return (
this.getBurstableInstanceVCPu(instanceType.split('.', 2).join('.')) *
this.usageAmount
)
}
if (!vCpuFromReport)
return this.extractVCpuFromInstanceType(instanceType) * this.usageAmount
return vCpuFromReport * this.usageAmount
}
private getGpuHours(): number {
const instanceType = this.extractInstanceTypeFromUsageType()
return GPU_INSTANCES_TYPES[instanceType] * this.usageAmount || 0
}
private extractInstanceTypeFromUsageType(): string {
return this.usageType
.split(':')
.pop()
.replace(
/^((db|cache|dax|dms|ml|mq|KernelGateway-ml|CodeEditor-ml|JupyterLab-ml|Notebook-sc|.+Kafka)\.)/,
'',
)
}
private getBurstableInstanceVCPu(instanceType: string) {
return (
this.extractVCpuFromInstanceType(instanceType) *
(BURSTABLE_INSTANCE_BASELINE_UTILIZATION[instanceType] /
AWS_CLOUD_CONSTANTS.AVG_CPU_UTILIZATION_2020)
)
}
private extractVCpuFromInstanceType(instanceType: string): number {
const [instanceFamily, instanceSize] = instanceType.split('.')
if (this.usageType.includes('Kafka'))
return MSK_INSTANCE_TYPES[`Kafka${this.usageType.split('Kafka').pop()}`]
if (this.usageType.includes('Express'))
return MSK_INSTANCE_TYPES[
`express${this.usageType.split('Express').pop()}`
]
if (this.serviceName === 'AmazonRedshift')
return REDSHIFT_INSTANCE_TYPES[instanceFamily]?.[instanceSize]?.[0] / 3600
return EC2_INSTANCE_TYPES[instanceFamily]?.[instanceSize]?.[0]
}
public parseInstanceTypeFromUsageType(): string {
// Sometimes the CUR usage type for xlarge instances returns only as 'xl', so append 'arge'
// to make it consistent with other responce data.
if (this.usageType.endsWith('xl'))
this.usageType = this.usageType.concat('arge')
const prefixes = ['db', 'cache', 'Kafka']
const includesPrefix = prefixes.find((prefix) =>
this.usageType.includes(prefix),
)
return includesPrefix
? this.usageType.split(concat(includesPrefix, '.')).pop()
: this.usageType.split(':').pop()
}
private getReplicationFactor(): number {
return (
(AWS_REPLICATION_FACTORS_FOR_SERVICES[this.serviceName] &&
AWS_REPLICATION_FACTORS_FOR_SERVICES[this.serviceName](
this.usageType,
this.region,
)) ||
AWS_REPLICATION_FACTORS_FOR_SERVICES.DEFAULT()
)
}
public getComputeProcessors(): string[] {
if (this.serviceName === 'AWSLambda') {
if (this.usageType.endsWith('-ARM')) {
return [COMPUTE_PROCESSOR_TYPES.AWS_GRAVITON_2]
} else {
return [COMPUTE_PROCESSOR_TYPES.UNKNOWN]
}
}
return (
INSTANCE_TYPE_COMPUTE_PROCESSOR_MAPPING[this.instanceType] || [
COMPUTE_PROCESSOR_TYPES.UNKNOWN,
]
)
}
public getGPUComputeProcessors(): string[] {
if (this.serviceName === 'AWSLambda') {
return [COMPUTE_PROCESSOR_TYPES.UNKNOWN]
}
return (
INSTANCE_TYPE_GPU_PROCESSOR_MAPPING[this.instanceType] || [
COMPUTE_PROCESSOR_TYPES.UNKNOWN,
]
)
}
}
const cleanUsageUnit = (
rawUsageUnit: string,
serviceName: string,
usageType: string,
): string => {
if (
usageType.includes('Fargate-GB-Hours') ||
usageType.includes('Fargate-ARM-GB-Hours')
) {
return KNOWN_USAGE_UNITS.GB_HOURS
}
if (isRedshiftComputeUsage(serviceName, rawUsageUnit)) {
return KNOWN_USAGE_UNITS.HOURS_1
}
return rawUsageUnit
}
const isRedshiftComputeUsage = (
serviceName: string,
usageUnit: string,
): boolean => {
return (
serviceName === 'AmazonRedshift' &&
usageUnit === KNOWN_USAGE_UNITS.SECONDS_1
)
}
const cleanUsageAmount = (
rawUsageAmount: number,
rawUsageUnit: string,
serviceName: string,
): number => {
if (isRedshiftComputeUsage(serviceName, rawUsageUnit)) {
return rawUsageAmount / 3600
}
return rawUsageAmount
}