Skip to content

Commit

Permalink
feat: add monthly_hrs usage field for aws/azure/gcp VMs (infracost#1812)
Browse files Browse the repository at this point in the history
* first pass at ec2 instance monthly hours

* add monthly hour usage example to golden files

* handle usage quantities for other aws cost components

* update goldens

* remove unsupported elastic inference accelerator test cases

* support monthly_hours on azure vm resources

* add google cloud compute instance hourly usage

* fix a missed bit of formatting

* update usage golden files and add makefile aliases for them

* update usage field names to use _hrs

* use floats to allow decimal monthly_hrs values
  • Loading branch information
jessecureton authored and rshade committed Jul 18, 2022
1 parent 29788ba commit b12476f
Show file tree
Hide file tree
Showing 35 changed files with 548 additions and 225 deletions.
6 changes: 6 additions & 0 deletions Makefile
Expand Up @@ -75,6 +75,12 @@ test_cmd:
test_update_cmd:
INFRACOST_LOG_LEVEL=warn go test -timeout 30m $(LD_FLAGS) ./cmd/infracost $(or $(ARGS), -update -v -cover)

test_usage:
INFRACOST_LOG_LEVEL=warn go test -timeout 30m $(LD_FLAGS) ./internal/usage $(or $(ARGS), -v -cover)

test_update_usage:
INFRACOST_LOG_LEVEL=warn go test -timeout 30m $(LD_FLAGS) ./internal/usage $(or $(ARGS), -update -v -cover)

# Run AWS resource tests
test_aws:
INFRACOST_LOG_LEVEL=warn go test -timeout 30m $(LD_FLAGS) ./internal/providers/terraform/aws $(or $(ARGS), -v -cover)
Expand Down
7 changes: 7 additions & 0 deletions infracost-usage-example.yml
Expand Up @@ -232,6 +232,7 @@ resource_usage:
reserved_instance_payment_option: all_upfront # Payment option for Reserved Instances, can be: no_upfront, partial_upfront, all_upfront.
monthly_cpu_credit_hrs: 350 # Number of hours in the month where the instance is expected to burst. Only applicable with t2, t3 & t4 Instance types. T2 requires credit_specification to be unlimited.
vcpu_count: 2 # Number of the vCPUs for the instance type. Only applicable with t2, t3 & t4 Instance types. T2 requires credit_specification to be unlimited.
monthly_hrs: 450 # Monthly number of hours the instance ran for.

aws_fsx_windows_file_system.my_system:
backup_storage_gb: 10000 # Total storage used for backups in GB.
Expand Down Expand Up @@ -647,6 +648,9 @@ resource_usage:
google_compute_image.my_image:
storage_gb: 1000 # Total size of image storage in GB.

google_compute_instance.my_instance:
monthly_hrs: 450 # Monthly number of hours the instance ran for.

google_compute_machine_image.my_machine_image:
storage_gb: 1000 # Total size of machine image storage in GB.

Expand Down Expand Up @@ -965,6 +969,7 @@ resource_usage:
monthly_data_processed_gb: 100000 # Monthly data processed by the firewall in GB.

azurerm_linux_virtual_machine.my_linux_vm:
monthly_hrs: 450 # Monthly number of hours the instance ran for.
os_disk:
monthly_disk_operations: 2000000 # Number of disk operations (writes, reads, deletes) using a unit size of 256KiB.

Expand Down Expand Up @@ -1103,12 +1108,14 @@ resource_usage:
monthly_disk_operations: 100000 # Monthly number of disk operations (writes, reads, deletes) using a unit size of 256KiB per additional disk.

azurerm_virtual_machine.my_vm:
monthly_hrs: 450 # Monthly number of hours the instance ran for.
storage_os_disk:
monthly_disk_operations: 100000 # Monthly number of main disk operations (writes, reads, deletes) using a unit size of 256KiB.
storage_data_disk:
monthly_disk_operations: 100000 # Monthly number of disk operations (writes, reads, deletes) using a unit size of 256KiB per additional disk.

azurerm_windows_virtual_machine.my_windows_vm:
monthly_hrs: 450 # Monthly number of hours the instance ran for.
os_disk:
monthly_disk_operations: 2000000 # Number of disk operations (writes, reads, deletes) using a unit size of 256KiB.

Expand Down

Large diffs are not rendered by default.

Expand Up @@ -236,3 +236,20 @@ resource "aws_instance" "instance_withLaunchTemplateOverride" {
volume_size = 20
}
}

