allocation.go 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197
  1. package costmodel
  2. import (
  3. "fmt"
  4. "time"
  5. "github.com/kubecost/cost-model/pkg/env"
  6. "github.com/kubecost/cost-model/pkg/kubecost"
  7. "github.com/kubecost/cost-model/pkg/log"
  8. "github.com/kubecost/cost-model/pkg/prom"
  9. "github.com/kubecost/cost-model/pkg/thanos"
  10. )
  11. // TODO niko/cdmr move to pkg/kubecost
  12. // TODO niko/cdmr add PersistenVolumeClaims to type Allocation?
  13. type PVC struct {
  14. Bytes float64 `json:"bytes"`
  15. Count int `json:"count"`
  16. Name string `json:"name"`
  17. Cluster string `json:"cluster"`
  18. Namespace string `json:"namespace"`
  19. Volume *PV `json:"persistentVolume"`
  20. Start time.Time `json:"start"`
  21. End time.Time `json:"end"`
  22. }
  23. func (pvc *PVC) Cost() float64 {
  24. if pvc == nil || pvc.Volume == nil {
  25. return 0.0
  26. }
  27. gib := pvc.Bytes / 1024 / 1024 / 1024
  28. hrs := pvc.Minutes() / 60.0
  29. return pvc.Volume.CostPerGiBHour * gib * hrs
  30. }
  31. func (pvc *PVC) Minutes() float64 {
  32. if pvc == nil {
  33. return 0.0
  34. }
  35. return pvc.End.Sub(pvc.Start).Minutes()
  36. }
  37. func (pvc *PVC) String() string {
  38. if pvc == nil {
  39. return "<nil>"
  40. }
  41. return fmt.Sprintf("%s/%s/%s{Bytes:%.2f, Cost:%.6f, Start,End:%s}", pvc.Cluster, pvc.Namespace, pvc.Name, pvc.Bytes, pvc.Cost(), kubecost.NewWindow(&pvc.Start, &pvc.End))
  42. }
  43. // TODO niko/cdmr move to pkg/kubecost
  44. type PV struct {
  45. Bytes float64 `json:"bytes"`
  46. CostPerGiBHour float64 `json:"costPerGiBHour"` // TODO niko/cdmr GiB or GB?
  47. Cluster string `json:"cluster"`
  48. Name string `json:"name"`
  49. StorageClass string `json:"storageClass"`
  50. }
  51. func (pv *PV) String() string {
  52. if pv == nil {
  53. return "<nil>"
  54. }
  55. return fmt.Sprintf("%s/%s{Bytes:%.2f, Cost/GiB*Hr:%.6f, StorageClass:%s}", pv.Cluster, pv.Name, pv.Bytes, pv.CostPerGiBHour, pv.StorageClass)
  56. }
  57. // ComputeAllocation uses the CostModel instance to compute an AllocationSet
  58. // for the window defined by the given start and end times. The Allocations
  59. // returned are unaggregated (i.e. down to the container level).
  60. func (cm *CostModel) ComputeAllocation(start, end time.Time) (*kubecost.AllocationSet, error) {
  61. // Create a window spanning the requested query
  62. s, e := start, end
  63. window := kubecost.NewWindow(&s, &e)
  64. // Create an empty AllocationSet. For safety, in the case of an error, we
  65. // should prefer to return this empty set with the error. (In the case of
  66. // no error, of course we populate the set and return it.)
  67. allocSet := kubecost.NewAllocationSet(start, end)
  68. // Convert window (start, end) to (duration, offset) for querying Prometheus
  69. timesToDurations := func(s, e time.Time) (dur, off time.Duration) {
  70. now := time.Now()
  71. off = now.Sub(e)
  72. dur = e.Sub(s)
  73. return dur, off
  74. }
  75. duration, offset := timesToDurations(start, end)
  76. // If using Thanos, increase offset to 3 hours, reducing the duration by
  77. // equal measure to maintain the same starting point.
  78. thanosDur := thanos.OffsetDuration()
  79. // TODO niko/cdmr confirm that this flag works interchangeably with ThanosClient != nil
  80. if offset < thanosDur && env.IsThanosEnabled() {
  81. diff := thanosDur - offset
  82. offset += diff
  83. duration -= diff
  84. }
  85. // If duration < 0, return an empty set
  86. if duration < 0 {
  87. return allocSet, nil
  88. }
  89. // Negative offset means that the end time is in the future. Prometheus
  90. // fails for non-positive offset values, so shrink the duration and
  91. // remove the offset altogether.
  92. if offset < 0 {
  93. duration = duration + offset
  94. offset = 0
  95. }
  96. durStr := fmt.Sprintf("%dm", int64(duration.Minutes()))
  97. offStr := fmt.Sprintf(" offset %dm", int64(offset.Minutes()))
  98. if offset < time.Minute {
  99. offStr = ""
  100. }
  101. // TODO niko/cdmr dynamic resolution? add to ComputeAllocation() in allocation.Source?
  102. resStr := "1m"
  103. // resPerHr := 60
  104. // TODO niko/cdmr remove after testing
  105. startQuerying := time.Now()
  106. ctx := prom.NewContext(cm.PrometheusClient)
  107. // TODO niko/cdmr retries? (That should probably go into the Store.)
  108. // TODO niko/cmdr check: will multiple Prometheus jobs multiply the totals?
  109. // TODO niko/cdmr should we try doing this without resolution? Could yield
  110. // more accurate results, but might also be more challenging in some
  111. // respects; e.g. "correcting" the start point by what amount?
  112. queryMinutes := fmt.Sprintf(`avg(kube_pod_container_status_running{}) by (container, pod, namespace, kubernetes_node, cluster_id)[%s:%s]%s`, durStr, resStr, offStr)
  113. resChMinutes := ctx.Query(queryMinutes)
  114. queryRAMBytesAllocated := fmt.Sprintf(`avg(avg_over_time(container_memory_allocation_bytes{container!="", container!="POD", node!=""}[%s]%s)) by (container, pod, namespace, node, cluster_id)`, durStr, offStr)
  115. resChRAMBytesAllocated := ctx.Query(queryRAMBytesAllocated)
  116. queryRAMRequests := fmt.Sprintf(`avg(avg_over_time(kube_pod_container_resource_requests_memory_bytes{container!="", container!="POD", node!=""}[%s]%s)) by (container, pod, namespace, node, cluster_id)`, durStr, offStr)
  117. resChRAMRequests := ctx.Query(queryRAMRequests)
  118. queryRAMUsage := fmt.Sprintf(`avg(avg_over_time(container_memory_working_set_bytes{container_name!="", container_name!="POD", instance!=""}[%s]%s)) by (container_name, pod_name, namespace, instance, cluster_id)`, durStr, offStr)
  119. resChRAMUsage := ctx.Query(queryRAMUsage)
  120. queryCPUCoresAllocated := fmt.Sprintf(`avg(avg_over_time(container_cpu_allocation{container!="", container!="POD", node!=""}[%s]%s)) by (container, pod, namespace, node, cluster_id)`, durStr, offStr)
  121. resChCPUCoresAllocated := ctx.Query(queryCPUCoresAllocated)
  122. queryCPURequests := fmt.Sprintf(`avg(avg_over_time(kube_pod_container_resource_requests_cpu_cores{container!="", container!="POD", node!=""}[%s]%s)) by (container, pod, namespace, node, cluster_id)`, durStr, offStr)
  123. resChCPURequests := ctx.Query(queryCPURequests)
  124. queryCPUUsage := fmt.Sprintf(`avg(rate(container_cpu_usage_seconds_total{container_name!="", container_name!="POD", instance!=""}[%s]%s)) by (container_name, pod_name, namespace, instance, cluster_id)`, durStr, offStr)
  125. resChCPUUsage := ctx.Query(queryCPUUsage)
  126. // TODO niko/cdmr find an env with GPUs to test this (generate one?)
  127. queryGPUsRequested := fmt.Sprintf(`avg(avg_over_time(kube_pod_container_resource_requests{resource="nvidia_com_gpu", container!="",container!="POD", node!=""}[%s]%s)) by (container, pod, namespace, node, cluster_id)`, durStr, offStr)
  128. resChGPUsRequested := ctx.Query(queryGPUsRequested)
  129. queryNodeCostPerCPUHr := fmt.Sprintf(`avg(avg_over_time(node_cpu_hourly_cost[%s]%s)) by (node, cluster_id, instance_type)`, durStr, offStr)
  130. resChNodeCostPerCPUHr := ctx.Query(queryNodeCostPerCPUHr)
  131. queryNodeCostPerRAMGiBHr := fmt.Sprintf(`avg(avg_over_time(node_ram_hourly_cost[%s]%s)) by (node, cluster_id, instance_type)`, durStr, offStr)
  132. resChNodeCostPerRAMGiBHr := ctx.Query(queryNodeCostPerRAMGiBHr)
  133. queryNodeCostPerGPUHr := fmt.Sprintf(`avg(avg_over_time(node_gpu_hourly_cost[%s]%s)) by (node, cluster_id, instance_type)`, durStr, offStr)
  134. resChNodeCostPerGPUHr := ctx.Query(queryNodeCostPerGPUHr)
  135. queryNodeIsSpot := fmt.Sprintf(`avg_over_time(kubecost_node_is_spot[%s]%s)`, durStr, offStr)
  136. resChNodeIsSpot := ctx.Query(queryNodeIsSpot)
  137. queryPVCInfo := fmt.Sprintf(`avg(kube_persistentvolumeclaim_info{volumename != ""}) by (persistentvolumeclaim, storageclass, volumename, namespace, cluster_id)[%s:%s]%s`, durStr, resStr, offStr)
  138. resChPVCInfo := ctx.Query(queryPVCInfo)
  139. queryPVBytes := fmt.Sprintf(`avg(avg_over_time(kube_persistentvolume_capacity_bytes[%s]%s)) by (persistentvolume, cluster_id)`, durStr, offStr)
  140. resChPVBytes := ctx.Query(queryPVBytes)
  141. queryPodPVCAllocation := fmt.Sprintf(`avg(avg_over_time(pod_pvc_allocation[%s]%s)) by (persistentvolume, persistentvolumeclaim, pod, namespace, cluster_id)`, durStr, offStr)
  142. resChPodPVCAllocation := ctx.Query(queryPodPVCAllocation)
  143. queryPVCBytesRequested := fmt.Sprintf(`avg(avg_over_time(kube_persistentvolumeclaim_resource_requests_storage_bytes{}[%s]%s)) by (persistentvolumeclaim, namespace, cluster_id)`, durStr, offStr)
  144. resChPVCBytesRequested := ctx.Query(queryPVCBytesRequested)
  145. queryPVCostPerGiBHour := fmt.Sprintf(`avg(avg_over_time(pv_hourly_cost[%s]%s)) by (volumename, cluster_id)`, durStr, offStr)
  146. resChPVCostPerGiBHour := ctx.Query(queryPVCostPerGiBHour)
  147. // TODO niko/cdmr
  148. // queryNetZoneRequests := fmt.Sprintf()
  149. // resChNetZoneRequests := ctx.Query(queryNetZoneRequests)
  150. // TODO niko/cdmr
  151. // queryNetRegionRequests := fmt.Sprintf()
  152. // resChNetRegionRequests := ctx.Query(queryNetRegionRequests)
  153. // TODO niko/cdmr
  154. // queryNetInternetRequests := fmt.Sprintf()
  155. // resChNetInternetRequests := ctx.Query(queryNetInternetRequests)
  156. // TODO niko/cdmr
  157. // queryNamespaceLabels := fmt.Sprintf()
  158. // resChNamespaceLabels := ctx.Query(queryNamespaceLabels)
  159. // TODO niko/cdmr
  160. // queryPodLabels := fmt.Sprintf()
  161. // resChPodLabels := ctx.Query(queryPodLabels)
  162. // TODO niko/cdmr
  163. // queryNamespaceAnnotations := fmt.Sprintf()
  164. // resChNamespaceAnnotations := ctx.Query(queryNamespaceAnnotations)
  165. // TODO niko/cdmr
  166. // queryPodAnnotations := fmt.Sprintf()
  167. // resChPodAnnotations := ctx.Query(queryPodAnnotations)
  168. // TODO niko/cdmr
  169. // queryServiceLabels := fmt.Sprintf()
  170. // resChServiceLabels := ctx.Query(queryServiceLabels)
  171. // TODO niko/cdmr
  172. // queryDeploymentLabels := fmt.Sprintf()
  173. // resChDeploymentLabels := ctx.Query(queryDeploymentLabels)
  174. // TODO niko/cdmr
  175. // queryStatefulSetLabels := fmt.Sprintf()
  176. // resChStatefulSetLabels := ctx.Query(queryStatefulSetLabels)
  177. // TODO niko/cdmr
  178. // queryDaemonSetLabels := fmt.Sprintf()
  179. // resChDaemonSetLabels := ctx.Query(queryDaemonSetLabels)
  180. // TODO niko/cdmr
  181. // queryJobLabels := fmt.Sprintf()
  182. // resChJobLabels := ctx.Query(queryJobLabels)
  183. resMinutes, _ := resChMinutes.Await()
  184. resCPUCoresAllocated, _ := resChCPUCoresAllocated.Await()
  185. resCPURequests, _ := resChCPURequests.Await()
  186. resCPUUsage, _ := resChCPUUsage.Await()
  187. resRAMBytesAllocated, _ := resChRAMBytesAllocated.Await()
  188. resRAMRequests, _ := resChRAMRequests.Await()
  189. resRAMUsage, _ := resChRAMUsage.Await()
  190. resGPUsRequested, _ := resChGPUsRequested.Await()
  191. resNodeCostPerCPUHr, _ := resChNodeCostPerCPUHr.Await()
  192. resNodeCostPerRAMGiBHr, _ := resChNodeCostPerRAMGiBHr.Await()
  193. resNodeCostPerGPUHr, _ := resChNodeCostPerGPUHr.Await()
  194. resNodeIsSpot, _ := resChNodeIsSpot.Await()
  195. resPVBytes, _ := resChPVBytes.Await()
  196. resPVCostPerGiBHour, _ := resChPVCostPerGiBHour.Await()
  197. resPVCInfo, _ := resChPVCInfo.Await()
  198. resPVCBytesRequested, _ := resChPVCBytesRequested.Await()
  199. resPodPVCAllocation, _ := resChPodPVCAllocation.Await()
  200. // TODO niko/cdmr remove after testing
  201. log.Infof("CostModel.ComputeAllocation: minutes : %s", queryMinutes)
  202. log.Infof("CostModel.ComputeAllocation: CPU cores: %s", queryCPUCoresAllocated)
  203. log.Infof("CostModel.ComputeAllocation: CPU req : %s", queryCPURequests)
  204. log.Infof("CostModel.ComputeAllocation: CPU use : %s", queryCPUUsage)
  205. log.Infof("CostModel.ComputeAllocation: $/CPU*Hr : %s", queryNodeCostPerCPUHr)
  206. log.Infof("CostModel.ComputeAllocation: RAM bytes: %s", queryRAMBytesAllocated)
  207. log.Infof("CostModel.ComputeAllocation: RAM req : %s", queryRAMRequests)
  208. log.Infof("CostModel.ComputeAllocation: RAM use : %s", queryRAMUsage)
  209. log.Infof("CostModel.ComputeAllocation: $/GiB*Hr : %s", queryNodeCostPerRAMGiBHr)
  210. log.Infof("CostModel.ComputeAllocation: PV $/gbhr: %s", queryPVCostPerGiBHour)
  211. log.Infof("CostModel.ComputeAllocation: PV bytes : %s", queryPVBytes)
  212. log.Infof("CostModel.ComputeAllocation: PVC alloc: %s", queryPodPVCAllocation)
  213. log.Infof("CostModel.ComputeAllocation: PVC bytes: %s", queryPVCBytesRequested)
  214. log.Infof("CostModel.ComputeAllocation: PVC info : %s", queryPVCInfo)
  215. log.Profile(startQuerying, "CostModel.ComputeAllocation: queries complete")
  216. // Build out a map of Allocations, starting with (start, end) so that we
  217. // begin with minutes, from which we compute resource allocation and cost
  218. // totals from measured rate data.
  219. // TODO niko/cdmr can we start with a reasonable guess at map size?
  220. allocationMap := map[containerKey]*kubecost.Allocation{}
  221. // Keep track of the number of allocations per pod, for the sake of
  222. // splitting PVC allocation into per-Allocation from per-Pod.
  223. podAllocationCount := map[podKey]int{}
  224. // clusterStarts and clusterEnds record the earliest start and latest end
  225. // times, respectively, on a cluster-basis. These are used for unmounted
  226. // PVs and other "virtual" Allocations so that minutes are maximally
  227. // accurate during start-up or spin-down of a cluster
  228. clusterStart := map[string]time.Time{}
  229. clusterEnd := map[string]time.Time{}
  230. buildAllocationMap(window, allocationMap, podAllocationCount, clusterStart, clusterEnd, resMinutes)
  231. applyCPUCoresAllocated(allocationMap, resCPUCoresAllocated)
  232. applyCPUCoresRequested(allocationMap, resCPURequests)
  233. applyCPUCoresUsed(allocationMap, resCPUUsage)
  234. applyRAMBytesAllocated(allocationMap, resRAMBytesAllocated)
  235. applyRAMBytesRequested(allocationMap, resRAMRequests)
  236. applyRAMBytesUsed(allocationMap, resRAMUsage)
  237. applyGPUsRequested(allocationMap, resGPUsRequested)
  238. // Build out a map of Nodes with resource costs, discounts, and node types
  239. // for converting resource allocation data to cumulative costs.
  240. nodeMap := map[nodeKey]*Node{}
  241. applyNodeCostPerCPUHr(nodeMap, resNodeCostPerCPUHr)
  242. applyNodeCostPerRAMGiBHr(nodeMap, resNodeCostPerRAMGiBHr)
  243. applyNodeCostPerGPUHr(nodeMap, resNodeCostPerGPUHr)
  244. applyNodeSpot(nodeMap, resNodeIsSpot)
  245. applyNodeDiscount(nodeMap, cm)
  246. // TODO niko/cdmr comment
  247. pvMap := map[pvKey]*PV{}
  248. buildPVMap(pvMap, resPVCostPerGiBHour)
  249. applyPVBytes(pvMap, resPVBytes)
  250. // TODO niko/cdmr apply PV bytes?
  251. // TODO niko/cdmr comment
  252. pvcMap := map[pvcKey]*PVC{}
  253. buildPVCMap(window, pvcMap, pvMap, resPVCInfo)
  254. applyPVCBytesRequested(pvcMap, resPVCBytesRequested)
  255. // TODO niko/cdmr comment
  256. podPVCMap := map[podKey][]*PVC{}
  257. buildPodPVCMap(podPVCMap, pvMap, pvcMap, podAllocationCount, resPodPVCAllocation)
  258. // Identify unmounted PVs (PVs without PVCs) and add one Allocation per
  259. // cluster representing each cluster's unmounted PVs (if necessary).
  260. applyUnmountedPVs(window, allocationMap, pvMap, pvcMap)
  261. // TODO niko/cdmr remove logs
  262. log.Infof("CostModel.ComputeAllocation: %d allocations", len(allocationMap))
  263. log.Infof("CostModel.ComputeAllocation: %d nodes", len(nodeMap))
  264. log.Infof("CostModel.ComputeAllocation: %d PVs", len(pvMap))
  265. log.Infof("CostModel.ComputeAllocation: %d PVCs", len(pvcMap))
  266. log.Infof("CostModel.ComputeAllocation: %d pods with PVCs", len(podPVCMap))
  267. for _, node := range nodeMap {
  268. log.Infof("CostModel.ComputeAllocation: Node: %s: %f/CPUHr; %f/RAMHr; %f/GPUHr; %f discount", node.Name, node.CostPerCPUHr, node.CostPerRAMGiBHr, node.CostPerGPUHr, node.Discount)
  269. }
  270. for _, pv := range pvMap {
  271. log.Infof("CostModel.ComputeAllocation: PV: %s", pv)
  272. }
  273. for pod, pvcs := range podPVCMap {
  274. for _, pvc := range pvcs {
  275. log.Infof("CostModel.ComputeAllocation: Pod %s: PVC: %s", pod, pvc)
  276. }
  277. }
  278. for _, alloc := range allocationMap {
  279. cluster, _ := alloc.Properties.GetCluster()
  280. node, _ := alloc.Properties.GetNode()
  281. namespace, _ := alloc.Properties.GetNamespace()
  282. pod, _ := alloc.Properties.GetPod()
  283. podKey := newPodKey(cluster, namespace, pod)
  284. nodeKey := newNodeKey(cluster, node)
  285. if n, ok := nodeMap[nodeKey]; !ok {
  286. if pod != "unmounted-pvs" {
  287. log.Warningf("CostModel.ComputeAllocation: failed to find node %s for %s", nodeKey, alloc.Name)
  288. }
  289. } else {
  290. alloc.CPUCost = alloc.CPUCoreHours * n.CostPerCPUHr
  291. alloc.RAMCost = (alloc.RAMByteHours / 1024 / 1024 / 1024) * n.CostPerRAMGiBHr
  292. alloc.GPUCost = alloc.GPUHours * n.CostPerGPUHr
  293. }
  294. if pvcs, ok := podPVCMap[podKey]; ok {
  295. for _, pvc := range pvcs {
  296. // Determine the (start, end) of the relationship between the
  297. // given PVC and the associated Allocation so that a precise
  298. // number of hours can be used to compute cumulative cost.
  299. s, e := alloc.Start, alloc.End
  300. if pvc.Start.After(alloc.Start) {
  301. s = pvc.Start
  302. }
  303. if pvc.End.Before(alloc.End) {
  304. e = pvc.End
  305. }
  306. minutes := e.Sub(s).Minutes()
  307. hrs := minutes / 60.0
  308. gib := pvc.Bytes / 1024 / 1024 / 1024
  309. alloc.PVByteHours += pvc.Bytes * hrs
  310. alloc.PVCost += pvc.Volume.CostPerGiBHour * gib * hrs / float64(pvc.Count)
  311. }
  312. }
  313. alloc.TotalCost = 0.0
  314. alloc.TotalCost += alloc.CPUCost
  315. alloc.TotalCost += alloc.RAMCost
  316. alloc.TotalCost += alloc.GPUCost
  317. alloc.TotalCost += alloc.PVCost
  318. alloc.TotalCost += alloc.NetworkCost
  319. alloc.TotalCost += alloc.SharedCost
  320. alloc.TotalCost += alloc.ExternalCost
  321. allocSet.Set(alloc)
  322. }
  323. return allocSet, nil
  324. }
  325. func buildAllocationMap(window kubecost.Window, allocationMap map[containerKey]*kubecost.Allocation, podAllocationCount map[podKey]int, clusterStart, clusterEnd map[string]time.Time, resMinutes []*prom.QueryResult) {
  326. for _, res := range resMinutes {
  327. if len(res.Values) == 0 {
  328. log.Warningf("CostModel.ComputeAllocation: empty minutes result")
  329. continue
  330. }
  331. cluster, err := res.GetString("cluster_id")
  332. if err != nil {
  333. cluster = env.GetClusterID()
  334. }
  335. labels, err := res.GetStrings("kubernetes_node", "namespace", "pod", "container")
  336. if err != nil {
  337. log.Warningf("CostModel.ComputeAllocation: minutes query result missing field: %s", err)
  338. continue
  339. }
  340. node := labels["kubernetes_node"]
  341. namespace := labels["namespace"]
  342. pod := labels["pod"]
  343. container := labels["container"]
  344. containerKey := newContainerKey(cluster, namespace, pod, container)
  345. podKey := newPodKey(cluster, namespace, pod)
  346. // allocStart and allocEnd are the timestamps of the first and last
  347. // minutes the allocation was running, respectively. We subtract 1m
  348. // from allocStart because this point will actually represent the end
  349. // of the first minute. We don't subtract from allocEnd because it
  350. // already represents the end of the last minute.
  351. var allocStart, allocEnd time.Time
  352. for _, datum := range res.Values {
  353. t := time.Unix(int64(datum.Timestamp), 0)
  354. if allocStart.IsZero() && datum.Value > 0 && window.Contains(t) {
  355. allocStart = t
  356. }
  357. if datum.Value > 0 && window.Contains(t) {
  358. allocEnd = t
  359. }
  360. }
  361. if allocStart.IsZero() || allocEnd.IsZero() {
  362. // TODO niko/cdmr remove log?
  363. // log.Warningf("CostModel.ComputeAllocation: allocation %s has no running time, skipping", containerKey)
  364. continue
  365. }
  366. allocStart = allocStart.Add(-time.Minute)
  367. // Set start if unset or this datum's start time is earlier than the
  368. // current earliest time.
  369. if _, ok := clusterStart[cluster]; !ok || allocStart.Before(clusterStart[cluster]) {
  370. clusterStart[cluster] = allocStart
  371. }
  372. // Set end if unset or this datum's end time is later than the
  373. // current latest time.
  374. if _, ok := clusterEnd[cluster]; !ok || allocEnd.After(clusterEnd[cluster]) {
  375. clusterEnd[cluster] = allocEnd
  376. }
  377. name := fmt.Sprintf("%s/%s/%s/%s", cluster, namespace, pod, container)
  378. alloc := &kubecost.Allocation{
  379. Name: name,
  380. Properties: kubecost.Properties{},
  381. Window: window.Clone(),
  382. Start: allocStart,
  383. End: allocEnd,
  384. }
  385. alloc.Properties.SetContainer(container)
  386. alloc.Properties.SetPod(pod)
  387. alloc.Properties.SetNamespace(namespace)
  388. alloc.Properties.SetNode(node)
  389. alloc.Properties.SetCluster(cluster)
  390. allocationMap[containerKey] = alloc
  391. podAllocationCount[podKey]++
  392. }
  393. }
  394. func applyCPUCoresAllocated(allocationMap map[containerKey]*kubecost.Allocation, resCPUCoresAllocated []*prom.QueryResult) {
  395. for _, res := range resCPUCoresAllocated {
  396. // TODO niko/cdmr do we need node here?
  397. key, err := resultContainerKey(res, "cluster_id", "namespace", "pod", "container")
  398. if err != nil {
  399. log.Warningf("CostModel.ComputeAllocation: CPU allocation query result missing field: %s", err)
  400. continue
  401. }
  402. _, ok := allocationMap[key]
  403. if !ok {
  404. log.Warningf("CostModel.ComputeAllocation: unidentified CPU allocation query result: %s", key)
  405. continue
  406. }
  407. cpuCores := res.Values[0].Value
  408. hours := allocationMap[key].Minutes() / 60.0
  409. allocationMap[key].CPUCoreHours = cpuCores * hours
  410. }
  411. }
  412. func applyCPUCoresRequested(allocationMap map[containerKey]*kubecost.Allocation, resCPUCoresRequested []*prom.QueryResult) {
  413. for _, res := range resCPUCoresRequested {
  414. key, err := resultContainerKey(res, "cluster_id", "namespace", "pod", "container")
  415. if err != nil {
  416. log.Warningf("CostModel.ComputeAllocation: CPU request query result missing field: %s", err)
  417. continue
  418. }
  419. _, ok := allocationMap[key]
  420. if !ok {
  421. // TODO niko/cdmr remove log?
  422. // log.Warningf("CostModel.ComputeAllocation: unidentified CPU request query result: %s", key)
  423. continue
  424. }
  425. allocationMap[key].CPUCoreRequestAverage = res.Values[0].Value
  426. // CPU allocation is less than requests, so set CPUCoreHours to
  427. // request level.
  428. // TODO niko/cdmr why is this happening?
  429. if allocationMap[key].CPUCores() < res.Values[0].Value {
  430. allocationMap[key].CPUCoreHours = res.Values[0].Value * (allocationMap[key].Minutes() / 60.0)
  431. }
  432. }
  433. }
  434. func applyCPUCoresUsed(allocationMap map[containerKey]*kubecost.Allocation, resCPUCoresUsed []*prom.QueryResult) {
  435. for _, res := range resCPUCoresUsed {
  436. key, err := resultContainerKey(res, "cluster_id", "namespace", "pod_name", "container_name")
  437. if err != nil {
  438. log.Warningf("CostModel.ComputeAllocation: CPU usage query result missing field: %s", err)
  439. continue
  440. }
  441. _, ok := allocationMap[key]
  442. if !ok {
  443. log.Warningf("CostModel.ComputeAllocation: unidentified CPU usage query result: %s", key)
  444. continue
  445. }
  446. allocationMap[key].CPUCoreUsageAverage = res.Values[0].Value
  447. }
  448. }
  449. func applyRAMBytesRequested(allocationMap map[containerKey]*kubecost.Allocation, resRAMBytesRequested []*prom.QueryResult) {
  450. for _, res := range resRAMBytesRequested {
  451. key, err := resultContainerKey(res, "cluster_id", "namespace", "pod", "container")
  452. if err != nil {
  453. log.Warningf("CostModel.ComputeAllocation: RAM request query result missing field: %s", err)
  454. continue
  455. }
  456. _, ok := allocationMap[key]
  457. if !ok {
  458. // TODO niko/cdmr remove log?
  459. // log.Warningf("CostModel.ComputeAllocation: unidentified RAM request query result: %s", key)
  460. continue
  461. }
  462. allocationMap[key].RAMBytesRequestAverage = res.Values[0].Value
  463. // RAM allocation is less than requests, so set RAMByteHours to
  464. // request level.
  465. // TODO niko/cdmr why is this happening?
  466. if allocationMap[key].RAMBytes() < res.Values[0].Value {
  467. allocationMap[key].RAMByteHours = res.Values[0].Value * (allocationMap[key].Minutes() / 60.0)
  468. }
  469. }
  470. }
  471. func applyRAMBytesUsed(allocationMap map[containerKey]*kubecost.Allocation, resRAMBytesUsed []*prom.QueryResult) {
  472. for _, res := range resRAMBytesUsed {
  473. key, err := resultContainerKey(res, "cluster_id", "namespace", "pod_name", "container_name")
  474. if err != nil {
  475. log.Warningf("CostModel.ComputeAllocation: RAM usage query result missing field: %s", err)
  476. continue
  477. }
  478. _, ok := allocationMap[key]
  479. if !ok {
  480. log.Warningf("CostModel.ComputeAllocation: unidentified RAM usage query result: %s", key)
  481. continue
  482. }
  483. allocationMap[key].RAMBytesUsageAverage = res.Values[0].Value
  484. }
  485. }
  486. func applyRAMBytesAllocated(allocationMap map[containerKey]*kubecost.Allocation, resRAMBytesAllocated []*prom.QueryResult) {
  487. for _, res := range resRAMBytesAllocated {
  488. // TODO niko/cdmr do we need node here?
  489. key, err := resultContainerKey(res, "cluster_id", "namespace", "pod", "container")
  490. if err != nil {
  491. log.Warningf("CostModel.ComputeAllocation: RAM allocation query result missing field: %s", err)
  492. continue
  493. }
  494. _, ok := allocationMap[key]
  495. if !ok {
  496. log.Warningf("CostModel.ComputeAllocation: unidentified RAM allocation query result: %s", key)
  497. continue
  498. }
  499. ramBytes := res.Values[0].Value
  500. hours := allocationMap[key].Minutes() / 60.0
  501. allocationMap[key].RAMByteHours = ramBytes * hours
  502. }
  503. }
  504. func applyGPUsRequested(allocationMap map[containerKey]*kubecost.Allocation, resGPUsRequested []*prom.QueryResult) {
  505. for _, res := range resGPUsRequested {
  506. // TODO niko/cdmr do we need node here?
  507. key, err := resultContainerKey(res, "cluster_id", "namespace", "pod", "container")
  508. if err != nil {
  509. log.Warningf("CostModel.ComputeAllocation: GPU allocation query result missing field: %s", err)
  510. continue
  511. }
  512. _, ok := allocationMap[key]
  513. if !ok {
  514. log.Warningf("CostModel.ComputeAllocation: unidentified GPU allocation query result: %s", key)
  515. continue
  516. }
  517. // TODO niko/cdmr complete
  518. log.Infof("CostModel.ComputeAllocation: GPU results: %s=%f", key, res.Values[0].Value)
  519. }
  520. }
  521. func applyNodeCostPerCPUHr(nodeMap map[nodeKey]*Node, resNodeCostPerCPUHr []*prom.QueryResult) {
  522. for _, res := range resNodeCostPerCPUHr {
  523. cluster, err := res.GetString("cluster_id")
  524. if err != nil {
  525. cluster = env.GetClusterID()
  526. }
  527. node, err := res.GetString("node")
  528. if err != nil {
  529. log.Warningf("CostModel.ComputeAllocation: Node CPU cost query result missing field: %s", err)
  530. continue
  531. }
  532. instanceType, err := res.GetString("instance_type")
  533. if err != nil {
  534. log.Warningf("CostModel.ComputeAllocation: Node CPU cost query result missing field: %s", err)
  535. continue
  536. }
  537. key := newNodeKey(cluster, node)
  538. if _, ok := nodeMap[key]; !ok {
  539. nodeMap[key] = &Node{
  540. Name: node,
  541. NodeType: instanceType,
  542. }
  543. }
  544. nodeMap[key].CostPerCPUHr = res.Values[0].Value
  545. }
  546. }
  547. func applyNodeCostPerRAMGiBHr(nodeMap map[nodeKey]*Node, resNodeCostPerRAMGiBHr []*prom.QueryResult) {
  548. for _, res := range resNodeCostPerRAMGiBHr {
  549. cluster, err := res.GetString("cluster_id")
  550. if err != nil {
  551. cluster = env.GetClusterID()
  552. }
  553. node, err := res.GetString("node")
  554. if err != nil {
  555. log.Warningf("CostModel.ComputeAllocation: Node RAM cost query result missing field: %s", err)
  556. continue
  557. }
  558. instanceType, err := res.GetString("instance_type")
  559. if err != nil {
  560. log.Warningf("CostModel.ComputeAllocation: Node RAM cost query result missing field: %s", err)
  561. continue
  562. }
  563. key := newNodeKey(cluster, node)
  564. if _, ok := nodeMap[key]; !ok {
  565. nodeMap[key] = &Node{
  566. Name: node,
  567. NodeType: instanceType,
  568. }
  569. }
  570. nodeMap[key].CostPerRAMGiBHr = res.Values[0].Value
  571. }
  572. }
  573. func applyNodeCostPerGPUHr(nodeMap map[nodeKey]*Node, resNodeCostPerGPUHr []*prom.QueryResult) {
  574. for _, res := range resNodeCostPerGPUHr {
  575. cluster, err := res.GetString("cluster_id")
  576. if err != nil {
  577. cluster = env.GetClusterID()
  578. }
  579. node, err := res.GetString("node")
  580. if err != nil {
  581. log.Warningf("CostModel.ComputeAllocation: Node GPU cost query result missing field: %s", err)
  582. continue
  583. }
  584. instanceType, err := res.GetString("instance_type")
  585. if err != nil {
  586. log.Warningf("CostModel.ComputeAllocation: Node GPU cost query result missing field: %s", err)
  587. continue
  588. }
  589. key := newNodeKey(cluster, node)
  590. if _, ok := nodeMap[key]; !ok {
  591. nodeMap[key] = &Node{
  592. Name: node,
  593. NodeType: instanceType,
  594. }
  595. }
  596. nodeMap[key].CostPerGPUHr = res.Values[0].Value
  597. }
  598. }
  599. func applyNodeSpot(nodeMap map[nodeKey]*Node, resNodeIsSpot []*prom.QueryResult) {
  600. for _, res := range resNodeIsSpot {
  601. cluster, err := res.GetString("cluster_id")
  602. if err != nil {
  603. cluster = env.GetClusterID()
  604. }
  605. node, err := res.GetString("node")
  606. if err != nil {
  607. log.Warningf("CostModel.ComputeAllocation: Node spot query result missing field: %s", err)
  608. continue
  609. }
  610. key := newNodeKey(cluster, node)
  611. if _, ok := nodeMap[key]; !ok {
  612. log.Warningf("CostModel.ComputeAllocation: Node spot query result for missing node: %s", key)
  613. continue
  614. }
  615. nodeMap[key].Preemptible = res.Values[0].Value > 0
  616. }
  617. }
  618. func applyNodeDiscount(nodeMap map[nodeKey]*Node, cm *CostModel) {
  619. if cm == nil {
  620. return
  621. }
  622. c, err := cm.Provider.GetConfig()
  623. if err != nil {
  624. log.Errorf("CostModel.ComputeAllocation: applyNodeDiscount: %s", err)
  625. return
  626. }
  627. discount, err := ParsePercentString(c.Discount)
  628. if err != nil {
  629. log.Errorf("CostModel.ComputeAllocation: applyNodeDiscount: %s", err)
  630. return
  631. }
  632. negotiatedDiscount, err := ParsePercentString(c.NegotiatedDiscount)
  633. if err != nil {
  634. log.Errorf("CostModel.ComputeAllocation: applyNodeDiscount: %s", err)
  635. return
  636. }
  637. for _, node := range nodeMap {
  638. // TODO niko/cdmr take RI into account?
  639. node.Discount = cm.Provider.CombinedDiscountForNode(node.NodeType, node.Preemptible, discount, negotiatedDiscount)
  640. node.CostPerCPUHr *= (1.0 - node.Discount)
  641. node.CostPerRAMGiBHr *= (1.0 - node.Discount)
  642. }
  643. }
  644. func buildPVMap(pvMap map[pvKey]*PV, resPVCostPerGiBHour []*prom.QueryResult) {
  645. for _, res := range resPVCostPerGiBHour {
  646. cluster, err := res.GetString("cluster_id")
  647. if err != nil {
  648. cluster = env.GetClusterID()
  649. }
  650. name, err := res.GetString("volumename")
  651. if err != nil {
  652. log.Warningf("CostModel.ComputeAllocation: PV cost without volumename")
  653. continue
  654. }
  655. key := newPVKey(cluster, name)
  656. pvMap[key] = &PV{
  657. Cluster: cluster,
  658. Name: name,
  659. CostPerGiBHour: res.Values[0].Value,
  660. }
  661. }
  662. }
  663. func applyPVBytes(pvMap map[pvKey]*PV, resPVBytes []*prom.QueryResult) {
  664. for _, res := range resPVBytes {
  665. key, err := resultPVKey(res, "cluster_id", "persistentvolume")
  666. if err != nil {
  667. log.Warningf("CostModel.ComputeAllocation: PV bytes query result missing field: %s", err)
  668. continue
  669. }
  670. if _, ok := pvMap[key]; !ok {
  671. log.Warningf("CostModel.ComputeAllocation: PV bytes result for missing PV: %s", err)
  672. continue
  673. }
  674. pvMap[key].Bytes = res.Values[0].Value
  675. }
  676. }
  677. func buildPVCMap(window kubecost.Window, pvcMap map[pvcKey]*PVC, pvMap map[pvKey]*PV, resPVCInfo []*prom.QueryResult) {
  678. for _, res := range resPVCInfo {
  679. cluster, err := res.GetString("cluster_id")
  680. if err != nil {
  681. cluster = env.GetClusterID()
  682. }
  683. values, err := res.GetStrings("persistentvolumeclaim", "storageclass", "volumename", "namespace")
  684. if err != nil {
  685. log.Warningf("CostModel.ComputeAllocation: PVC info query result missing field: %s", err)
  686. continue
  687. }
  688. // TODO niko/cdmr ?
  689. namespace := values["namespace"]
  690. name := values["persistentvolumeclaim"]
  691. volume := values["volumename"]
  692. storageClass := values["storageclass"]
  693. pvKey := newPVKey(cluster, volume)
  694. pvcKey := newPVCKey(cluster, namespace, name)
  695. // pvcStart and pvcEnd are the timestamps of the first and last minutes
  696. // the PVC was running, respectively. We subtract 1m from pvcStart
  697. // because this point will actually represent the end of the first
  698. // minute. We don't subtract from pvcEnd because it already represents
  699. // the end of the last minute.
  700. var pvcStart, pvcEnd time.Time
  701. for _, datum := range res.Values {
  702. t := time.Unix(int64(datum.Timestamp), 0)
  703. if pvcStart.IsZero() && datum.Value > 0 && window.Contains(t) {
  704. pvcStart = t
  705. }
  706. if datum.Value > 0 && window.Contains(t) {
  707. pvcEnd = t
  708. }
  709. }
  710. if pvcStart.IsZero() || pvcEnd.IsZero() {
  711. log.Warningf("CostModel.ComputeAllocation: PVC %s has no running time", pvcKey)
  712. }
  713. pvcStart = pvcStart.Add(-time.Minute)
  714. if _, ok := pvMap[pvKey]; !ok {
  715. log.Warningf("CostModel.ComputeAllocation: PV missing for PVC info query result: %s", pvKey)
  716. continue
  717. }
  718. pvMap[pvKey].StorageClass = storageClass
  719. if _, ok := pvcMap[pvcKey]; !ok {
  720. pvcMap[pvcKey] = &PVC{}
  721. }
  722. pvcMap[pvcKey].Name = name
  723. pvcMap[pvcKey].Namespace = namespace
  724. pvcMap[pvcKey].Volume = pvMap[pvKey]
  725. pvcMap[pvcKey].Start = pvcStart
  726. pvcMap[pvcKey].End = pvcEnd
  727. }
  728. }
  729. func applyPVCBytesRequested(pvcMap map[pvcKey]*PVC, resPVCBytesRequested []*prom.QueryResult) {
  730. for _, res := range resPVCBytesRequested {
  731. key, err := resultPVCKey(res, "cluster_id", "namespace", "persistentvolumeclaim")
  732. if err != nil {
  733. log.Warningf("CostModel.ComputeAllocation: PVC bytes requested query result missing field: %s", err)
  734. continue
  735. }
  736. if _, ok := pvcMap[key]; !ok {
  737. log.Warningf("CostModel.ComputeAllocation: PVC bytes requested result for missing PVC: %s", err)
  738. continue
  739. }
  740. pvcMap[key].Bytes = res.Values[0].Value
  741. }
  742. }
  743. func buildPodPVCMap(podPVCMap map[podKey][]*PVC, pvMap map[pvKey]*PV, pvcMap map[pvcKey]*PVC, podAllocationCount map[podKey]int, resPodPVCAllocation []*prom.QueryResult) {
  744. for _, res := range resPodPVCAllocation {
  745. cluster, err := res.GetString("cluster_id")
  746. if err != nil {
  747. cluster = env.GetClusterID()
  748. }
  749. values, err := res.GetStrings("persistentvolume", "persistentvolumeclaim", "pod", "namespace")
  750. if err != nil {
  751. log.Warningf("CostModel.ComputeAllocation: PVC allocation query result missing field: %s", err)
  752. continue
  753. }
  754. namespace := values["namespace"]
  755. pod := values["pod"]
  756. name := values["persistentvolumeclaim"]
  757. volume := values["persistentvolume"]
  758. podKey := newPodKey(cluster, namespace, pod)
  759. pvKey := newPVKey(cluster, volume)
  760. pvcKey := newPVCKey(cluster, namespace, name)
  761. if _, ok := pvMap[pvKey]; !ok {
  762. log.Warningf("CostModel.ComputeAllocation: PV missing for PVC allocation query result: %s", pvKey)
  763. continue
  764. }
  765. if _, ok := podPVCMap[podKey]; !ok {
  766. podPVCMap[podKey] = []*PVC{}
  767. }
  768. pvc, ok := pvcMap[pvcKey]
  769. if !ok {
  770. log.Warningf("CostModel.ComputeAllocation: PVC missing for PVC allocation query: %s", pvcKey)
  771. continue
  772. }
  773. pvc.Count = podAllocationCount[podKey]
  774. podPVCMap[podKey] = append(podPVCMap[podKey], pvc)
  775. }
  776. }
  777. func applyUnmountedPVs(window kubecost.Window, allocationMap map[containerKey]*kubecost.Allocation, pvMap map[pvKey]*PV, pvcMap map[pvcKey]*PVC) {
  778. unmountedPVBytes := map[string]float64{}
  779. unmountedPVCost := map[string]float64{}
  780. for _, pv := range pvMap {
  781. mounted := false
  782. for _, pvc := range pvcMap {
  783. if pvc.Volume == nil {
  784. continue
  785. }
  786. if pvc.Volume == pv {
  787. mounted = true
  788. break
  789. }
  790. }
  791. log.Infof("CostModel.ComputeAllocation: PV %s is mounted? %t", pv.Name, mounted)
  792. if !mounted {
  793. gib := pv.Bytes / 1024 / 1024 / 1024
  794. hrs := window.Minutes() / 60.0
  795. cost := pv.CostPerGiBHour * gib * hrs
  796. unmountedPVCost[pv.Cluster] += cost
  797. unmountedPVBytes[pv.Cluster] += pv.Bytes
  798. }
  799. }
  800. for cluster, amount := range unmountedPVCost {
  801. container := "unmounted-pvs"
  802. pod := "unmounted-pvs"
  803. namespace := "" // TODO niko/cdmr what about this?
  804. containerKey := newContainerKey(cluster, namespace, pod, container)
  805. allocationMap[containerKey] = &kubecost.Allocation{
  806. Name: fmt.Sprintf("%s/%s/%s/%s", cluster, namespace, pod, container),
  807. Properties: kubecost.Properties{
  808. kubecost.ClusterProp: cluster,
  809. kubecost.NamespaceProp: namespace,
  810. kubecost.PodProp: pod,
  811. kubecost.ContainerProp: container,
  812. },
  813. Window: window.Clone(),
  814. Start: *window.Start(),
  815. End: *window.End(),
  816. PVByteHours: unmountedPVBytes[cluster] * window.Minutes() / 60.0,
  817. PVCost: amount,
  818. TotalCost: amount,
  819. }
  820. }
  821. }
  822. type containerKey struct {
  823. Cluster string
  824. Namespace string
  825. Pod string
  826. Container string
  827. }
  828. func (k containerKey) String() string {
  829. return fmt.Sprintf("%s/%s/%s/%s", k.Cluster, k.Namespace, k.Pod, k.Container)
  830. }
  831. func newContainerKey(cluster, namespace, pod, container string) containerKey {
  832. return containerKey{
  833. Cluster: cluster,
  834. Namespace: namespace,
  835. Pod: pod,
  836. Container: container,
  837. }
  838. }
  839. func resultContainerKey(res *prom.QueryResult, clusterLabel, namespaceLabel, podLabel, containerLabel string) (containerKey, error) {
  840. key := containerKey{}
  841. cluster, err := res.GetString(clusterLabel)
  842. if err != nil {
  843. cluster = env.GetClusterID()
  844. }
  845. key.Cluster = cluster
  846. namespace, err := res.GetString(namespaceLabel)
  847. if err != nil {
  848. return key, err
  849. }
  850. key.Namespace = namespace
  851. pod, err := res.GetString(podLabel)
  852. if err != nil {
  853. return key, err
  854. }
  855. key.Pod = pod
  856. container, err := res.GetString(containerLabel)
  857. if err != nil {
  858. return key, err
  859. }
  860. key.Container = container
  861. return key, nil
  862. }
  863. type podKey struct {
  864. Cluster string
  865. Namespace string
  866. Pod string
  867. }
  868. func (k podKey) String() string {
  869. return fmt.Sprintf("%s/%s/%s", k.Cluster, k.Namespace, k.Pod)
  870. }
  871. func newPodKey(cluster, namespace, pod string) podKey {
  872. return podKey{
  873. Cluster: cluster,
  874. Namespace: namespace,
  875. Pod: pod,
  876. }
  877. }
  878. func resultPodKey(res *prom.QueryResult, clusterLabel, namespaceLabel, podLabel string) (podKey, error) {
  879. key := podKey{}
  880. cluster, err := res.GetString(clusterLabel)
  881. if err != nil {
  882. cluster = env.GetClusterID()
  883. }
  884. key.Cluster = cluster
  885. namespace, err := res.GetString(namespaceLabel)
  886. if err != nil {
  887. return key, err
  888. }
  889. key.Namespace = namespace
  890. pod, err := res.GetString(podLabel)
  891. if err != nil {
  892. return key, err
  893. }
  894. key.Pod = pod
  895. return key, nil
  896. }
  897. type nodeKey struct {
  898. Cluster string
  899. Node string
  900. }
  901. func (k nodeKey) String() string {
  902. return fmt.Sprintf("%s/%s", k.Cluster, k.Node)
  903. }
  904. func newNodeKey(cluster, node string) nodeKey {
  905. return nodeKey{
  906. Cluster: cluster,
  907. Node: node,
  908. }
  909. }
  910. func resultNodeKey(res *prom.QueryResult, clusterLabel, nodeLabel string) (nodeKey, error) {
  911. key := nodeKey{}
  912. cluster, err := res.GetString(clusterLabel)
  913. if err != nil {
  914. cluster = env.GetClusterID()
  915. }
  916. key.Cluster = cluster
  917. node, err := res.GetString(nodeLabel)
  918. if err != nil {
  919. return key, err
  920. }
  921. key.Node = node
  922. return key, nil
  923. }
  924. type pvcKey struct {
  925. Cluster string
  926. Namespace string
  927. PersistentVolumeClaim string
  928. }
  929. func (k pvcKey) String() string {
  930. return fmt.Sprintf("%s/%s/%s", k.Cluster, k.Namespace, k.PersistentVolumeClaim)
  931. }
  932. func newPVCKey(cluster, namespace, persistentVolumeClaim string) pvcKey {
  933. return pvcKey{
  934. Cluster: cluster,
  935. Namespace: namespace,
  936. PersistentVolumeClaim: persistentVolumeClaim,
  937. }
  938. }
  939. func resultPVCKey(res *prom.QueryResult, clusterLabel, namespaceLabel, pvcLabel string) (pvcKey, error) {
  940. key := pvcKey{}
  941. cluster, err := res.GetString(clusterLabel)
  942. if err != nil {
  943. cluster = env.GetClusterID()
  944. }
  945. key.Cluster = cluster
  946. namespace, err := res.GetString(namespaceLabel)
  947. if err != nil {
  948. return key, err
  949. }
  950. key.Namespace = namespace
  951. pvc, err := res.GetString(pvcLabel)
  952. if err != nil {
  953. return key, err
  954. }
  955. key.PersistentVolumeClaim = pvc
  956. return key, nil
  957. }
  958. type pvKey struct {
  959. Cluster string
  960. PersistentVolume string
  961. }
  962. func (k pvKey) String() string {
  963. return fmt.Sprintf("%s/%s", k.Cluster, k.PersistentVolume)
  964. }
  965. func newPVKey(cluster, persistentVolume string) pvKey {
  966. return pvKey{
  967. Cluster: cluster,
  968. PersistentVolume: persistentVolume,
  969. }
  970. }
  971. func resultPVKey(res *prom.QueryResult, clusterLabel, persistentVolumeLabel string) (pvKey, error) {
  972. key := pvKey{}
  973. cluster, err := res.GetString(clusterLabel)
  974. if err != nil {
  975. cluster = env.GetClusterID()
  976. }
  977. key.Cluster = cluster
  978. persistentVolume, err := res.GetString(persistentVolumeLabel)
  979. if err != nil {
  980. return key, err
  981. }
  982. key.PersistentVolume = persistentVolume
  983. return key, nil
  984. }