assets.go 7.2 KB

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