resource "aws_instance" "instance_withMonthlyHours" {
ami = "fake_ami"
instance_type = "t3.medium"
}

resource "aws_instance" "instance_ebsOptimized_withMonthlyHours" {
ami = "fake_ami"
instance_type = "r3.xlarge"
ebs_optimized = true
}

resource "aws_instance" "instance_detailedMonitoring_withMonthlyHours" {
ami = "fake_ami"
instance_type = "m3.large"
monitoring = true
}
Expand Up @@ -79,3 +79,12 @@ resource_usage:
aws_instance.instance_withLaunchTemplateOverride:
monthly_cpu_credit_hrs: 730
vcpu_count: 2

aws_instance.instance_withMonthlyHours:
monthly_hrs: 100

aws_instance.instance_ebsOptimized_withMonthlyHours:
monthly_hrs: 100

aws_instance.instance_detailedMonitoring_withMonthlyHours:
monthly_hrs: 100
Expand Up @@ -42,7 +42,7 @@ func aksClusterNodePool(name, region string, n gjson.Result, nodeCount decimal.D
Name: name,
}
instanceType := n.Get("vm_size").String()
costComponents = append(costComponents, linuxVirtualMachineCostComponent(region, instanceType))
costComponents = append(costComponents, linuxVirtualMachineCostComponent(region, instanceType, nil))
mainResource.CostComponents = costComponents
schema.MultiplyQuantities(mainResource, nodeCount)

Expand Down
22 changes: 16 additions & 6 deletions internal/providers/terraform/azure/linux_virtual_machine.go
Expand Up @@ -25,7 +25,12 @@ func NewAzureRMLinuxVirtualMachine(d *schema.ResourceData, u *schema.UsageData)

instanceType := d.Get("size").String()

costComponents := []*schema.CostComponent{linuxVirtualMachineCostComponent(region, instanceType)}
var monthlyHours *float64 = nil
if u != nil {
monthlyHours = u.GetFloat("monthly_hrs")
}

costComponents := []*schema.CostComponent{linuxVirtualMachineCostComponent(region, instanceType, monthlyHours)}

if d.Get("additional_capabilities.0.ultra_ssd_enabled").Bool() {
costComponents = append(costComponents, ultraSSDReservationCostComponent(region))
Expand All @@ -45,7 +50,7 @@ func NewAzureRMLinuxVirtualMachine(d *schema.ResourceData, u *schema.UsageData)
}
}

