2
0

assets.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. package costmodel
  2. import (
  3. "fmt"
  4. "time"
  5. "github.com/opencost/opencost/core/pkg/log"
  6. "github.com/opencost/opencost/core/pkg/opencost"
  7. )
  8. // clampTimeToRange does not permit timestamps to exceed a given start, end
  9. // range, inclusive of start and end times. For examples:
  10. //
  11. // If time is within (start, end) inclusive, return that time:
  12. //
  13. // > S----T-------------E => T
  14. //
  15. // If time is before start, return start:
  16. //
  17. // > T S------------------E => S
  18. //
  19. // If time is after end, return end:
  20. //
  21. // > S------------------E T => E
  22. //
  23. // Note: if this function encounters a "zero" time (either time.Zero or Unix
  24. // timestamp 0) the time returned will be the given start time.
  25. func clampTimeToRange(t time.Time, start, end time.Time) time.Time {
  26. if t.Before(start) {
  27. return start
  28. }
  29. if t.After(end) {
  30. return end
  31. }
  32. return t
  33. }
  34. func (cm *CostModel) ComputeAssets(start, end time.Time) (*opencost.AssetSet, error) {
  35. assetSet := opencost.NewAssetSet(start, end)
  36. nodeMap, err := cm.ClusterNodes(start, end)
  37. if err != nil {
  38. return nil, fmt.Errorf("error computing node assets for %s: %w", opencost.NewClosedWindow(start, end), err)
  39. }
  40. lbMap, err := cm.ClusterLoadBalancers(start, end)
  41. if err != nil {
  42. return nil, fmt.Errorf("error computing load balancer assets for %s: %w", opencost.NewClosedWindow(start, end), err)
  43. }
  44. diskMap, err := cm.ClusterDisks(start, end)
  45. if err != nil {
  46. return nil, fmt.Errorf("error computing disk assets for %s: %w", opencost.NewClosedWindow(start, end), err)
  47. }
  48. clusterManagement, err := cm.ClusterManagement(start, end)
  49. if err != nil {
  50. return nil, fmt.Errorf("error computing cluster management assets for %s: %w", opencost.NewClosedWindow(start, end), err)
  51. }
  52. for _, d := range diskMap {
  53. // Clamp the start and end fields to the start and end of the window.
  54. // In the case that start and end are missing (e.g. due to the "active
  55. // minutes" metric being absent), both times will be set to the start
  56. // of the window -- representing zero "runtime" within the window.
  57. s := clampTimeToRange(d.Start, start, end)
  58. e := clampTimeToRange(d.End, start, end)
  59. hours := e.Sub(s).Hours()
  60. disk := opencost.NewDisk(d.Name, d.Cluster, d.ProviderID, s, e, opencost.NewWindow(&start, &end))
  61. cm.PropertiesFromCluster(disk.Properties)
  62. disk.Cost = d.Cost
  63. disk.ByteHours = d.Bytes * hours
  64. if d.BytesUsedAvgPtr != nil {
  65. byteHours := *d.BytesUsedAvgPtr * hours
  66. disk.ByteHoursUsed = &byteHours
  67. }
  68. if d.BytesUsedMaxPtr != nil {
  69. usageMax := *d.BytesUsedMaxPtr
  70. disk.ByteUsageMax = &usageMax
  71. }
  72. if d.Local {
  73. disk.Local = 1.0
  74. }
  75. disk.Breakdown = &opencost.Breakdown{
  76. Idle: d.Breakdown.Idle,
  77. System: d.Breakdown.System,
  78. User: d.Breakdown.User,
  79. Other: d.Breakdown.Other,
  80. }
  81. disk.StorageClass = d.StorageClass
  82. disk.VolumeName = d.VolumeName
  83. disk.ClaimName = d.ClaimName
  84. disk.ClaimNamespace = d.ClaimNamespace
  85. assetSet.Insert(disk, nil)
  86. }
  87. for _, lb := range lbMap {
  88. // Clamp the start and end fields to the start and end of the window.
  89. // In the case that start and end are missing (e.g. due to the "active
  90. // minutes" metric being absent), both times will be set to the start
  91. // of the window -- representing zero "runtime" within the window.
  92. s := clampTimeToRange(lb.Start, start, end)
  93. e := clampTimeToRange(lb.End, start, end)
  94. loadBalancer := opencost.NewLoadBalancer(lb.Name, lb.Cluster, lb.ProviderID, s, e, opencost.NewWindow(&start, &end), lb.Private, lb.Ip)
  95. cm.PropertiesFromCluster(loadBalancer.Properties)
  96. loadBalancer.Cost = lb.Cost
  97. assetSet.Insert(loadBalancer, nil)
  98. }
  99. for _, cman := range clusterManagement {
  100. cmAsset := opencost.NewClusterManagement(cman.Provisioner, cman.Cluster, opencost.NewClosedWindow(start, end))
  101. cm.PropertiesFromCluster(cmAsset.Properties)
  102. cmAsset.Cost = cman.Cost
  103. assetSet.Insert(cmAsset, nil)
  104. }
  105. for _, n := range nodeMap {
  106. // check label, to see if node from fargate, if so ignore.
  107. if n.Labels != nil {
  108. if value, ok := n.Labels["label_eks_amazonaws_com_compute_type"]; ok && value == "fargate" {
  109. continue
  110. }
  111. }
  112. // Clamp the start and end fields to the start and end of the window.
  113. // In the case that start and end are missing (e.g. due to the "active
  114. // minutes" metric being absent), both times will be set to the start
  115. // of the window -- representing zero "runtime" within the window.
  116. s := clampTimeToRange(n.Start, start, end)
  117. e := clampTimeToRange(n.End, start, end)
  118. hours := e.Sub(s).Hours()
  119. node := opencost.NewNode(n.Name, n.Cluster, n.ProviderID, s, e, opencost.NewWindow(&start, &end))
  120. cm.PropertiesFromCluster(node.Properties)
  121. node.NodeType = n.NodeType
  122. node.CPUCoreHours = n.CPUCores * hours
  123. node.RAMByteHours = n.RAMBytes * hours
  124. node.GPUHours = n.GPUCount * hours
  125. node.CPUBreakdown = &opencost.Breakdown{
  126. Idle: n.CPUBreakdown.Idle,
  127. System: n.CPUBreakdown.System,
  128. User: n.CPUBreakdown.User,
  129. Other: n.CPUBreakdown.Other,
  130. }
  131. node.RAMBreakdown = &opencost.Breakdown{
  132. Idle: n.RAMBreakdown.Idle,
  133. System: n.RAMBreakdown.System,
  134. User: n.RAMBreakdown.User,
  135. Other: n.RAMBreakdown.Other,
  136. }
  137. node.CPUCost = n.CPUCost
  138. node.GPUCost = n.GPUCost
  139. node.GPUCount = n.GPUCount
  140. node.RAMCost = n.RAMCost
  141. if n.Overhead != nil {
  142. node.Overhead = &opencost.NodeOverhead{
  143. RamOverheadFraction: n.Overhead.RamOverheadFraction,
  144. CpuOverheadFraction: n.Overhead.CpuOverheadFraction,
  145. OverheadCostFraction: ((n.Overhead.CpuOverheadFraction * n.CPUCost) +
  146. (n.Overhead.RamOverheadFraction * n.RAMCost)) / node.TotalCost(),
  147. }
  148. } else {
  149. node.Overhead = &opencost.NodeOverhead{}
  150. }
  151. node.Discount = n.Discount
  152. if n.Preemptible {
  153. node.Preemptible = 1.0
  154. }
  155. node.SetLabels(opencost.AssetLabels(n.Labels))
  156. assetSet.Insert(node, nil)
  157. }
  158. return assetSet, nil
  159. }
  160. func (cm *CostModel) ClusterDisks(start, end time.Time) (map[DiskIdentifier]*Disk, error) {
  161. return ClusterDisks(cm.DataSource, cm.Provider, start, end)
  162. }
  163. func (cm *CostModel) ClusterLoadBalancers(start, end time.Time) (map[LoadBalancerIdentifier]*LoadBalancer, error) {
  164. return ClusterLoadBalancers(cm.DataSource, start, end)
  165. }
  166. func (cm *CostModel) ClusterNodes(start, end time.Time) (map[NodeIdentifier]*Node, error) {
  167. return ClusterNodes(cm.DataSource, cm.Provider, start, end)
  168. }
  169. func (cm *CostModel) ClusterManagement(start, end time.Time) (map[ClusterManagementIdentifier]*ClusterManagementCost, error) {
  170. return ClusterManagement(cm.DataSource, start, end)
  171. }
  172. // propertiesFromCluster populates static cluster properties to individual asset properties
  173. func (cm *CostModel) PropertiesFromCluster(props *opencost.AssetProperties) {
  174. // If properties does not have cluster value, do nothing
  175. if props.Cluster == "" {
  176. return
  177. }
  178. clusterMap := cm.ClusterMap.AsMap()
  179. ci, ok := clusterMap[props.Cluster]
  180. if !ok {
  181. log.Debugf("CostMode.PropertiesFromCluster: cluster '%s' was not found in ClusterMap", props.Cluster)
  182. return
  183. }
  184. props.Project = ci.Project
  185. props.Account = ci.Account
  186. props.Provider = ci.Provider
  187. }