cluster_helpers.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788
  1. package costmodel
  2. import (
  3. "strconv"
  4. "time"
  5. "github.com/kubecost/cost-model/pkg/cloud"
  6. "github.com/kubecost/cost-model/pkg/env"
  7. "github.com/kubecost/cost-model/pkg/log"
  8. "github.com/kubecost/cost-model/pkg/prom"
  9. )
  10. // mergeTypeMaps takes two maps of (cluster name, node name) -> node type
  11. // and combines them into a single map, preferring the k/v pairs in
  12. // the first map.
  13. func mergeTypeMaps(clusterAndNameToType1, clusterAndNameToType2 map[nodeIdentifierNoProviderID]string) map[nodeIdentifierNoProviderID]string {
  14. merged := map[nodeIdentifierNoProviderID]string{}
  15. for k, v := range clusterAndNameToType2 {
  16. merged[k] = v
  17. }
  18. // This ordering ensures the mappings in the first arg are preferred.
  19. for k, v := range clusterAndNameToType1 {
  20. merged[k] = v
  21. }
  22. return merged
  23. }
  24. func buildCPUCostMap(
  25. resNodeCPUCost []*prom.QueryResult,
  26. cp cloud.Provider,
  27. preemptible map[NodeIdentifier]bool,
  28. ) (
  29. map[NodeIdentifier]float64,
  30. map[nodeIdentifierNoProviderID]string,
  31. ) {
  32. cpuCostMap := make(map[NodeIdentifier]float64)
  33. clusterAndNameToType := make(map[nodeIdentifierNoProviderID]string)
  34. customPricingEnabled := cloud.CustomPricesEnabled(cp)
  35. customPricingConfig, err := cp.GetConfig()
  36. if err != nil {
  37. log.Warningf("ClusterNodes: failed to load custom pricing: %s", err)
  38. }
  39. for _, result := range resNodeCPUCost {
  40. cluster, err := result.GetString(env.GetPromClusterLabel())
  41. if err != nil {
  42. cluster = env.GetClusterID()
  43. }
  44. name, err := result.GetString("node")
  45. if err != nil {
  46. log.Warningf("ClusterNodes: CPU cost data missing node")
  47. continue
  48. }
  49. nodeType, _ := result.GetString("instance_type")
  50. providerID, _ := result.GetString("provider_id")
  51. key := NodeIdentifier{
  52. Cluster: cluster,
  53. Name: name,
  54. ProviderID: cloud.ParseID(providerID),
  55. }
  56. keyNon := nodeIdentifierNoProviderID{
  57. Cluster: cluster,
  58. Name: name,
  59. }
  60. var cpuCost float64
  61. if customPricingEnabled && customPricingConfig != nil {
  62. var customCPUStr string
  63. if spot, ok := preemptible[key]; ok && spot {
  64. customCPUStr = customPricingConfig.SpotCPU
  65. } else {
  66. customCPUStr = customPricingConfig.CPU
  67. }
  68. customCPUCost, err := strconv.ParseFloat(customCPUStr, 64)
  69. if err != nil {
  70. log.Warningf("ClusterNodes: error parsing custom CPU price: %s", customCPUStr)
  71. }
  72. cpuCost = customCPUCost
  73. } else {
  74. cpuCost = result.Values[0].Value
  75. }
  76. clusterAndNameToType[keyNon] = nodeType
  77. cpuCostMap[key] = cpuCost
  78. }
  79. return cpuCostMap, clusterAndNameToType
  80. }
  81. func buildRAMCostMap(
  82. resNodeRAMCost []*prom.QueryResult,
  83. cp cloud.Provider,
  84. preemptible map[NodeIdentifier]bool,
  85. ) (
  86. map[NodeIdentifier]float64,
  87. map[nodeIdentifierNoProviderID]string,
  88. ) {
  89. ramCostMap := make(map[NodeIdentifier]float64)
  90. clusterAndNameToType := make(map[nodeIdentifierNoProviderID]string)
  91. customPricingEnabled := cloud.CustomPricesEnabled(cp)
  92. customPricingConfig, err := cp.GetConfig()
  93. if err != nil {
  94. log.Warningf("ClusterNodes: failed to load custom pricing: %s", err)
  95. }
  96. for _, result := range resNodeRAMCost {
  97. cluster, err := result.GetString(env.GetPromClusterLabel())
  98. if err != nil {
  99. cluster = env.GetClusterID()
  100. }
  101. name, err := result.GetString("node")
  102. if err != nil {
  103. log.Warningf("ClusterNodes: RAM cost data missing node")
  104. continue
  105. }
  106. nodeType, _ := result.GetString("instance_type")
  107. providerID, _ := result.GetString("provider_id")
  108. key := NodeIdentifier{
  109. Cluster: cluster,
  110. Name: name,
  111. ProviderID: cloud.ParseID(providerID),
  112. }
  113. keyNon := nodeIdentifierNoProviderID{
  114. Cluster: cluster,
  115. Name: name,
  116. }
  117. var ramCost float64
  118. if customPricingEnabled && customPricingConfig != nil {
  119. var customRAMStr string
  120. if spot, ok := preemptible[key]; ok && spot {
  121. customRAMStr = customPricingConfig.SpotRAM
  122. } else {
  123. customRAMStr = customPricingConfig.RAM
  124. }
  125. customRAMCost, err := strconv.ParseFloat(customRAMStr, 64)
  126. if err != nil {
  127. log.Warningf("ClusterNodes: error parsing custom RAM price: %s", customRAMStr)
  128. }
  129. ramCost = customRAMCost / 1024 / 1024 / 1024
  130. } else {
  131. ramCost = result.Values[0].Value
  132. }
  133. clusterAndNameToType[keyNon] = nodeType
  134. ramCostMap[key] = ramCost
  135. }
  136. return ramCostMap, clusterAndNameToType
  137. }
  138. func buildGPUCostMap(
  139. resNodeGPUCost []*prom.QueryResult,
  140. gpuCountMap map[NodeIdentifier]float64,
  141. cp cloud.Provider,
  142. preemptible map[NodeIdentifier]bool,
  143. ) (
  144. map[NodeIdentifier]float64,
  145. map[nodeIdentifierNoProviderID]string,
  146. ) {
  147. gpuCostMap := make(map[NodeIdentifier]float64)
  148. clusterAndNameToType := make(map[nodeIdentifierNoProviderID]string)
  149. customPricingEnabled := cloud.CustomPricesEnabled(cp)
  150. customPricingConfig, err := cp.GetConfig()
  151. if err != nil {
  152. log.Warningf("ClusterNodes: failed to load custom pricing: %s", err)
  153. }
  154. for _, result := range resNodeGPUCost {
  155. cluster, err := result.GetString(env.GetPromClusterLabel())
  156. if err != nil {
  157. cluster = env.GetClusterID()
  158. }
  159. name, err := result.GetString("node")
  160. if err != nil {
  161. log.Warningf("ClusterNodes: GPU cost data missing node")
  162. continue
  163. }
  164. nodeType, _ := result.GetString("instance_type")
  165. providerID, _ := result.GetString("provider_id")
  166. key := NodeIdentifier{
  167. Cluster: cluster,
  168. Name: name,
  169. ProviderID: cloud.ParseID(providerID),
  170. }
  171. keyNon := nodeIdentifierNoProviderID{
  172. Cluster: cluster,
  173. Name: name,
  174. }
  175. var gpuCost float64
  176. if customPricingEnabled && customPricingConfig != nil {
  177. var customGPUStr string
  178. if spot, ok := preemptible[key]; ok && spot {
  179. customGPUStr = customPricingConfig.SpotGPU
  180. } else {
  181. customGPUStr = customPricingConfig.GPU
  182. }
  183. customGPUCost, err := strconv.ParseFloat(customGPUStr, 64)
  184. if err != nil {
  185. log.Warningf("ClusterNodes: error parsing custom GPU price: %s", customGPUStr)
  186. }
  187. gpuCost = customGPUCost
  188. } else {
  189. gpuCost = result.Values[0].Value
  190. }
  191. clusterAndNameToType[keyNon] = nodeType
  192. // If gpu count is available use it to multiply gpu cost
  193. if value, ok := gpuCountMap[key]; ok && value != 0 {
  194. gpuCostMap[key] = gpuCost * value
  195. } else {
  196. gpuCostMap[key] = gpuCost
  197. }
  198. }
  199. return gpuCostMap, clusterAndNameToType
  200. }
  201. func buildGPUCountMap(
  202. resNodeGPUCount []*prom.QueryResult,
  203. ) map[NodeIdentifier]float64 {
  204. gpuCountMap := make(map[NodeIdentifier]float64)
  205. for _, result := range resNodeGPUCount {
  206. cluster, err := result.GetString(env.GetPromClusterLabel())
  207. if err != nil {
  208. cluster = env.GetClusterID()
  209. }
  210. name, err := result.GetString("node")
  211. if err != nil {
  212. log.Warningf("ClusterNodes: GPU count data missing node")
  213. continue
  214. }
  215. gpuCount := result.Values[0].Value
  216. providerID, _ := result.GetString("provider_id")
  217. key := NodeIdentifier{
  218. Cluster: cluster,
  219. Name: name,
  220. ProviderID: cloud.ParseID(providerID),
  221. }
  222. gpuCountMap[key] = gpuCount
  223. }
  224. return gpuCountMap
  225. }
  226. func buildCPUCoresMap(
  227. resNodeCPUCores []*prom.QueryResult,
  228. ) map[nodeIdentifierNoProviderID]float64 {
  229. m := make(map[nodeIdentifierNoProviderID]float64)
  230. for _, result := range resNodeCPUCores {
  231. cluster, err := result.GetString(env.GetPromClusterLabel())
  232. if err != nil {
  233. cluster = env.GetClusterID()
  234. }
  235. name, err := result.GetString("node")
  236. if err != nil {
  237. log.Warningf("ClusterNodes: CPU cores data missing node")
  238. continue
  239. }
  240. cpuCores := result.Values[0].Value
  241. key := nodeIdentifierNoProviderID{
  242. Cluster: cluster,
  243. Name: name,
  244. }
  245. m[key] = cpuCores
  246. }
  247. return m
  248. }
  249. func buildRAMBytesMap(resNodeRAMBytes []*prom.QueryResult) map[nodeIdentifierNoProviderID]float64 {
  250. m := make(map[nodeIdentifierNoProviderID]float64)
  251. for _, result := range resNodeRAMBytes {
  252. cluster, err := result.GetString(env.GetPromClusterLabel())
  253. if err != nil {
  254. cluster = env.GetClusterID()
  255. }
  256. name, err := result.GetString("node")
  257. if err != nil {
  258. log.Warningf("ClusterNodes: RAM bytes data missing node")
  259. continue
  260. }
  261. ramBytes := result.Values[0].Value
  262. key := nodeIdentifierNoProviderID{
  263. Cluster: cluster,
  264. Name: name,
  265. }
  266. m[key] = ramBytes
  267. }
  268. return m
  269. }
  270. // Mapping of cluster/node=cpu for computing resource efficiency
  271. func buildCPUBreakdownMap(resNodeCPUModeTotal []*prom.QueryResult) map[nodeIdentifierNoProviderID]*ClusterCostsBreakdown {
  272. cpuBreakdownMap := make(map[nodeIdentifierNoProviderID]*ClusterCostsBreakdown)
  273. // Mapping of cluster/node=cpu for computing resource efficiency
  274. clusterNodeCPUTotal := map[nodeIdentifierNoProviderID]float64{}
  275. // Mapping of cluster/node:mode=cpu for computing resource efficiency
  276. clusterNodeModeCPUTotal := map[nodeIdentifierNoProviderID]map[string]float64{}
  277. // Build intermediate structures for CPU usage by (cluster, node) and by
  278. // (cluster, node, mode) for computing resouce efficiency
  279. for _, result := range resNodeCPUModeTotal {
  280. cluster, err := result.GetString(env.GetPromClusterLabel())
  281. if err != nil {
  282. cluster = env.GetClusterID()
  283. }
  284. node, err := result.GetString("kubernetes_node")
  285. if err != nil {
  286. log.DedupedWarningf(5, "ClusterNodes: CPU mode data missing node")
  287. continue
  288. }
  289. mode, err := result.GetString("mode")
  290. if err != nil {
  291. log.Warningf("ClusterNodes: unable to read CPU mode: %s", err)
  292. mode = "other"
  293. }
  294. key := nodeIdentifierNoProviderID{
  295. Cluster: cluster,
  296. Name: node,
  297. }
  298. total := result.Values[0].Value
  299. // Increment total
  300. clusterNodeCPUTotal[key] += total
  301. // Increment mode
  302. if _, ok := clusterNodeModeCPUTotal[key]; !ok {
  303. clusterNodeModeCPUTotal[key] = map[string]float64{}
  304. }
  305. clusterNodeModeCPUTotal[key][mode] += total
  306. }
  307. // Compute resource efficiency from intermediate structures
  308. for key, total := range clusterNodeCPUTotal {
  309. if modeTotals, ok := clusterNodeModeCPUTotal[key]; ok {
  310. for mode, subtotal := range modeTotals {
  311. // Compute percentage for the current cluster, node, mode
  312. pct := 0.0
  313. if total > 0 {
  314. pct = subtotal / total
  315. }
  316. if _, ok := cpuBreakdownMap[key]; !ok {
  317. cpuBreakdownMap[key] = &ClusterCostsBreakdown{}
  318. }
  319. switch mode {
  320. case "idle":
  321. cpuBreakdownMap[key].Idle += pct
  322. case "system":
  323. cpuBreakdownMap[key].System += pct
  324. case "user":
  325. cpuBreakdownMap[key].User += pct
  326. default:
  327. cpuBreakdownMap[key].Other += pct
  328. }
  329. }
  330. }
  331. }
  332. return cpuBreakdownMap
  333. }
  334. func buildRAMUserPctMap(resNodeRAMUserPct []*prom.QueryResult) map[nodeIdentifierNoProviderID]float64 {
  335. m := make(map[nodeIdentifierNoProviderID]float64)
  336. for _, result := range resNodeRAMUserPct {
  337. cluster, err := result.GetString(env.GetPromClusterLabel())
  338. if err != nil {
  339. cluster = env.GetClusterID()
  340. }
  341. name, err := result.GetString("instance")
  342. if err != nil {
  343. log.Warningf("ClusterNodes: RAM user percent missing node")
  344. continue
  345. }
  346. pct := result.Values[0].Value
  347. key := nodeIdentifierNoProviderID{
  348. Cluster: cluster,
  349. Name: name,
  350. }
  351. m[key] = pct
  352. }
  353. return m
  354. }
  355. func buildRAMSystemPctMap(resNodeRAMSystemPct []*prom.QueryResult) map[nodeIdentifierNoProviderID]float64 {
  356. m := make(map[nodeIdentifierNoProviderID]float64)
  357. for _, result := range resNodeRAMSystemPct {
  358. cluster, err := result.GetString(env.GetPromClusterLabel())
  359. if err != nil {
  360. cluster = env.GetClusterID()
  361. }
  362. name, err := result.GetString("instance")
  363. if err != nil {
  364. log.Warningf("ClusterNodes: RAM system percent missing node")
  365. continue
  366. }
  367. pct := result.Values[0].Value
  368. key := nodeIdentifierNoProviderID{
  369. Cluster: cluster,
  370. Name: name,
  371. }
  372. m[key] = pct
  373. }
  374. return m
  375. }
  376. type activeData struct {
  377. start time.Time
  378. end time.Time
  379. minutes float64
  380. }
  381. func buildActiveDataMap(resActiveMins []*prom.QueryResult, resolution time.Duration) map[NodeIdentifier]activeData {
  382. m := make(map[NodeIdentifier]activeData)
  383. for _, result := range resActiveMins {
  384. cluster, err := result.GetString(env.GetPromClusterLabel())
  385. if err != nil {
  386. cluster = env.GetClusterID()
  387. }
  388. name, err := result.GetString("node")
  389. if err != nil {
  390. log.Warningf("ClusterNodes: active mins missing node")
  391. continue
  392. }
  393. providerID, _ := result.GetString("provider_id")
  394. key := NodeIdentifier{
  395. Cluster: cluster,
  396. Name: name,
  397. ProviderID: cloud.ParseID(providerID),
  398. }
  399. if len(result.Values) == 0 {
  400. continue
  401. }
  402. s := time.Unix(int64(result.Values[0].Timestamp), 0)
  403. e := time.Unix(int64(result.Values[len(result.Values)-1].Timestamp), 0).Add(resolution)
  404. mins := e.Sub(s).Minutes()
  405. // TODO niko/assets if mins >= threshold, interpolate for missing data?
  406. m[key] = activeData{
  407. start: s,
  408. end: e,
  409. minutes: mins,
  410. }
  411. }
  412. return m
  413. }
  414. // Determine preemptibility with node labels
  415. // node id -> is preemptible?
  416. func buildPreemptibleMap(
  417. resIsSpot []*prom.QueryResult,
  418. ) map[NodeIdentifier]bool {
  419. m := make(map[NodeIdentifier]bool)
  420. for _, result := range resIsSpot {
  421. nodeName, err := result.GetString("node")
  422. if err != nil {
  423. continue
  424. }
  425. // GCP preemptible label
  426. pre := result.Values[0].Value
  427. cluster, err := result.GetString(env.GetPromClusterLabel())
  428. if err != nil {
  429. cluster = env.GetClusterID()
  430. }
  431. providerID, _ := result.GetString("provider_id")
  432. key := NodeIdentifier{
  433. Cluster: cluster,
  434. Name: nodeName,
  435. ProviderID: cloud.ParseID(providerID),
  436. }
  437. // TODO(michaelmdresser): check this condition at merge time?
  438. // if node, ok := nodeMap[key]; pre > 0.0 && ok {
  439. // node.Preemptible = true
  440. // }
  441. m[key] = pre > 0.0
  442. // TODO AWS preemptible
  443. // TODO Azure preemptible
  444. }
  445. return m
  446. }
  447. func buildLabelsMap(
  448. resLabels []*prom.QueryResult,
  449. ) map[nodeIdentifierNoProviderID]map[string]string {
  450. m := make(map[nodeIdentifierNoProviderID]map[string]string)
  451. // Copy labels into node
  452. for _, result := range resLabels {
  453. cluster, err := result.GetString(env.GetPromClusterLabel())
  454. if err != nil {
  455. cluster = env.GetClusterID()
  456. }
  457. node, err := result.GetString("node")
  458. if err != nil {
  459. log.DedupedWarningf(5, "ClusterNodes: label data missing node")
  460. continue
  461. }
  462. key := nodeIdentifierNoProviderID{
  463. Cluster: cluster,
  464. Name: node,
  465. }
  466. m[key] = make(map[string]string)
  467. for name, value := range result.Metric {
  468. if val, ok := value.(string); ok {
  469. m[key][name] = val
  470. }
  471. }
  472. }
  473. return m
  474. }
  475. // checkForKeyAndInitIfMissing inits a key in the provided nodemap if
  476. // it does not exist. Intended to be called ONLY by buildNodeMap
  477. func checkForKeyAndInitIfMissing(
  478. nodeMap map[NodeIdentifier]*Node,
  479. key NodeIdentifier,
  480. clusterAndNameToType map[nodeIdentifierNoProviderID]string,
  481. ) {
  482. if _, ok := nodeMap[key]; !ok {
  483. // default nodeType in case we don't have the mapping
  484. var nodeType string
  485. if t, ok := clusterAndNameToType[nodeIdentifierNoProviderID{
  486. Cluster: key.Cluster,
  487. Name: key.Name,
  488. }]; ok {
  489. nodeType = t
  490. } else {
  491. log.Warningf("ClusterNodes: Type does not exist for node identifier %s", key)
  492. }
  493. nodeMap[key] = &Node{
  494. Cluster: key.Cluster,
  495. Name: key.Name,
  496. NodeType: nodeType,
  497. ProviderID: key.ProviderID,
  498. CPUBreakdown: &ClusterCostsBreakdown{},
  499. RAMBreakdown: &ClusterCostsBreakdown{},
  500. }
  501. }
  502. }
  503. // buildNodeMap creates the main set of node data for ClusterNodes from
  504. // the data maps built from Prometheus queries. Some of the Prometheus
  505. // data has access to the provider_id field and some does not. To get
  506. // around this problem, we use the data that includes provider_id
  507. // to build up the definitive set of nodes and then use the data
  508. // with less-specific identifiers (i.e. without provider_id) to fill
  509. // in the remaining fields.
  510. //
  511. // For example, let's say we have nodes identified like so:
  512. // cluster name/node name/provider_id. For the sake of the example,
  513. // we will also limit data to CPU cost, CPU cores, and preemptibility.
  514. //
  515. // We have CPU cost data that looks like this:
  516. // cluster1/node1/prov_node1_A: $10
  517. // cluster1/node1/prov_node1_B: $8
  518. // cluster1/node2/prov_node2: $15
  519. //
  520. // We have Preemptible data that looks like this:
  521. // cluster1/node1/prov_node1_A: true
  522. // cluster1/node1/prov_node1_B: false
  523. // cluster1/node2/prov_node2_B: false
  524. //
  525. // We have CPU cores data that looks like this:
  526. // cluster1/node1: 4
  527. // cluster1/node2: 6
  528. //
  529. // This function first combines the data that is fully identified,
  530. // creating the following:
  531. // cluster1/node1/prov_node1_A: CPUCost($10), Preemptible(true)
  532. // cluster1/node1/prov_node1_B: CPUCost($8), Preemptible(false)
  533. // cluster1/node2/prov_node2: CPUCost($15), Preemptible(false)
  534. //
  535. // It then uses the less-specific data to extend the specific data,
  536. // making the following:
  537. // cluster1/node1/prov_node1_A: CPUCost($10), Preemptible(true), Cores(4)
  538. // cluster1/node1/prov_node1_B: CPUCost($8), Preemptible(false), Cores(4)
  539. // cluster1/node2/prov_node2: CPUCost($15), Preemptible(false), Cores(6)
  540. //
  541. // In the situation where provider_id doesn't exist for any metrics,
  542. // that is the same as all provider_ids being empty strings. If
  543. // provider_id doesn't exist at all, then we (without having to do
  544. // extra work) easily fall back on identifying nodes only by cluster name
  545. // and node name because the provider_id part of the key will always
  546. // be the empty string.
  547. //
  548. // It is worth nothing that, in this approach, if a node is not present
  549. // in the more specific data but is present in the less-specific data,
  550. // that data is never processed into the final node map. For example,
  551. // let's say the CPU cores map has the following entry:
  552. // cluster1/node8: 6
  553. // But none of the maps with provider_id (CPU cost, RAM cost, etc.)
  554. // have an identifier for cluster1/node8 (regardless of provider_id).
  555. // In this situation, the final node map will not have a cluster1/node8
  556. // entry. This could be fixed by iterating over all of the less specific
  557. // identifiers and, inside that iteration, all of the identifiers in
  558. // the node map, but this would introduce a roughly quadratic time
  559. // complexity.
  560. func buildNodeMap(
  561. cpuCostMap, ramCostMap, gpuCostMap, gpuCountMap map[NodeIdentifier]float64,
  562. cpuCoresMap, ramBytesMap, ramUserPctMap,
  563. ramSystemPctMap map[nodeIdentifierNoProviderID]float64,
  564. cpuBreakdownMap map[nodeIdentifierNoProviderID]*ClusterCostsBreakdown,
  565. activeDataMap map[NodeIdentifier]activeData,
  566. preemptibleMap map[NodeIdentifier]bool,
  567. labelsMap map[nodeIdentifierNoProviderID]map[string]string,
  568. clusterAndNameToType map[nodeIdentifierNoProviderID]string,
  569. ) map[NodeIdentifier]*Node {
  570. nodeMap := make(map[NodeIdentifier]*Node)
  571. // Initialize the map with the most-specific data:
  572. for id, cost := range cpuCostMap {
  573. checkForKeyAndInitIfMissing(nodeMap, id, clusterAndNameToType)
  574. nodeMap[id].CPUCost = cost
  575. }
  576. for id, cost := range ramCostMap {
  577. checkForKeyAndInitIfMissing(nodeMap, id, clusterAndNameToType)
  578. nodeMap[id].RAMCost = cost
  579. }
  580. for id, cost := range gpuCostMap {
  581. checkForKeyAndInitIfMissing(nodeMap, id, clusterAndNameToType)
  582. nodeMap[id].GPUCost = cost
  583. }
  584. for id, count := range gpuCountMap {
  585. checkForKeyAndInitIfMissing(nodeMap, id, clusterAndNameToType)
  586. nodeMap[id].GPUCount = count
  587. }
  588. for id, preemptible := range preemptibleMap {
  589. checkForKeyAndInitIfMissing(nodeMap, id, clusterAndNameToType)
  590. nodeMap[id].Preemptible = preemptible
  591. }
  592. for id, activeData := range activeDataMap {
  593. checkForKeyAndInitIfMissing(nodeMap, id, clusterAndNameToType)
  594. nodeMap[id].Start = activeData.start
  595. nodeMap[id].End = activeData.end
  596. nodeMap[id].Minutes = activeData.minutes
  597. }
  598. // We now merge in data that doesn't have a provider id by looping over
  599. // all keys already added and inserting data according to their
  600. // cluster name/node name combos.
  601. for id, nodePtr := range nodeMap {
  602. clusterAndNameID := nodeIdentifierNoProviderID{
  603. Cluster: id.Cluster,
  604. Name: id.Name,
  605. }
  606. if cores, ok := cpuCoresMap[clusterAndNameID]; ok {
  607. nodePtr.CPUCores = cores
  608. if v, ok := partialCPUMap[nodePtr.NodeType]; ok {
  609. if cores > 0 {
  610. nodePtr.CPUCores = v
  611. adjustmentFactor := v / cores
  612. nodePtr.CPUCost = nodePtr.CPUCost * adjustmentFactor
  613. }
  614. }
  615. }
  616. if ramBytes, ok := ramBytesMap[clusterAndNameID]; ok {
  617. nodePtr.RAMBytes = ramBytes
  618. }
  619. if ramUserPct, ok := ramUserPctMap[clusterAndNameID]; ok {
  620. nodePtr.RAMBreakdown.User = ramUserPct
  621. }
  622. if ramSystemPct, ok := ramSystemPctMap[clusterAndNameID]; ok {
  623. nodePtr.RAMBreakdown.System = ramSystemPct
  624. }
  625. if cpuBreakdown, ok := cpuBreakdownMap[clusterAndNameID]; ok {
  626. nodePtr.CPUBreakdown = cpuBreakdown
  627. }
  628. if labels, ok := labelsMap[clusterAndNameID]; ok {
  629. nodePtr.Labels = labels
  630. }
  631. }
  632. return nodeMap
  633. }