func linuxVirtualMachineCostComponent(region string, instanceType string) *schema.CostComponent {
func linuxVirtualMachineCostComponent(region string, instanceType string, monthlyHours *float64) *schema.CostComponent {
purchaseOption := "Consumption"
purchaseOptionLabel := "pay as you go"

Expand All @@ -56,11 +61,16 @@ func linuxVirtualMachineCostComponent(region string, instanceType string) *schem
instanceType = fmt.Sprintf("Standard_%s", instanceType)
}

qty := decimal.NewFromFloat(730)
if monthlyHours != nil {
qty = decimal.NewFromFloat(*monthlyHours)
}

return &schema.CostComponent{
Name: fmt.Sprintf("Instance usage (%s, %s)", purchaseOptionLabel, instanceType),
Unit: "hours",
UnitMultiplier: decimal.NewFromInt(1),
HourlyQuantity: decimalPtr(decimal.NewFromInt(1)),
Name: fmt.Sprintf("Instance usage (%s, %s)", purchaseOptionLabel, instanceType),
Unit: "hours",
UnitMultiplier: decimal.NewFromInt(1),
MonthlyQuantity: decimalPtr(qty),
ProductFilter: &schema.ProductFilter{
VendorName: strPtr("azure"),
Region: strPtr(region),
Expand Down
Expand Up @@ -18,7 +18,7 @@ func NewAzureRMLinuxVirtualMachineScaleSet(d *schema.ResourceData, u *schema.Usa

instanceType := d.Get("sku").String()

costComponents := []*schema.CostComponent{linuxVirtualMachineCostComponent(region, instanceType)}
costComponents := []*schema.CostComponent{linuxVirtualMachineCostComponent(region, instanceType, nil)}
subResources := make([]*schema.Resource, 0)

if d.Get("additional_capabilities.0.ultra_ssd_enabled").Bool() {
Expand Down
Expand Up @@ -13,6 +13,12 @@
├─ Storage (S4) 1 months $1.54
└─ Disk operations Monthly cost depends on usage: $0.0005 per 10k operations

azurerm_linux_virtual_machine.basic_b1_withMonthlyHours
├─ Instance usage (pay as you go, Standard_B1s) 100 hours $1.04
└─ os_disk
├─ Storage (S4) 1 months $1.54
└─ Disk operations Monthly cost depends on usage: $0.0005 per 10k operations

azurerm_linux_virtual_machine.standard_a2_ultra_enabled
├─ Instance usage (pay as you go, Standard_A2_v2) 730 hours $66.43
├─ Ultra disk reservation (if unattached) Monthly cost depends on usage: $4.38 per vCPU
Expand All @@ -31,7 +37,7 @@
└─ os_disk
└─ Storage (P4) 1 months $5.28

OVERALL TOTAL $357.95
OVERALL TOTAL $360.52
──────────────────────────────────
5 cloud resources were detected:
5 were estimated, all of which include usage-based costs, see https://infracost.io/usage-file
6 cloud resources were detected:
6 were estimated, all of which include usage-based costs, see https://infracost.io/usage-file
Expand Up @@ -137,3 +137,29 @@ resource "azurerm_linux_virtual_machine" "standard_a2_ultra_enabled" {
version = "latest"
}
}

resource "azurerm_linux_virtual_machine" "basic_b1_withMonthlyHours" {
name = "basic_b1"
resource_group_name = "fake_resource_group"
location = "eastus"

size = "Standard_B1s"
admin_username = "fakeuser"
admin_password = "fakepass"

network_interface_ids = [
"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testrg/providers/Microsoft.Network/networkInterfaces/fakenic",
]

os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}

source_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "16.04-LTS"
version = "latest"
}
}
Expand Up @@ -3,3 +3,5 @@ resource_usage:
azurerm_linux_virtual_machine.standard_a2_v2_custom_disk:
os_disk:
monthly_disk_operations: 20000
azurerm_linux_virtual_machine.basic_b1_withMonthlyHours:
monthly_hrs: 100
Expand Up @@ -8,6 +8,13 @@
├─ Storage (S4) 1 months $1.54
└─ Disk operations Monthly cost depends on usage: $0.0005 per 10k operations

azurerm_virtual_machine.linux_withMonthlyHours
├─ Instance usage (pay as you go, Standard_DS1_v2) 100 hours $7.30
├─ Ultra disk reservation (if unattached) Monthly cost depends on usage: $4.38 per vCPU
└─ storage_os_disk
├─ Storage (S4) 1 months $1.54
└─ Disk operations Monthly cost depends on usage: $0.0005 per 10k operations

azurerm_virtual_machine.windows
├─ Instance usage (pay as you go, Standard_DS1_v2) 730 hours $91.98
├─ Ultra disk reservation (if unattached) Monthly cost depends on usage: $4.38 per vCPU
Expand All @@ -27,10 +34,17 @@
├─ Provisioned IOPS 2,048 IOPS $101.66
└─ Throughput 8 MB/s $2.80

OVERALL TOTAL $385.16
azurerm_virtual_machine.windows_withMonthlyHours
├─ Instance usage (pay as you go, Standard_DS1_v2) 100 hours $12.60
├─ Ultra disk reservation (if unattached) Monthly cost depends on usage: $4.38 per vCPU
└─ storage_os_disk
├─ Storage (S4) 1 months $1.54
└─ Disk operations Monthly cost depends on usage: $0.0005 per 10k operations

OVERALL TOTAL $408.13
──────────────────────────────────
6 cloud resources were detected:
2 were estimated, all of which include usage-based costs, see https://infracost.io/usage-file
8 cloud resources were detected:
4 were estimated, all of which include usage-based costs, see https://infracost.io/usage-file
∙ 4 were free:
∙ 1 x azurerm_network_interface
∙ 1 x azurerm_resource_group
Expand Down
Expand Up @@ -61,8 +61,35 @@ resource "azurerm_virtual_machine" "linux" {
os_profile_linux_config {
disable_password_authentication = false
}
}

resource "azurerm_virtual_machine" "linux_withMonthlyHours" {
name = "vm"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
network_interface_ids = [azurerm_network_interface.main.id]
vm_size = "Standard_DS1_v2"

storage_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "16.04-LTS"
version = "latest"
}
storage_os_disk {
name = "myosdisk1"
caching = "ReadWrite"
create_option = "FromImage"
managed_disk_type = "Standard_LRS"
}
os_profile {
computer_name = "hostname"
admin_username = "testadmin"
admin_password = "Password1234!"
}
os_profile_linux_config {
disable_password_authentication = false
}
}

resource "azurerm_virtual_machine" "windows" {
Expand Down Expand Up @@ -113,3 +140,24 @@ resource "azurerm_virtual_machine" "windows" {
lun = 4
}
}

resource "azurerm_virtual_machine" "windows_withMonthlyHours" {
name = "vm"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
network_interface_ids = [azurerm_network_interface.main.id]
vm_size = "Standard_DS1_v2"

storage_os_disk {
name = "myosdisk1"
caching = "ReadWrite"
create_option = "FromImage"
managed_disk_type = "Standard_LRS"
os_type = "Windows"
}
os_profile {
computer_name = "hostname"
admin_username = "testadmin"
admin_password = "Password1234!"
}
}
Expand Up @@ -5,3 +5,7 @@ resource_usage:
monthly_disk_operations: 1000000
storage_data_disk:
monthly_disk_operations: 2000000
azurerm_virtual_machine.linux_withMonthlyHours:
monthly_hrs: 100
azurerm_virtual_machine.windows_withMonthlyHours:
monthly_hrs: 100
Expand Up @@ -13,6 +13,12 @@
├─ Storage (S4) 1 months $1.54
└─ Disk operations Monthly cost depends on usage: $0.0005 per 10k operations

azurerm_windows_virtual_machine.basic_a2_withMonthlyHours
├─ Instance usage (pay as you go, Basic_A2) 100 hours $13.30
└─ os_disk
├─ Storage (S4) 1 months $1.54
└─ Disk operations Monthly cost depends on usage: $0.0005 per 10k operations

azurerm_windows_virtual_machine.standard_a2_ultra_enabled
├─ Instance usage (pay as you go, Standard_A2_v2) 730 hours $99.28
├─ Ultra disk reservation (if unattached) Monthly cost depends on usage: $4.38 per vCPU
Expand All @@ -37,7 +43,7 @@
└─ os_disk
└─ Storage (P4) 1 months $5.28

OVERALL TOTAL $1,943.37
OVERALL TOTAL $1,958.20
──────────────────────────────────
6 cloud resources were detected:
6 were estimated, all of which include usage-based costs, see https://infracost.io/usage-file
7 cloud resources were detected:
7 were estimated, all of which include usage-based costs, see https://infracost.io/usage-file
Expand Up @@ -167,3 +167,29 @@ resource "azurerm_windows_virtual_machine" "Standard_E16-8as_v4" {
version = "fake"
}
}

