carbonassets.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. package carbon
  2. import (
  3. "embed"
  4. "encoding/csv"
  5. "fmt"
  6. "strconv"
  7. "strings"
  8. "github.com/opencost/opencost/core/pkg/log"
  9. "github.com/opencost/opencost/core/pkg/opencost"
  10. "github.com/opencost/opencost/core/pkg/util"
  11. )
  12. //go:embed carbonlookupdata.csv
  13. var f embed.FS
  14. type carbonLookupKeyDisk struct {
  15. provider string
  16. region string
  17. }
  18. type carbonLookupKeyNode struct {
  19. provider string
  20. region string
  21. instanceType string
  22. }
  23. var carbonLookupNode map[carbonLookupKeyNode]float64
  24. var carbonLookupDisk map[carbonLookupKeyDisk]float64
  25. // Opencost does not build network types
  26. var carbonValidInstanceTypes map[string]string
  27. var carbonValidRegions map[string]string
  28. func init() {
  29. carbonData, err := f.ReadFile("carbonlookupdata.csv")
  30. if err != nil {
  31. log.Errorf("Error getting content of carbon lookup file: %s", err)
  32. return
  33. }
  34. reader := csv.NewReader(strings.NewReader(string(carbonData)))
  35. // skip header
  36. _, err = reader.Read()
  37. if err != nil {
  38. log.Errorf("Error reading carbon lookup data: %s", err)
  39. return
  40. }
  41. dat, err := reader.ReadAll()
  42. if err != nil {
  43. log.Errorf("Error reading carbon lookup data: %s", err)
  44. return
  45. }
  46. carbonLookupNode = make(map[carbonLookupKeyNode]float64)
  47. carbonLookupDisk = make(map[carbonLookupKeyDisk]float64)
  48. carbonValidInstanceTypes = make(map[string]string)
  49. carbonValidRegions = make(map[string]string)
  50. for _, carbonItem := range dat {
  51. if coeff, err := strconv.ParseFloat(carbonItem[5], 64); err != nil {
  52. panic(fmt.Errorf("error setting up carbon lookup table: malformed carbon cost '%s'", carbonItem[5]))
  53. } else {
  54. provider := carbonItem[0]
  55. region := carbonItem[1]
  56. instanceType := carbonItem[2]
  57. assetType := carbonItem[3]
  58. switch assetType {
  59. case "Node":
  60. carbonLookupNode[carbonLookupKeyNode{
  61. provider: provider,
  62. region: region,
  63. instanceType: instanceType,
  64. }] = coeff
  65. case "Disk":
  66. carbonLookupDisk[carbonLookupKeyDisk{
  67. provider: provider,
  68. region: region,
  69. }] = coeff
  70. }
  71. carbonValidInstanceTypes[instanceType] = provider
  72. carbonValidRegions[region] = provider
  73. }
  74. }
  75. }
  76. type CarbonRow struct {
  77. Co2e float64 `json:"co2e"`
  78. }
  79. func RelateCarbonAssets(as *opencost.AssetSet) (map[string]CarbonRow, error) {
  80. res := make(map[string]CarbonRow)
  81. for key, asset := range as.Assets {
  82. // If no valid region, default to per-provider calculated average
  83. region, _ := util.GetRegion(asset.GetLabels())
  84. if _, ok := carbonValidRegions[region]; !ok {
  85. region = "average-region"
  86. }
  87. // If no valid instance type, also default to per-provider calculated average
  88. instanceType, _ := util.GetInstanceType(asset.GetLabels())
  89. if _, ok := carbonValidInstanceTypes[instanceType]; !ok {
  90. region = "average-region"
  91. }
  92. provider := getProviderFromProviderID(asset.GetProperties().ProviderID)
  93. // If we're not able to parse the provider id, try to fetch the provider from the carbon data
  94. if provider == "" && region != "average-region" {
  95. provider = carbonValidRegions[region]
  96. } else {
  97. if asset.Type() == opencost.NodeAssetType || asset.Type() == opencost.DiskAssetType {
  98. log.DedupedErrorf(10, "Cannot infer region information for asset '%s'", asset.GetProperties().ProviderID)
  99. }
  100. }
  101. var carbonCoeff float64
  102. switch asset.Type() {
  103. case opencost.NodeAssetType:
  104. carbonCoeff = carbonLookupNode[carbonLookupKeyNode{
  105. provider: provider,
  106. region: region,
  107. instanceType: instanceType,
  108. }]
  109. case opencost.DiskAssetType:
  110. carbonCoeff = carbonLookupDisk[carbonLookupKeyDisk{
  111. provider: provider,
  112. region: region,
  113. }]
  114. }
  115. res[key] = CarbonRow{
  116. Co2e: carbonCoeff * asset.Minutes() / 60,
  117. }
  118. }
  119. return res, nil
  120. }
  121. func getProviderFromProviderID(providerid string) string {
  122. if strings.HasPrefix(providerid, "gke") {
  123. return opencost.GCPProvider
  124. } else if strings.HasPrefix(providerid, "i-") {
  125. return opencost.AWSProvider
  126. } else if strings.HasPrefix(providerid, "azure") {
  127. return opencost.AzureProvider
  128. }
  129. return ""
  130. }