totals.go 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763
  1. package kubecost
  2. import (
  3. "errors"
  4. "fmt"
  5. "strconv"
  6. "time"
  7. "github.com/opencost/opencost/pkg/log"
  8. "github.com/patrickmn/go-cache"
  9. )
  10. // AllocationTotals represents aggregate costs of all Allocations for
  11. // a given cluster or tuple of (cluster, node) between a given start and end
  12. // time, where the costs are aggregated per-resource. AllocationTotals
  13. // is designed to be used as a pre-computed intermediate data structure when
  14. // contextual knowledge is required to carry out a task, but computing totals
  15. // on-the-fly would be expensive; e.g. idle allocation; sharing coefficients
  16. // for idle or shared resources, etc.
  17. type AllocationTotals struct {
  18. Start time.Time `json:"start"`
  19. End time.Time `json:"end"`
  20. Cluster string `json:"cluster"`
  21. Node string `json:"node"`
  22. Count int `json:"count"`
  23. CPUCost float64 `json:"cpuCost"`
  24. CPUCostAdjustment float64 `json:"cpuCostAdjustment"`
  25. GPUCost float64 `json:"gpuCost"`
  26. GPUCostAdjustment float64 `json:"gpuCostAdjustment"`
  27. LoadBalancerCost float64 `json:"loadBalancerCost"`
  28. LoadBalancerCostAdjustment float64 `json:"loadBalancerCostAdjustment"`
  29. NetworkCost float64 `json:"networkCost"`
  30. NetworkCostAdjustment float64 `json:"networkCostAdjustment"`
  31. PersistentVolumeCost float64 `json:"persistentVolumeCost"`
  32. PersistentVolumeCostAdjustment float64 `json:"persistentVolumeCostAdjustment"`
  33. RAMCost float64 `json:"ramCost"`
  34. RAMCostAdjustment float64 `json:"ramCostAdjustment"`
  35. }
  36. // ClearAdjustments sets all adjustment fields to 0.0
  37. func (art *AllocationTotals) ClearAdjustments() {
  38. art.CPUCostAdjustment = 0.0
  39. art.GPUCostAdjustment = 0.0
  40. art.LoadBalancerCostAdjustment = 0.0
  41. art.NetworkCostAdjustment = 0.0
  42. art.PersistentVolumeCostAdjustment = 0.0
  43. art.RAMCostAdjustment = 0.0
  44. }
  45. // Clone deep copies the AllocationTotals
  46. func (art *AllocationTotals) Clone() *AllocationTotals {
  47. return &AllocationTotals{
  48. Start: art.Start,
  49. End: art.End,
  50. Cluster: art.Cluster,
  51. Node: art.Node,
  52. Count: art.Count,
  53. CPUCost: art.CPUCost,
  54. CPUCostAdjustment: art.CPUCostAdjustment,
  55. GPUCost: art.GPUCost,
  56. GPUCostAdjustment: art.GPUCostAdjustment,
  57. LoadBalancerCost: art.LoadBalancerCost,
  58. LoadBalancerCostAdjustment: art.LoadBalancerCostAdjustment,
  59. NetworkCost: art.NetworkCost,
  60. NetworkCostAdjustment: art.NetworkCostAdjustment,
  61. PersistentVolumeCost: art.PersistentVolumeCost,
  62. PersistentVolumeCostAdjustment: art.PersistentVolumeCostAdjustment,
  63. RAMCost: art.RAMCost,
  64. RAMCostAdjustment: art.RAMCostAdjustment,
  65. }
  66. }
  67. // TotalCPUCost returns CPU cost with adjustment.
  68. func (art *AllocationTotals) TotalCPUCost() float64 {
  69. return art.CPUCost + art.CPUCostAdjustment
  70. }
  71. // TotalGPUCost returns GPU cost with adjustment.
  72. func (art *AllocationTotals) TotalGPUCost() float64 {
  73. return art.GPUCost + art.GPUCostAdjustment
  74. }
  75. // TotalLoadBalancerCost returns LoadBalancer cost with adjustment.
  76. func (art *AllocationTotals) TotalLoadBalancerCost() float64 {
  77. return art.LoadBalancerCost + art.LoadBalancerCostAdjustment
  78. }
  79. // TotalNetworkCost returns Network cost with adjustment.
  80. func (art *AllocationTotals) TotalNetworkCost() float64 {
  81. return art.NetworkCost + art.NetworkCostAdjustment
  82. }
  83. // TotalPersistentVolumeCost returns PersistentVolume cost with adjustment.
  84. func (art *AllocationTotals) TotalPersistentVolumeCost() float64 {
  85. return art.PersistentVolumeCost + art.PersistentVolumeCostAdjustment
  86. }
  87. // TotalRAMCost returns RAM cost with adjustment.
  88. func (art *AllocationTotals) TotalRAMCost() float64 {
  89. return art.RAMCost + art.RAMCostAdjustment
  90. }
  91. // TotalCost returns the sum of all costs.
  92. func (art *AllocationTotals) TotalCost() float64 {
  93. return art.TotalCPUCost() + art.TotalGPUCost() + art.TotalLoadBalancerCost() +
  94. art.TotalNetworkCost() + art.TotalPersistentVolumeCost() + art.TotalRAMCost()
  95. }
  96. // ComputeAllocationTotals totals the resource costs of the given AllocationSet
  97. // using the given property, i.e. cluster or node, where "node" really means to
  98. // use the fully-qualified (cluster, node) tuple.
  99. func ComputeAllocationTotals(as *AllocationSet, prop string) map[string]*AllocationTotals {
  100. arts := map[string]*AllocationTotals{}
  101. for _, alloc := range as.Allocations {
  102. // Do not count idle or unmounted allocations
  103. if alloc.IsIdle() || alloc.IsUnmounted() {
  104. continue
  105. }
  106. // Default to computing totals by Cluster, but allow override to use Node.
  107. key := alloc.Properties.Cluster
  108. if prop == AllocationNodeProp {
  109. key = fmt.Sprintf("%s/%s", alloc.Properties.Cluster, alloc.Properties.Node)
  110. }
  111. if _, ok := arts[key]; !ok {
  112. arts[key] = &AllocationTotals{
  113. Start: alloc.Start,
  114. End: alloc.End,
  115. Cluster: alloc.Properties.Cluster,
  116. Node: alloc.Properties.Node,
  117. }
  118. }
  119. if arts[key].Start.After(alloc.Start) {
  120. arts[key].Start = alloc.Start
  121. }
  122. if arts[key].End.Before(alloc.End) {
  123. arts[key].End = alloc.End
  124. }
  125. if arts[key].Node != alloc.Properties.Node {
  126. arts[key].Node = ""
  127. }
  128. arts[key].Count++
  129. arts[key].CPUCost += alloc.CPUCost
  130. arts[key].CPUCostAdjustment += alloc.CPUCostAdjustment
  131. arts[key].GPUCost += alloc.GPUCost
  132. arts[key].GPUCostAdjustment += alloc.GPUCostAdjustment
  133. arts[key].LoadBalancerCost += alloc.LoadBalancerCost
  134. arts[key].LoadBalancerCostAdjustment += alloc.LoadBalancerCostAdjustment
  135. arts[key].NetworkCost += alloc.NetworkCost
  136. arts[key].NetworkCostAdjustment += alloc.NetworkCostAdjustment
  137. arts[key].PersistentVolumeCost += alloc.PVCost() // NOTE: PVCost() does not include adjustment
  138. arts[key].PersistentVolumeCostAdjustment += alloc.PVCostAdjustment
  139. arts[key].RAMCost += alloc.RAMCost
  140. arts[key].RAMCostAdjustment += alloc.RAMCostAdjustment
  141. }
  142. return arts
  143. }
  144. // AllocationTotalsSet represents totals, summed by both "cluster" and "node"
  145. // for a given window of time.
  146. type AllocationTotalsSet struct {
  147. Cluster map[string]*AllocationTotals `json:"cluster"`
  148. Node map[string]*AllocationTotals `json:"node"`
  149. Window Window `json:"window"`
  150. }
  151. func NewAllocationTotalsSet(window Window, byCluster, byNode map[string]*AllocationTotals) *AllocationTotalsSet {
  152. return &AllocationTotalsSet{
  153. Cluster: byCluster,
  154. Node: byNode,
  155. Window: window.Clone(),
  156. }
  157. }
  158. // AssetTotals represents aggregate costs of all Assets for a given
  159. // cluster or tuple of (cluster, node) between a given start and end time,
  160. // where the costs are aggregated per-resource. AssetTotals is designed
  161. // to be used as a pre-computed intermediate data structure when contextual
  162. // knowledge is required to carry out a task, but computing totals on-the-fly
  163. // would be expensive; e.g. idle allocation, shared tenancy costs
  164. type AssetTotals struct {
  165. Start time.Time `json:"start"`
  166. End time.Time `json:"end"`
  167. Cluster string `json:"cluster"`
  168. Node string `json:"node"`
  169. Count int `json:"count"`
  170. AttachedVolumeCost float64 `json:"attachedVolumeCost"`
  171. AttachedVolumeCostAdjustment float64 `json:"attachedVolumeCostAdjustment"`
  172. ClusterManagementCost float64 `json:"clusterManagementCost"`
  173. ClusterManagementCostAdjustment float64 `json:"clusterManagementCostAdjustment"`
  174. CPUCost float64 `json:"cpuCost"`
  175. CPUCostAdjustment float64 `json:"cpuCostAdjustment"`
  176. GPUCost float64 `json:"gpuCost"`
  177. GPUCostAdjustment float64 `json:"gpuCostAdjustment"`
  178. LoadBalancerCost float64 `json:"loadBalancerCost"`
  179. LoadBalancerCostAdjustment float64 `json:"loadBalancerCostAdjustment"`
  180. PersistentVolumeCost float64 `json:"persistentVolumeCost"`
  181. PersistentVolumeCostAdjustment float64 `json:"persistentVolumeCostAdjustment"`
  182. RAMCost float64 `json:"ramCost"`
  183. RAMCostAdjustment float64 `json:"ramCostAdjustment"`
  184. }
  185. // ClearAdjustments sets all adjustment fields to 0.0
  186. func (art *AssetTotals) ClearAdjustments() {
  187. art.AttachedVolumeCostAdjustment = 0.0
  188. art.ClusterManagementCostAdjustment = 0.0
  189. art.CPUCostAdjustment = 0.0
  190. art.GPUCostAdjustment = 0.0
  191. art.LoadBalancerCostAdjustment = 0.0
  192. art.PersistentVolumeCostAdjustment = 0.0
  193. art.RAMCostAdjustment = 0.0
  194. }
  195. // Clone deep copies the AssetTotals
  196. func (art *AssetTotals) Clone() *AssetTotals {
  197. return &AssetTotals{
  198. Start: art.Start,
  199. End: art.End,
  200. Cluster: art.Cluster,
  201. Node: art.Node,
  202. Count: art.Count,
  203. AttachedVolumeCost: art.AttachedVolumeCost,
  204. AttachedVolumeCostAdjustment: art.AttachedVolumeCostAdjustment,
  205. ClusterManagementCost: art.ClusterManagementCost,
  206. ClusterManagementCostAdjustment: art.ClusterManagementCostAdjustment,
  207. CPUCost: art.CPUCost,
  208. CPUCostAdjustment: art.CPUCostAdjustment,
  209. GPUCost: art.GPUCost,
  210. GPUCostAdjustment: art.GPUCostAdjustment,
  211. LoadBalancerCost: art.LoadBalancerCost,
  212. LoadBalancerCostAdjustment: art.LoadBalancerCostAdjustment,
  213. PersistentVolumeCost: art.PersistentVolumeCost,
  214. PersistentVolumeCostAdjustment: art.PersistentVolumeCostAdjustment,
  215. RAMCost: art.RAMCost,
  216. RAMCostAdjustment: art.RAMCostAdjustment,
  217. }
  218. }
  219. // TotalAttachedVolumeCost returns CPU cost with adjustment.
  220. func (art *AssetTotals) TotalAttachedVolumeCost() float64 {
  221. return art.AttachedVolumeCost + art.AttachedVolumeCostAdjustment
  222. }
  223. // TotalClusterManagementCost returns ClusterManagement cost with adjustment.
  224. func (art *AssetTotals) TotalClusterManagementCost() float64 {
  225. return art.ClusterManagementCost + art.ClusterManagementCostAdjustment
  226. }
  227. // TotalCPUCost returns CPU cost with adjustment.
  228. func (art *AssetTotals) TotalCPUCost() float64 {
  229. return art.CPUCost + art.CPUCostAdjustment
  230. }
  231. // TotalGPUCost returns GPU cost with adjustment.
  232. func (art *AssetTotals) TotalGPUCost() float64 {
  233. return art.GPUCost + art.GPUCostAdjustment
  234. }
  235. // TotalLoadBalancerCost returns LoadBalancer cost with adjustment.
  236. func (art *AssetTotals) TotalLoadBalancerCost() float64 {
  237. return art.LoadBalancerCost + art.LoadBalancerCostAdjustment
  238. }
  239. // TotalPersistentVolumeCost returns PersistentVolume cost with adjustment.
  240. func (art *AssetTotals) TotalPersistentVolumeCost() float64 {
  241. return art.PersistentVolumeCost + art.PersistentVolumeCostAdjustment
  242. }
  243. // TotalRAMCost returns RAM cost with adjustment.
  244. func (art *AssetTotals) TotalRAMCost() float64 {
  245. return art.RAMCost + art.RAMCostAdjustment
  246. }
  247. // TotalCost returns the sum of all costs
  248. func (art *AssetTotals) TotalCost() float64 {
  249. return art.TotalAttachedVolumeCost() + art.TotalClusterManagementCost() +
  250. art.TotalCPUCost() + art.TotalGPUCost() + art.TotalLoadBalancerCost() +
  251. art.TotalPersistentVolumeCost() + art.TotalRAMCost()
  252. }
  253. // ComputeAssetTotals totals the resource costs of the given AssetSet,
  254. // using the given property, i.e. cluster or node, where "node" really means to
  255. // use the fully-qualified (cluster, node) tuple.
  256. // NOTE: we're not capturing LoadBalancers here yet, but only because we don't
  257. // yet need them. They could be added.
  258. func ComputeAssetTotals(as *AssetSet, prop AssetProperty) map[string]*AssetTotals {
  259. arts := map[string]*AssetTotals{}
  260. // Attached disks are tracked by matching their name with the name of the
  261. // node, as is standard for attached disks.
  262. nodeNames := map[string]bool{}
  263. disks := map[string]*Disk{}
  264. for _, node := range as.Nodes {
  265. // Default to computing totals by Cluster, but allow override to use Node.
  266. key := node.Properties.Cluster
  267. if prop == AssetNodeProp {
  268. key = fmt.Sprintf("%s/%s", node.Properties.Cluster, node.Properties.Name)
  269. }
  270. // Add node name to list of node names. (These are to be used later
  271. // for attached volumes.)
  272. nodeNames[fmt.Sprintf("%s/%s", node.Properties.Cluster, node.Properties.Name)] = true
  273. // adjustmentRate is used to scale resource costs proportionally
  274. // by the adjustment. This is necessary because we only get one
  275. // adjustment per Node, not one per-resource-per-Node.
  276. //
  277. // e.g. total cost = $90 (cost = $100, adjustment = -$10) => 0.9000 ( 90 / 100)
  278. // e.g. total cost = $150 (cost = $450, adjustment = -$300) => 0.3333 (150 / 450)
  279. // e.g. total cost = $150 (cost = $100, adjustment = $50) => 1.5000 (150 / 100)
  280. adjustmentRate := 1.0
  281. if node.TotalCost()-node.Adjustment == 0 {
  282. // If (totalCost - adjustment) is 0.0 then adjustment cancels
  283. // the entire node cost and we should make everything 0
  284. // without dividing by 0.
  285. adjustmentRate = 0.0
  286. log.DedupedWarningf(5, "ComputeTotals: node cost adjusted to $0.00 for %s", node.Properties.Name)
  287. } else if node.Adjustment != 0.0 {
  288. // adjustmentRate is the ratio of cost-with-adjustment (i.e. TotalCost)
  289. // to cost-without-adjustment (i.e. TotalCost - Adjustment).
  290. adjustmentRate = node.TotalCost() / (node.TotalCost() - node.Adjustment)
  291. }
  292. // 1. Start with raw, measured resource cost
  293. // 2. Apply discount to get discounted resource cost
  294. // 3. Apply adjustment to get final "adjusted" resource cost
  295. // 4. Subtract (3 - 2) to get adjustment in doller-terms
  296. // 5. Use (2 + 4) as total cost, so (2) is "cost" and (4) is "adjustment"
  297. // Example:
  298. // - node.CPUCost = 10.00
  299. // - node.Discount = 0.20 // We assume a 20% discount
  300. // - adjustmentRate = 0.75 // CUR says we need to reduce to 75% of our post-discount node cost
  301. //
  302. // 1. See above
  303. // 2. discountedCPUCost = 10.00 * (1.0 - 0.2) = 8.00
  304. // 3. adjustedCPUCost = 8.00 * 0.75 = 6.00 // this is the actual cost according to the CUR
  305. // 4. adjustment = 6.00 - 8.00 = -2.00
  306. // 5. totalCost = 6.00, which is the sum of (2) cost = 8.00 and (4) adjustment = -2.00
  307. discountedCPUCost := node.CPUCost * (1.0 - node.Discount)
  308. adjustedCPUCost := discountedCPUCost * adjustmentRate
  309. cpuCostAdjustment := adjustedCPUCost - discountedCPUCost
  310. discountedRAMCost := node.RAMCost * (1.0 - node.Discount)
  311. adjustedRAMCost := discountedRAMCost * adjustmentRate
  312. ramCostAdjustment := adjustedRAMCost - discountedRAMCost
  313. adjustedGPUCost := node.GPUCost * adjustmentRate
  314. gpuCostAdjustment := adjustedGPUCost - node.GPUCost
  315. if _, ok := arts[key]; !ok {
  316. arts[key] = &AssetTotals{
  317. Start: node.Start,
  318. End: node.End,
  319. Cluster: node.Properties.Cluster,
  320. Node: node.Properties.Name,
  321. }
  322. }
  323. if arts[key].Start.After(node.Start) {
  324. arts[key].Start = node.Start
  325. }
  326. if arts[key].End.Before(node.End) {
  327. arts[key].End = node.End
  328. }
  329. if arts[key].Node != node.Properties.Name {
  330. arts[key].Node = ""
  331. }
  332. arts[key].Count++
  333. // TotalCPUCost will be discounted cost + adjustment
  334. arts[key].CPUCost += discountedCPUCost
  335. arts[key].CPUCostAdjustment += cpuCostAdjustment
  336. // TotalRAMCost will be discounted cost + adjustment
  337. arts[key].RAMCost += discountedRAMCost
  338. arts[key].RAMCostAdjustment += ramCostAdjustment
  339. // TotalGPUCost will be discounted cost + adjustment
  340. arts[key].GPUCost += node.GPUCost
  341. arts[key].GPUCostAdjustment += gpuCostAdjustment
  342. }
  343. // Only record LoadBalancer and ClusterManagement when prop
  344. // is cluster. We can't breakdown these types by Node.
  345. if prop == AssetClusterProp {
  346. for _, lb := range as.LoadBalancers {
  347. key := lb.Properties.Cluster
  348. if _, ok := arts[key]; !ok {
  349. arts[key] = &AssetTotals{
  350. Start: lb.Start,
  351. End: lb.End,
  352. Cluster: lb.Properties.Cluster,
  353. }
  354. }
  355. arts[key].Count++
  356. arts[key].LoadBalancerCost += lb.Cost
  357. arts[key].LoadBalancerCostAdjustment += lb.Adjustment
  358. }
  359. for _, cm := range as.ClusterManagement {
  360. key := cm.Properties.Cluster
  361. if _, ok := arts[key]; !ok {
  362. arts[key] = &AssetTotals{
  363. Start: cm.GetStart(),
  364. End: cm.GetEnd(),
  365. Cluster: cm.Properties.Cluster,
  366. }
  367. }
  368. arts[key].Count++
  369. arts[key].ClusterManagementCost += cm.Cost
  370. arts[key].ClusterManagementCostAdjustment += cm.Adjustment
  371. }
  372. }
  373. // Record disks in an intermediate structure, which will be
  374. // processed after all assets have been seen.
  375. for _, disk := range as.Disks {
  376. key := fmt.Sprintf("%s/%s", disk.Properties.Cluster, disk.Properties.Name)
  377. disks[key] = disk
  378. }
  379. // Record all disks as either attached volumes or persistent volumes.
  380. for name, disk := range disks {
  381. // By default, the key will be the name, which is the tuple of
  382. // cluster/node. But if we're aggregating by cluster only, then
  383. // reset the key to just the cluster.
  384. key := name
  385. if prop == AssetClusterProp {
  386. key = disk.Properties.Cluster
  387. }
  388. if _, ok := arts[key]; !ok {
  389. arts[key] = &AssetTotals{
  390. Start: disk.Start,
  391. End: disk.End,
  392. Cluster: disk.Properties.Cluster,
  393. }
  394. if prop == AssetNodeProp {
  395. arts[key].Node = disk.Properties.Name
  396. }
  397. }
  398. _, isAttached := nodeNames[name]
  399. if isAttached {
  400. // Record attached volume data at the cluster and node level, using
  401. // name matching to distinguish from PersistentVolumes.
  402. // TODO can we make a stronger match at the underlying ETL layer?
  403. arts[key].Count++
  404. arts[key].AttachedVolumeCost += disk.Cost
  405. arts[key].AttachedVolumeCostAdjustment += disk.Adjustment
  406. } else if prop == AssetClusterProp {
  407. // Here, we're looking at a PersistentVolume because we're not
  408. // looking at an AttachedVolume. Only record PersistentVolume data
  409. // at the cluster level (i.e. prop == AssetClusterProp).
  410. arts[key].Count++
  411. arts[key].PersistentVolumeCost += disk.Cost
  412. arts[key].PersistentVolumeCostAdjustment += disk.Adjustment
  413. }
  414. }
  415. return arts
  416. }
  417. // AssetTotalsSet represents totals, summed by both "cluster" and "node"
  418. // for a given window of time.
  419. type AssetTotalsSet struct {
  420. Cluster map[string]*AssetTotals `json:"cluster"`
  421. Node map[string]*AssetTotals `json:"node"`
  422. Window Window `json:"window"`
  423. }
  424. func NewAssetTotalsSet(window Window, byCluster, byNode map[string]*AssetTotals) *AssetTotalsSet {
  425. return &AssetTotalsSet{
  426. Cluster: byCluster,
  427. Node: byNode,
  428. Window: window.Clone(),
  429. }
  430. }
  431. // ComputeIdleCoefficients returns the idle coefficients for CPU, GPU, and RAM
  432. // (in that order) for the given resource costs and totals.
  433. func ComputeIdleCoefficients(shareSplit, key string, cpuCost, gpuCost, ramCost float64, allocationTotals map[string]*AllocationTotals) (float64, float64, float64) {
  434. if shareSplit == ShareNone {
  435. return 0.0, 0.0, 0.0
  436. }
  437. if shareSplit != ShareEven {
  438. shareSplit = ShareWeighted
  439. }
  440. var cpuCoeff, gpuCoeff, ramCoeff float64
  441. if _, ok := allocationTotals[key]; !ok {
  442. return 0.0, 0.0, 0.0
  443. }
  444. if shareSplit == ShareEven {
  445. coeff := 1.0 / float64(allocationTotals[key].Count)
  446. return coeff, coeff, coeff
  447. }
  448. if allocationTotals[key].TotalCPUCost() > 0 {
  449. cpuCoeff = cpuCost / allocationTotals[key].TotalCPUCost()
  450. }
  451. if allocationTotals[key].TotalGPUCost() > 0 {
  452. gpuCoeff = gpuCost / allocationTotals[key].TotalGPUCost()
  453. }
  454. if allocationTotals[key].TotalRAMCost() > 0 {
  455. ramCoeff = ramCost / allocationTotals[key].TotalRAMCost()
  456. }
  457. return cpuCoeff, gpuCoeff, ramCoeff
  458. }
  459. // TotalsStore acts as both an AllocationTotalsStore and an
  460. // AssetTotalsStore.
  461. type TotalsStore interface {
  462. AllocationTotalsStore
  463. AssetTotalsStore
  464. }
  465. // AllocationTotalsStore allows for storing (i.e. setting and
  466. // getting) AllocationTotals by cluster and by node.
  467. type AllocationTotalsStore interface {
  468. GetAllocationTotalsByCluster(start, end time.Time) (map[string]*AllocationTotals, bool)
  469. GetAllocationTotalsByNode(start, end time.Time) (map[string]*AllocationTotals, bool)
  470. SetAllocationTotalsByCluster(start, end time.Time, rts map[string]*AllocationTotals)
  471. SetAllocationTotalsByNode(start, end time.Time, rts map[string]*AllocationTotals)
  472. }
  473. // UpdateAllocationTotalsStore updates an AllocationTotalsStore
  474. // by totaling the given AllocationSet and saving the totals.
  475. func UpdateAllocationTotalsStore(arts AllocationTotalsStore, as *AllocationSet) (*AllocationTotalsSet, error) {
  476. if arts == nil {
  477. return nil, errors.New("cannot update nil AllocationTotalsStore")
  478. }
  479. if as == nil {
  480. return nil, errors.New("cannot update AllocationTotalsStore from nil AllocationSet")
  481. }
  482. if as.Window.IsOpen() {
  483. return nil, errors.New("cannot update AllocationTotalsStore from AllocationSet with open window")
  484. }
  485. start := *as.Window.Start()
  486. end := *as.Window.End()
  487. artsByCluster := ComputeAllocationTotals(as, AllocationClusterProp)
  488. arts.SetAllocationTotalsByCluster(start, end, artsByCluster)
  489. artsByNode := ComputeAllocationTotals(as, AllocationNodeProp)
  490. arts.SetAllocationTotalsByNode(start, end, artsByNode)
  491. log.Debugf("ETL: Allocation: updated resource totals for %s", as.Window)
  492. win := NewClosedWindow(start, end)
  493. abc := map[string]*AllocationTotals{}
  494. for key, val := range artsByCluster {
  495. abc[key] = val.Clone()
  496. }
  497. abn := map[string]*AllocationTotals{}
  498. for key, val := range artsByNode {
  499. abn[key] = val.Clone()
  500. }
  501. return NewAllocationTotalsSet(win, abc, abn), nil
  502. }
  503. // AssetTotalsStore allows for storing (i.e. setting and getting)
  504. // AssetTotals by cluster and by node.
  505. type AssetTotalsStore interface {
  506. GetAssetTotalsByCluster(start, end time.Time) (map[string]*AssetTotals, bool)
  507. GetAssetTotalsByNode(start, end time.Time) (map[string]*AssetTotals, bool)
  508. SetAssetTotalsByCluster(start, end time.Time, rts map[string]*AssetTotals)
  509. SetAssetTotalsByNode(start, end time.Time, rts map[string]*AssetTotals)
  510. }
  511. // UpdateAssetTotalsStore updates an AssetTotalsStore
  512. // by totaling the given AssetSet and saving the totals.
  513. func UpdateAssetTotalsStore(arts AssetTotalsStore, as *AssetSet) (*AssetTotalsSet, error) {
  514. if arts == nil {
  515. return nil, errors.New("cannot update nil AssetTotalsStore")
  516. }
  517. if as == nil {
  518. return nil, errors.New("cannot update AssetTotalsStore from nil AssetSet")
  519. }
  520. if as.Window.IsOpen() {
  521. return nil, errors.New("cannot update AssetTotalsStore from AssetSet with open window")
  522. }
  523. start := *as.Window.Start()
  524. end := *as.Window.End()
  525. artsByCluster := ComputeAssetTotals(as, AssetClusterProp)
  526. arts.SetAssetTotalsByCluster(start, end, artsByCluster)
  527. artsByNode := ComputeAssetTotals(as, AssetNodeProp)
  528. arts.SetAssetTotalsByNode(start, end, artsByNode)
  529. log.Debugf("ETL: Asset: updated resource totals for %s", as.Window)
  530. win := NewClosedWindow(start, end)
  531. abc := map[string]*AssetTotals{}
  532. for key, val := range artsByCluster {
  533. abc[key] = val.Clone()
  534. }
  535. abn := map[string]*AssetTotals{}
  536. for key, val := range artsByNode {
  537. abn[key] = val.Clone()
  538. }
  539. return NewAssetTotalsSet(win, abc, abn), nil
  540. }
  541. // MemoryTotalsStore is an in-memory cache TotalsStore
  542. type MemoryTotalsStore struct {
  543. allocTotalsByCluster *cache.Cache
  544. allocTotalsByNode *cache.Cache
  545. assetTotalsByCluster *cache.Cache
  546. assetTotalsByNode *cache.Cache
  547. }
  548. // NewMemoryTotalsStore instantiates a new MemoryTotalsStore,
  549. // which is composed of four in-memory caches.
  550. func NewMemoryTotalsStore() *MemoryTotalsStore {
  551. return &MemoryTotalsStore{
  552. allocTotalsByCluster: cache.New(cache.NoExpiration, cache.NoExpiration),
  553. allocTotalsByNode: cache.New(cache.NoExpiration, cache.NoExpiration),
  554. assetTotalsByCluster: cache.New(cache.NoExpiration, cache.NoExpiration),
  555. assetTotalsByNode: cache.New(cache.NoExpiration, cache.NoExpiration),
  556. }
  557. }
  558. // GetAllocationTotalsByCluster retrieves the AllocationTotals
  559. // by cluster for the given start and end times.
  560. func (mts *MemoryTotalsStore) GetAllocationTotalsByCluster(start time.Time, end time.Time) (map[string]*AllocationTotals, bool) {
  561. k := storeKey(start, end)
  562. if raw, ok := mts.allocTotalsByCluster.Get(k); !ok {
  563. return map[string]*AllocationTotals{}, false
  564. } else {
  565. original := raw.(map[string]*AllocationTotals)
  566. totals := make(map[string]*AllocationTotals, len(original))
  567. for k, v := range original {
  568. totals[k] = v.Clone()
  569. }
  570. return totals, true
  571. }
  572. }
  573. // GetAllocationTotalsByNode retrieves the AllocationTotals
  574. // by node for the given start and end times.
  575. func (mts *MemoryTotalsStore) GetAllocationTotalsByNode(start time.Time, end time.Time) (map[string]*AllocationTotals, bool) {
  576. k := storeKey(start, end)
  577. if raw, ok := mts.allocTotalsByNode.Get(k); !ok {
  578. return map[string]*AllocationTotals{}, false
  579. } else {
  580. original := raw.(map[string]*AllocationTotals)
  581. totals := make(map[string]*AllocationTotals, len(original))
  582. for k, v := range original {
  583. totals[k] = v.Clone()
  584. }
  585. return totals, true
  586. }
  587. }
  588. // SetAllocationTotalsByCluster set the per-cluster AllocationTotals
  589. // to the given values for the given start and end times.
  590. func (mts *MemoryTotalsStore) SetAllocationTotalsByCluster(start time.Time, end time.Time, arts map[string]*AllocationTotals) {
  591. k := storeKey(start, end)
  592. mts.allocTotalsByCluster.Set(k, arts, cache.NoExpiration)
  593. }
  594. // SetAllocationTotalsByNode set the per-node AllocationTotals
  595. // to the given values for the given start and end times.
  596. func (mts *MemoryTotalsStore) SetAllocationTotalsByNode(start time.Time, end time.Time, arts map[string]*AllocationTotals) {
  597. k := storeKey(start, end)
  598. mts.allocTotalsByNode.Set(k, arts, cache.NoExpiration)
  599. }
  600. // GetAssetTotalsByCluster retrieves the AssetTotals
  601. // by cluster for the given start and end times.
  602. func (mts *MemoryTotalsStore) GetAssetTotalsByCluster(start time.Time, end time.Time) (map[string]*AssetTotals, bool) {
  603. k := storeKey(start, end)
  604. if raw, ok := mts.assetTotalsByCluster.Get(k); !ok {
  605. return map[string]*AssetTotals{}, false
  606. } else {
  607. original := raw.(map[string]*AssetTotals)
  608. totals := make(map[string]*AssetTotals, len(original))
  609. for k, v := range original {
  610. totals[k] = v.Clone()
  611. }
  612. return totals, true
  613. }
  614. }
  615. // GetAssetTotalsByNode retrieves the AssetTotals
  616. // by node for the given start and end times.
  617. func (mts *MemoryTotalsStore) GetAssetTotalsByNode(start time.Time, end time.Time) (map[string]*AssetTotals, bool) {
  618. k := storeKey(start, end)
  619. if raw, ok := mts.assetTotalsByNode.Get(k); !ok {
  620. return map[string]*AssetTotals{}, false
  621. } else {
  622. original := raw.(map[string]*AssetTotals)
  623. totals := make(map[string]*AssetTotals, len(original))
  624. for k, v := range original {
  625. totals[k] = v.Clone()
  626. }
  627. return totals, true
  628. }
  629. }
  630. // SetAssetTotalsByCluster set the per-cluster AssetTotals
  631. // to the given values for the given start and end times.
  632. func (mts *MemoryTotalsStore) SetAssetTotalsByCluster(start time.Time, end time.Time, arts map[string]*AssetTotals) {
  633. k := storeKey(start, end)
  634. mts.assetTotalsByCluster.Set(k, arts, cache.NoExpiration)
  635. }
  636. // SetAssetTotalsByNode set the per-node AssetTotals
  637. // to the given values for the given start and end times.
  638. func (mts *MemoryTotalsStore) SetAssetTotalsByNode(start time.Time, end time.Time, arts map[string]*AssetTotals) {
  639. k := storeKey(start, end)
  640. mts.assetTotalsByNode.Set(k, arts, cache.NoExpiration)
  641. }
  642. // storeKey creates a storage key based on start and end times
  643. func storeKey(start, end time.Time) string {
  644. startStr := strconv.FormatInt(start.Unix(), 10)
  645. endStr := strconv.FormatInt(end.Unix(), 10)
  646. return fmt.Sprintf("%s-%s", startStr, endStr)
  647. }