resource "azurerm_windows_virtual_machine" "basic_a2_withMonthlyHours" {
name = "basic_a2"
resource_group_name = "fake_resource_group"
location = "eastus"

size = "Basic_A2"
admin_username = "fakeuser"
admin_password = "fakepass"

network_interface_ids = [
"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testrg/providers/Microsoft.Network/networkInterfaces/fakenic",
]

os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}

source_image_reference {
publisher = "MicrosoftWindowsServer"
offer = "WindowsServer"
sku = "2016-Datacenter"
version = "latest"
}
}
Expand Up @@ -3,3 +3,5 @@ resource_usage:
azurerm_windows_virtual_machine.standard_a2_v2_custom_disk:
os_disk:
monthly_disk_operations: 20000
azurerm_windows_virtual_machine.basic_a2_withMonthlyHours:
monthly_hrs: 100
10 changes: 8 additions & 2 deletions internal/providers/terraform/azure/virtual_machine.go
Expand Up @@ -32,13 +32,19 @@ func NewAzureRMVirtualMachine(d *schema.ResourceData, u *schema.UsageData) *sche
os = "Windows"
}

var monthlyHours *float64 = nil
if u != nil {
monthlyHours = u.GetFloat("monthly_hrs")
}

if strings.ToLower(os) == "windows" {
licenseType := d.Get("license_type").String()
costComponents = append(costComponents, windowsVirtualMachineCostComponent(region, instanceType, licenseType))
costComponents = append(costComponents, windowsVirtualMachineCostComponent(region, instanceType, licenseType, monthlyHours))
} else {
costComponents = append(costComponents, linuxVirtualMachineCostComponent(region, instanceType))
costComponents = append(costComponents, linuxVirtualMachineCostComponent(region, instanceType, monthlyHours))
}

// TODO: is this always assuming ultrassdreservation cost?
costComponents = append(costComponents, ultraSSDReservationCostComponent(region))

var storageOperations *decimal.Decimal
Expand Down

0 comments on commit b12476f

Please sign in to comment.