allocation.go 59 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788
  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. "k8s.io/apimachinery/pkg/labels"
  11. )
  12. // TODO niko/computeallocation NodeProp issue
  13. // http://kubecost.nikovacevic.io/model/allocation?window=yesterday => Error: NodeProp not set
  14. // TODO niko/computeallocation split into required and optional queries?
  15. // TODO niko/computeallocation move to pkg/kubecost
  16. // TODO niko/computeallocation add PersistenVolumeClaims to type Allocation?
  17. type PVC struct {
  18. Bytes float64 `json:"bytes"`
  19. Count int `json:"count"`
  20. Name string `json:"name"`
  21. Cluster string `json:"cluster"`
  22. Namespace string `json:"namespace"`
  23. Volume *PV `json:"persistentVolume"`
  24. Start time.Time `json:"start"`
  25. End time.Time `json:"end"`
  26. }
  27. func (pvc *PVC) Cost() float64 {
  28. if pvc == nil || pvc.Volume == nil {
  29. return 0.0
  30. }
  31. gib := pvc.Bytes / 1024 / 1024 / 1024
  32. hrs := pvc.Minutes() / 60.0
  33. return pvc.Volume.CostPerGiBHour * gib * hrs
  34. }
  35. func (pvc *PVC) Minutes() float64 {
  36. if pvc == nil {
  37. return 0.0
  38. }
  39. return pvc.End.Sub(pvc.Start).Minutes()
  40. }
  41. func (pvc *PVC) String() string {
  42. if pvc == nil {
  43. return "<nil>"
  44. }
  45. 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))
  46. }
  47. // TODO niko/computeallocation move to pkg/kubecost
  48. type PV struct {
  49. Bytes float64 `json:"bytes"`
  50. CostPerGiBHour float64 `json:"costPerGiBHour"` // TODO niko/computeallocation GiB or GB?
  51. Cluster string `json:"cluster"`
  52. Name string `json:"name"`
  53. StorageClass string `json:"storageClass"`
  54. }
  55. func (pv *PV) String() string {
  56. if pv == nil {
  57. return "<nil>"
  58. }
  59. return fmt.Sprintf("%s/%s{Bytes:%.2f, Cost/GiB*Hr:%.6f, StorageClass:%s}", pv.Cluster, pv.Name, pv.Bytes, pv.CostPerGiBHour, pv.StorageClass)
  60. }
  61. const (
  62. queryFmtMinutes = `avg(kube_pod_container_status_running{}) by (container, pod, namespace, cluster_id)[%s:%s]%s`
  63. queryFmtRAMBytesAllocated = `avg(avg_over_time(container_memory_allocation_bytes{container!="", container!="POD", node!=""}[%s]%s)) by (container, pod, namespace, node, cluster_id)`
  64. queryFmtRAMRequests = `avg(avg_over_time(kube_pod_container_resource_requests_memory_bytes{container!="", container!="POD", node!=""}[%s]%s)) by (container, pod, namespace, node, cluster_id)`
  65. queryFmtRAMUsage = `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)`
  66. queryFmtCPUCoresAllocated = `avg(avg_over_time(container_cpu_allocation{container!="", container!="POD", node!=""}[%s]%s)) by (container, pod, namespace, node, cluster_id)`
  67. queryFmtCPURequests = `avg(avg_over_time(kube_pod_container_resource_requests_cpu_cores{container!="", container!="POD", node!=""}[%s]%s)) by (container, pod, namespace, node, cluster_id)`
  68. queryFmtCPUUsage = `avg(rate(container_cpu_usage_seconds_total{container_name!="", container_name!="POD", instance!=""}[%s]%s)) by (container_name, pod_name, namespace, instance, cluster_id)`
  69. queryFmtGPUsRequested = `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)`
  70. queryFmtNodeCostPerCPUHr = `avg(avg_over_time(node_cpu_hourly_cost[%s]%s)) by (node, cluster_id, instance_type)`
  71. queryFmtNodeCostPerRAMGiBHr = `avg(avg_over_time(node_ram_hourly_cost[%s]%s)) by (node, cluster_id, instance_type)`
  72. queryFmtNodeCostPerGPUHr = `avg(avg_over_time(node_gpu_hourly_cost[%s]%s)) by (node, cluster_id, instance_type)`
  73. queryFmtNodeIsSpot = `avg_over_time(kubecost_node_is_spot[%s]%s)`
  74. queryFmtPVCInfo = `avg(kube_persistentvolumeclaim_info{volumename != ""}) by (persistentvolumeclaim, storageclass, volumename, namespace, cluster_id)[%s:%s]%s`
  75. queryFmtPVBytes = `avg(avg_over_time(kube_persistentvolume_capacity_bytes[%s]%s)) by (persistentvolume, cluster_id)`
  76. queryFmtPodPVCAllocation = `avg(avg_over_time(pod_pvc_allocation[%s]%s)) by (persistentvolume, persistentvolumeclaim, pod, namespace, cluster_id)`
  77. queryFmtPVCBytesRequested = `avg(avg_over_time(kube_persistentvolumeclaim_resource_requests_storage_bytes{}[%s]%s)) by (persistentvolumeclaim, namespace, cluster_id)`
  78. queryFmtPVCostPerGiBHour = `avg(avg_over_time(pv_hourly_cost[%s]%s)) by (volumename, cluster_id)`
  79. queryFmtNetZoneGiB = `sum(increase(kubecost_pod_network_egress_bytes_total{internet="false", sameZone="false", sameRegion="true"}[%s]%s)) by (pod_name, namespace, cluster_id) / 1024 / 1024 / 1024`
  80. queryFmtNetZoneCostPerGiB = `avg(avg_over_time(kubecost_network_zone_egress_cost{}[%s]%s)) by (cluster_id)`
  81. queryFmtNetRegionGiB = `sum(increase(kubecost_pod_network_egress_bytes_total{internet="false", sameZone="false", sameRegion="false"}[%s]%s)) by (pod_name, namespace, cluster_id) / 1024 / 1024 / 1024`
  82. queryFmtNetRegionCostPerGiB = `avg(avg_over_time(kubecost_network_region_egress_cost{}[%s]%s)) by (cluster_id)`
  83. queryFmtNetInternetGiB = `sum(increase(kubecost_pod_network_egress_bytes_total{internet="true"}[%s]%s)) by (pod_name, namespace, cluster_id) / 1024 / 1024 / 1024`
  84. queryFmtNetInternetCostPerGiB = `avg(avg_over_time(kubecost_network_internet_egress_cost{}[%s]%s)) by (cluster_id)`
  85. queryFmtNamespaceLabels = `avg_over_time(kube_namespace_labels[%s]%s)`
  86. queryFmtNamespaceAnnotations = `avg_over_time(kube_namespace_annotations[%s]%s)`
  87. queryFmtPodLabels = `avg_over_time(kube_pod_labels[%s]%s)`
  88. queryFmtPodAnnotations = `avg_over_time(kube_pod_annotations[%s]%s)`
  89. queryFmtServiceLabels = `avg_over_time(service_selector_labels[%s]%s)`
  90. queryFmtDeploymentLabels = `avg_over_time(deployment_match_labels[%s]%s)`
  91. queryFmtStatefulSetLabels = `avg_over_time(statefulSet_match_labels[%s]%s)`
  92. queryFmtDaemonSetLabels = `sum(avg_over_time(kube_pod_owner{owner_kind="DaemonSet"}[%s]%s)) by (pod, owner_name, namespace, cluster_id)`
  93. queryFmtJobLabels = `sum(avg_over_time(kube_pod_owner{owner_kind="Job"}[%s]%s)) by (pod, owner_name, namespace ,cluster_id)`
  94. )
  95. // ComputeAllocation uses the CostModel instance to compute an AllocationSet
  96. // for the window defined by the given start and end times. The Allocations
  97. // returned are unaggregated (i.e. down to the container level).
  98. func (cm *CostModel) ComputeAllocation(start, end time.Time) (*kubecost.AllocationSet, error) {
  99. // Create a window spanning the requested query
  100. s, e := start, end
  101. window := kubecost.NewWindow(&s, &e)
  102. // Create an empty AllocationSet. For safety, in the case of an error, we
  103. // should prefer to return this empty set with the error. (In the case of
  104. // no error, of course we populate the set and return it.)
  105. allocSet := kubecost.NewAllocationSet(start, end)
  106. // Convert window (start, end) to (duration, offset) for querying Prometheus
  107. timesToDurations := func(s, e time.Time) (dur, off time.Duration) {
  108. now := time.Now()
  109. off = now.Sub(e)
  110. dur = e.Sub(s)
  111. return dur, off
  112. }
  113. duration, offset := timesToDurations(start, end)
  114. // If using Thanos, increase offset to 3 hours, reducing the duration by
  115. // equal measure to maintain the same starting point.
  116. thanosDur := thanos.OffsetDuration()
  117. // TODO niko/computeallocation confirm that this flag works interchangeably with ThanosClient != nil
  118. if offset < thanosDur && env.IsThanosEnabled() {
  119. diff := thanosDur - offset
  120. offset += diff
  121. duration -= diff
  122. }
  123. // If duration < 0, return an empty set
  124. if duration < 0 {
  125. return allocSet, nil
  126. }
  127. // Negative offset means that the end time is in the future. Prometheus
  128. // fails for non-positive offset values, so shrink the duration and
  129. // remove the offset altogether.
  130. if offset < 0 {
  131. duration = duration + offset
  132. offset = 0
  133. }
  134. durStr := fmt.Sprintf("%dm", int64(duration.Minutes()))
  135. offStr := fmt.Sprintf(" offset %dm", int64(offset.Minutes()))
  136. if offset < time.Minute {
  137. offStr = ""
  138. }
  139. // TODO niko/computeallocation dynamic resolution? add to ComputeAllocation() in allocation.Source?
  140. resStr := "1m"
  141. // resPerHr := 60
  142. // TODO niko/computeallocation remove after testing
  143. startQuerying := time.Now()
  144. ctx := prom.NewContext(cm.PrometheusClient)
  145. // TODO niko/computeallocation retries? (That should probably go into the Store.)
  146. // TODO niko/cmdr check: will multiple Prometheus jobs multiply the totals?
  147. // TODO niko/computeallocation should we try doing this without resolution? Could yield
  148. // more accurate results, but might also be more challenging in some
  149. // respects; e.g. "correcting" the start point by what amount?
  150. queryMinutes := fmt.Sprintf(queryFmtMinutes, durStr, resStr, offStr)
  151. resChMinutes := ctx.Query(queryMinutes)
  152. queryRAMBytesAllocated := fmt.Sprintf(queryFmtRAMBytesAllocated, durStr, offStr)
  153. resChRAMBytesAllocated := ctx.Query(queryRAMBytesAllocated)
  154. queryRAMRequests := fmt.Sprintf(queryFmtRAMRequests, durStr, offStr)
  155. resChRAMRequests := ctx.Query(queryRAMRequests)
  156. queryRAMUsage := fmt.Sprintf(queryFmtRAMUsage, durStr, offStr)
  157. resChRAMUsage := ctx.Query(queryRAMUsage)
  158. queryCPUCoresAllocated := fmt.Sprintf(queryFmtCPUCoresAllocated, durStr, offStr)
  159. resChCPUCoresAllocated := ctx.Query(queryCPUCoresAllocated)
  160. queryCPURequests := fmt.Sprintf(queryFmtCPURequests, durStr, offStr)
  161. resChCPURequests := ctx.Query(queryCPURequests)
  162. queryCPUUsage := fmt.Sprintf(queryFmtCPUUsage, durStr, offStr)
  163. resChCPUUsage := ctx.Query(queryCPUUsage)
  164. queryGPUsRequested := fmt.Sprintf(queryFmtGPUsRequested, durStr, offStr)
  165. resChGPUsRequested := ctx.Query(queryGPUsRequested)
  166. queryNodeCostPerCPUHr := fmt.Sprintf(queryFmtNodeCostPerCPUHr, durStr, offStr)
  167. resChNodeCostPerCPUHr := ctx.Query(queryNodeCostPerCPUHr)
  168. queryNodeCostPerRAMGiBHr := fmt.Sprintf(queryFmtNodeCostPerRAMGiBHr, durStr, offStr)
  169. resChNodeCostPerRAMGiBHr := ctx.Query(queryNodeCostPerRAMGiBHr)
  170. queryNodeCostPerGPUHr := fmt.Sprintf(queryFmtNodeCostPerGPUHr, durStr, offStr)
  171. resChNodeCostPerGPUHr := ctx.Query(queryNodeCostPerGPUHr)
  172. queryNodeIsSpot := fmt.Sprintf(queryFmtNodeIsSpot, durStr, offStr)
  173. resChNodeIsSpot := ctx.Query(queryNodeIsSpot)
  174. queryPVCInfo := fmt.Sprintf(queryFmtPVCInfo, durStr, resStr, offStr)
  175. resChPVCInfo := ctx.Query(queryPVCInfo)
  176. queryPVBytes := fmt.Sprintf(queryFmtPVBytes, durStr, offStr)
  177. resChPVBytes := ctx.Query(queryPVBytes)
  178. queryPodPVCAllocation := fmt.Sprintf(queryFmtPodPVCAllocation, durStr, offStr)
  179. resChPodPVCAllocation := ctx.Query(queryPodPVCAllocation)
  180. queryPVCBytesRequested := fmt.Sprintf(queryFmtPVCBytesRequested, durStr, offStr)
  181. resChPVCBytesRequested := ctx.Query(queryPVCBytesRequested)
  182. queryPVCostPerGiBHour := fmt.Sprintf(queryFmtPVCostPerGiBHour, durStr, offStr)
  183. resChPVCostPerGiBHour := ctx.Query(queryPVCostPerGiBHour)
  184. queryNetZoneGiB := fmt.Sprintf(queryFmtNetZoneGiB, durStr, offStr)
  185. resChNetZoneGiB := ctx.Query(queryNetZoneGiB)
  186. queryNetZoneCostPerGiB := fmt.Sprintf(queryFmtNetZoneCostPerGiB, durStr, offStr)
  187. resChNetZoneCostPerGiB := ctx.Query(queryNetZoneCostPerGiB)
  188. queryNetRegionGiB := fmt.Sprintf(queryFmtNetRegionGiB, durStr, offStr)
  189. resChNetRegionGiB := ctx.Query(queryNetRegionGiB)
  190. queryNetRegionCostPerGiB := fmt.Sprintf(queryFmtNetRegionCostPerGiB, durStr, offStr)
  191. resChNetRegionCostPerGiB := ctx.Query(queryNetRegionCostPerGiB)
  192. queryNetInternetGiB := fmt.Sprintf(queryFmtNetInternetGiB, durStr, offStr)
  193. resChNetInternetGiB := ctx.Query(queryNetInternetGiB)
  194. queryNetInternetCostPerGiB := fmt.Sprintf(queryFmtNetInternetCostPerGiB, durStr, offStr)
  195. resChNetInternetCostPerGiB := ctx.Query(queryNetInternetCostPerGiB)
  196. queryNamespaceLabels := fmt.Sprintf(queryFmtNamespaceLabels, durStr, offStr)
  197. resChNamespaceLabels := ctx.Query(queryNamespaceLabels)
  198. queryNamespaceAnnotations := fmt.Sprintf(queryFmtNamespaceAnnotations, durStr, offStr)
  199. resChNamespaceAnnotations := ctx.Query(queryNamespaceAnnotations)
  200. queryPodLabels := fmt.Sprintf(queryFmtPodLabels, durStr, offStr)
  201. resChPodLabels := ctx.Query(queryPodLabels)
  202. queryPodAnnotations := fmt.Sprintf(queryFmtPodAnnotations, durStr, offStr)
  203. resChPodAnnotations := ctx.Query(queryPodAnnotations)
  204. queryServiceLabels := fmt.Sprintf(queryFmtServiceLabels, durStr, offStr)
  205. resChServiceLabels := ctx.Query(queryServiceLabels)
  206. queryDeploymentLabels := fmt.Sprintf(queryFmtDeploymentLabels, durStr, offStr)
  207. resChDeploymentLabels := ctx.Query(queryDeploymentLabels)
  208. queryStatefulSetLabels := fmt.Sprintf(queryFmtStatefulSetLabels, durStr, offStr)
  209. resChStatefulSetLabels := ctx.Query(queryStatefulSetLabels)
  210. queryDaemonSetLabels := fmt.Sprintf(queryFmtDaemonSetLabels, durStr, offStr)
  211. resChDaemonSetLabels := ctx.Query(queryDaemonSetLabels)
  212. queryJobLabels := fmt.Sprintf(queryFmtJobLabels, durStr, offStr)
  213. resChJobLabels := ctx.Query(queryJobLabels)
  214. resMinutes, _ := resChMinutes.Await()
  215. resCPUCoresAllocated, _ := resChCPUCoresAllocated.Await()
  216. resCPURequests, _ := resChCPURequests.Await()
  217. resCPUUsage, _ := resChCPUUsage.Await()
  218. resRAMBytesAllocated, _ := resChRAMBytesAllocated.Await()
  219. resRAMRequests, _ := resChRAMRequests.Await()
  220. resRAMUsage, _ := resChRAMUsage.Await()
  221. resGPUsRequested, _ := resChGPUsRequested.Await()
  222. resNodeCostPerCPUHr, _ := resChNodeCostPerCPUHr.Await()
  223. resNodeCostPerRAMGiBHr, _ := resChNodeCostPerRAMGiBHr.Await()
  224. resNodeCostPerGPUHr, _ := resChNodeCostPerGPUHr.Await()
  225. resNodeIsSpot, _ := resChNodeIsSpot.Await()
  226. resPVBytes, _ := resChPVBytes.Await()
  227. resPVCostPerGiBHour, _ := resChPVCostPerGiBHour.Await()
  228. resPVCInfo, _ := resChPVCInfo.Await()
  229. resPVCBytesRequested, _ := resChPVCBytesRequested.Await()
  230. resPodPVCAllocation, _ := resChPodPVCAllocation.Await()
  231. resNetZoneGiB, _ := resChNetZoneGiB.Await()
  232. resNetZoneCostPerGiB, _ := resChNetZoneCostPerGiB.Await()
  233. resNetRegionGiB, _ := resChNetRegionGiB.Await()
  234. resNetRegionCostPerGiB, _ := resChNetRegionCostPerGiB.Await()
  235. resNetInternetGiB, _ := resChNetInternetGiB.Await()
  236. resNetInternetCostPerGiB, _ := resChNetInternetCostPerGiB.Await()
  237. resNamespaceLabels, _ := resChNamespaceLabels.Await()
  238. resNamespaceAnnotations, _ := resChNamespaceAnnotations.Await()
  239. resPodLabels, _ := resChPodLabels.Await()
  240. resPodAnnotations, _ := resChPodAnnotations.Await()
  241. resServiceLabels, _ := resChServiceLabels.Await()
  242. resDeploymentLabels, _ := resChDeploymentLabels.Await()
  243. resStatefulSetLabels, _ := resChStatefulSetLabels.Await()
  244. resDaemonSetLabels, _ := resChDaemonSetLabels.Await()
  245. resJobLabels, _ := resChJobLabels.Await()
  246. // ----------------------------------------------------------------------//
  247. // TODO niko/computeallocation remove all logs after testing
  248. // log.Infof("CostModel.ComputeAllocation: minutes : %s", queryMinutes)
  249. // log.Infof("CostModel.ComputeAllocation: CPU cores: %s", queryCPUCoresAllocated)
  250. // log.Infof("CostModel.ComputeAllocation: CPU req : %s", queryCPURequests)
  251. // log.Infof("CostModel.ComputeAllocation: CPU use : %s", queryCPUUsage)
  252. // log.Infof("CostModel.ComputeAllocation: $/CPU*Hr : %s", queryNodeCostPerCPUHr)
  253. // log.Infof("CostModel.ComputeAllocation: RAM bytes: %s", queryRAMBytesAllocated)
  254. // log.Infof("CostModel.ComputeAllocation: RAM req : %s", queryRAMRequests)
  255. // log.Infof("CostModel.ComputeAllocation: RAM use : %s", queryRAMUsage)
  256. // log.Infof("CostModel.ComputeAllocation: $/GiB*Hr : %s", queryNodeCostPerRAMGiBHr)
  257. // log.Infof("CostModel.ComputeAllocation: PV $/gbhr: %s", queryPVCostPerGiBHour)
  258. // log.Infof("CostModel.ComputeAllocation: PV bytes : %s", queryPVBytes)
  259. // log.Infof("CostModel.ComputeAllocation: PVC alloc: %s", queryPodPVCAllocation)
  260. // log.Infof("CostModel.ComputeAllocation: PVC bytes: %s", queryPVCBytesRequested)
  261. // log.Infof("CostModel.ComputeAllocation: PVC info : %s", queryPVCInfo)
  262. // log.Infof("CostModel.ComputeAllocation: Net Z GiB: %s", queryNetZoneGiB)
  263. // log.Infof("CostModel.ComputeAllocation: Net Z $ : %s", queryNetZoneCostPerGiB)
  264. // log.Infof("CostModel.ComputeAllocation: Net R GiB: %s", queryNetRegionGiB)
  265. // log.Infof("CostModel.ComputeAllocation: Net R $ : %s", queryNetRegionCostPerGiB)
  266. // log.Infof("CostModel.ComputeAllocation: Net I GiB: %s", queryNetInternetGiB)
  267. // log.Infof("CostModel.ComputeAllocation: Net I $ : %s", queryNetInternetCostPerGiB)
  268. // log.Infof("CostModel.ComputeAllocation: NamespaceLabels: %s", queryNamespaceLabels)
  269. // log.Infof("CostModel.ComputeAllocation: NamespaceAnnotations: %s", queryNamespaceAnnotations)
  270. // log.Infof("CostModel.ComputeAllocation: PodLabels: %s", queryPodLabels)
  271. // log.Infof("CostModel.ComputeAllocation: PodAnnotations: %s", queryPodAnnotations)
  272. // log.Infof("CostModel.ComputeAllocation: ServiceLabels: %s", queryServiceLabels)
  273. // log.Infof("CostModel.ComputeAllocation: DeploymentLabels: %s", queryDeploymentLabels)
  274. // log.Infof("CostModel.ComputeAllocation: StatefulSetLabels: %s", queryStatefulSetLabels)
  275. // log.Infof("CostModel.ComputeAllocation: DaemonSetLabels: %s", queryDaemonSetLabels)
  276. // log.Infof("CostModel.ComputeAllocation: JobLabels: %s", queryJobLabels)
  277. log.Profile(startQuerying, "CostModel.ComputeAllocation: queries complete")
  278. defer log.Profile(time.Now(), "CostModel.ComputeAllocation: processing complete")
  279. // ----------------------------------------------------------------------//
  280. // Build out a map of Allocations, starting with (start, end) so that we
  281. // begin with minutes, from which we compute resource allocation and cost
  282. // totals from measured rate data.
  283. // TODO niko/computeallocation can we start with a reasonable guess at map size?
  284. allocationMap := map[containerKey]*kubecost.Allocation{}
  285. // Keep track of the allocations per pod, for the sake of splitting PVC and
  286. // Network allocation into per-Allocation from per-Pod.
  287. podAllocation := map[podKey][]*kubecost.Allocation{}
  288. // clusterStarts and clusterEnds record the earliest start and latest end
  289. // times, respectively, on a cluster-basis. These are used for unmounted
  290. // PVs and other "virtual" Allocations so that minutes are maximally
  291. // accurate during start-up or spin-down of a cluster
  292. clusterStart := map[string]time.Time{}
  293. clusterEnd := map[string]time.Time{}
  294. buildAllocationMap(window, allocationMap, podAllocation, clusterStart, clusterEnd, resMinutes)
  295. applyCPUCoresAllocated(allocationMap, resCPUCoresAllocated)
  296. applyCPUCoresRequested(allocationMap, resCPURequests)
  297. applyCPUCoresUsed(allocationMap, resCPUUsage)
  298. applyRAMBytesAllocated(allocationMap, resRAMBytesAllocated)
  299. applyRAMBytesRequested(allocationMap, resRAMRequests)
  300. applyRAMBytesUsed(allocationMap, resRAMUsage)
  301. applyGPUsRequested(allocationMap, resGPUsRequested)
  302. applyNetworkAllocation(allocationMap, podAllocation, resNetZoneGiB, resNetZoneCostPerGiB)
  303. applyNetworkAllocation(allocationMap, podAllocation, resNetRegionGiB, resNetRegionCostPerGiB)
  304. applyNetworkAllocation(allocationMap, podAllocation, resNetInternetGiB, resNetInternetCostPerGiB)
  305. // TODO niko/computeallocation pruneDuplicateData? (see costmodel.go)
  306. namespaceLabels := resToNamespaceLabels(resNamespaceLabels)
  307. podLabels := resToPodLabels(resPodLabels)
  308. namespaceAnnotations := resToNamespaceAnnotations(resNamespaceAnnotations)
  309. podAnnotations := resToPodAnnotations(resPodAnnotations)
  310. applyLabels(allocationMap, namespaceLabels, podLabels)
  311. applyAnnotations(allocationMap, namespaceAnnotations, podAnnotations)
  312. serviceLabels := getServiceLabels(resServiceLabels)
  313. applyServicesToPods(allocationMap, podLabels, serviceLabels)
  314. podDeploymentMap := labelsToPodControllerMap(podLabels, resToDeploymentLabels(resDeploymentLabels))
  315. podStatefulSetMap := labelsToPodControllerMap(podLabels, resToStatefulSetLabels(resStatefulSetLabels))
  316. podDaemonSetMap := resToPodDaemonSetMap(resDaemonSetLabels)
  317. podJobMap := resToPodJobMap(resJobLabels)
  318. applyControllersToPods(allocationMap, podDeploymentMap)
  319. applyControllersToPods(allocationMap, podStatefulSetMap)
  320. applyControllersToPods(allocationMap, podDaemonSetMap)
  321. applyControllersToPods(allocationMap, podJobMap)
  322. // TODO niko/computeallocation breakdown network costs?
  323. // Build out a map of Nodes with resource costs, discounts, and node types
  324. // for converting resource allocation data to cumulative costs.
  325. nodeMap := map[nodeKey]*Node{}
  326. applyNodeCostPerCPUHr(nodeMap, resNodeCostPerCPUHr)
  327. applyNodeCostPerRAMGiBHr(nodeMap, resNodeCostPerRAMGiBHr)
  328. applyNodeCostPerGPUHr(nodeMap, resNodeCostPerGPUHr)
  329. applyNodeSpot(nodeMap, resNodeIsSpot)
  330. applyNodeDiscount(nodeMap, cm)
  331. // TODO niko/computeallocation comment
  332. pvMap := map[pvKey]*PV{}
  333. buildPVMap(pvMap, resPVCostPerGiBHour)
  334. applyPVBytes(pvMap, resPVBytes)
  335. // TODO niko/computeallocation apply PV bytes?
  336. // TODO niko/computeallocation comment
  337. pvcMap := map[pvcKey]*PVC{}
  338. buildPVCMap(window, pvcMap, pvMap, resPVCInfo)
  339. applyPVCBytesRequested(pvcMap, resPVCBytesRequested)
  340. // TODO niko/computeallocation comment
  341. podPVCMap := map[podKey][]*PVC{}
  342. buildPodPVCMap(podPVCMap, pvMap, pvcMap, podAllocation, resPodPVCAllocation)
  343. // Identify unmounted PVs (PVs without PVCs) and add one Allocation per
  344. // cluster representing each cluster's unmounted PVs (if necessary).
  345. applyUnmountedPVs(window, allocationMap, pvMap, pvcMap)
  346. for _, alloc := range allocationMap {
  347. cluster, _ := alloc.Properties.GetCluster()
  348. node, _ := alloc.Properties.GetNode()
  349. namespace, _ := alloc.Properties.GetNamespace()
  350. pod, _ := alloc.Properties.GetPod()
  351. container, _ := alloc.Properties.GetContainer()
  352. podKey := newPodKey(cluster, namespace, pod)
  353. nodeKey := newNodeKey(cluster, node)
  354. if n, ok := nodeMap[nodeKey]; !ok {
  355. if pod != "unmounted-pvs" {
  356. log.Warningf("CostModel.ComputeAllocation: failed to find node %s for %s", nodeKey, alloc.Name)
  357. }
  358. } else {
  359. alloc.CPUCost = alloc.CPUCoreHours * n.CostPerCPUHr
  360. alloc.RAMCost = (alloc.RAMByteHours / 1024 / 1024 / 1024) * n.CostPerRAMGiBHr
  361. alloc.GPUCost = alloc.GPUHours * n.CostPerGPUHr
  362. }
  363. if pvcs, ok := podPVCMap[podKey]; ok {
  364. for _, pvc := range pvcs {
  365. // Determine the (start, end) of the relationship between the
  366. // given PVC and the associated Allocation so that a precise
  367. // number of hours can be used to compute cumulative cost.
  368. s, e := alloc.Start, alloc.End
  369. if pvc.Start.After(alloc.Start) {
  370. s = pvc.Start
  371. }
  372. if pvc.End.Before(alloc.End) {
  373. e = pvc.End
  374. }
  375. minutes := e.Sub(s).Minutes()
  376. hrs := minutes / 60.0
  377. gib := pvc.Bytes / 1024 / 1024 / 1024
  378. alloc.PVByteHours += pvc.Bytes * hrs
  379. count := float64(pvc.Count)
  380. if pvc.Count < 1 {
  381. // TODO niko/computeallocation why is this happening?
  382. log.Warningf("CostModel.ComputeAllocation: PVC.Count=%d for %s", pvc.Count, alloc.Name)
  383. count = 1
  384. }
  385. alloc.PVCost += pvc.Volume.CostPerGiBHour * gib * hrs / count
  386. }
  387. }
  388. alloc.TotalCost = 0.0
  389. alloc.TotalCost += alloc.CPUCost
  390. alloc.TotalCost += alloc.RAMCost
  391. alloc.TotalCost += alloc.GPUCost
  392. alloc.TotalCost += alloc.PVCost
  393. alloc.TotalCost += alloc.NetworkCost
  394. alloc.TotalCost += alloc.SharedCost
  395. alloc.TotalCost += alloc.ExternalCost
  396. if alloc.RAMBytesRequestAverage > 0 {
  397. alloc.RAMEfficiency = alloc.RAMBytesUsageAverage / alloc.RAMBytesRequestAverage
  398. }
  399. if alloc.CPUCoreRequestAverage > 0 {
  400. alloc.CPUEfficiency = alloc.CPUCoreUsageAverage / alloc.CPUCoreRequestAverage
  401. }
  402. if alloc.CPUCost+alloc.RAMCost > 0 {
  403. ramCostEff := alloc.RAMEfficiency * alloc.RAMCost
  404. cpuCostEff := alloc.CPUEfficiency * alloc.CPUCost
  405. alloc.TotalEfficiency = (ramCostEff + cpuCostEff) / (alloc.CPUCost + alloc.RAMCost)
  406. }
  407. // Make sure that the name is correct (node may not be present at this
  408. // point due to it missing from queryMinutes) then insert.
  409. alloc.Name = fmt.Sprintf("%s/%s/%s/%s/%s", cluster, node, namespace, pod, container)
  410. allocSet.Set(alloc)
  411. }
  412. return allocSet, nil
  413. }
  414. func buildAllocationMap(window kubecost.Window, allocationMap map[containerKey]*kubecost.Allocation, podAllocation map[podKey][]*kubecost.Allocation, clusterStart, clusterEnd map[string]time.Time, resMinutes []*prom.QueryResult) {
  415. for _, res := range resMinutes {
  416. if len(res.Values) == 0 {
  417. log.Warningf("CostModel.ComputeAllocation: empty minutes result")
  418. continue
  419. }
  420. cluster, err := res.GetString("cluster_id")
  421. if err != nil {
  422. cluster = env.GetClusterID()
  423. }
  424. labels, err := res.GetStrings("namespace", "pod", "container")
  425. if err != nil {
  426. log.Warningf("CostModel.ComputeAllocation: minutes query result missing field: %s", err)
  427. continue
  428. }
  429. namespace := labels["namespace"]
  430. pod := labels["pod"]
  431. container := labels["container"]
  432. containerKey := newContainerKey(cluster, namespace, pod, container)
  433. podKey := newPodKey(cluster, namespace, pod)
  434. // allocStart and allocEnd are the timestamps of the first and last
  435. // minutes the allocation was running, respectively. We subtract 1m
  436. // from allocStart because this point will actually represent the end
  437. // of the first minute. We don't subtract from allocEnd because it
  438. // already represents the end of the last minute.
  439. var allocStart, allocEnd time.Time
  440. for _, datum := range res.Values {
  441. t := time.Unix(int64(datum.Timestamp), 0)
  442. if allocStart.IsZero() && datum.Value > 0 && window.Contains(t) {
  443. allocStart = t
  444. }
  445. if datum.Value > 0 && window.Contains(t) {
  446. allocEnd = t
  447. }
  448. }
  449. if allocStart.IsZero() || allocEnd.IsZero() {
  450. continue
  451. }
  452. allocStart = allocStart.Add(-time.Minute)
  453. // Set start if unset or this datum's start time is earlier than the
  454. // current earliest time.
  455. if _, ok := clusterStart[cluster]; !ok || allocStart.Before(clusterStart[cluster]) {
  456. clusterStart[cluster] = allocStart
  457. }
  458. // Set end if unset or this datum's end time is later than the
  459. // current latest time.
  460. if _, ok := clusterEnd[cluster]; !ok || allocEnd.After(clusterEnd[cluster]) {
  461. clusterEnd[cluster] = allocEnd
  462. }
  463. name := fmt.Sprintf("%s/%s/%s/%s", cluster, namespace, pod, container)
  464. alloc := &kubecost.Allocation{
  465. Name: name,
  466. Properties: kubecost.Properties{},
  467. Window: window.Clone(),
  468. Start: allocStart,
  469. End: allocEnd,
  470. }
  471. alloc.Properties.SetContainer(container)
  472. alloc.Properties.SetPod(pod)
  473. alloc.Properties.SetNamespace(namespace)
  474. alloc.Properties.SetCluster(cluster)
  475. allocationMap[containerKey] = alloc
  476. if _, ok := podAllocation[podKey]; !ok {
  477. podAllocation[podKey] = []*kubecost.Allocation{}
  478. }
  479. podAllocation[podKey] = append(podAllocation[podKey], alloc)
  480. }
  481. }
  482. func applyCPUCoresAllocated(allocationMap map[containerKey]*kubecost.Allocation, resCPUCoresAllocated []*prom.QueryResult) {
  483. for _, res := range resCPUCoresAllocated {
  484. key, err := resultContainerKey(res, "cluster_id", "namespace", "pod", "container")
  485. if err != nil {
  486. log.Warningf("CostModel.ComputeAllocation: CPU allocation query result missing field: %s", err)
  487. continue
  488. }
  489. _, ok := allocationMap[key]
  490. if !ok {
  491. log.Warningf("CostModel.ComputeAllocation: unidentified CPU allocation query result: %s", key)
  492. continue
  493. }
  494. cpuCores := res.Values[0].Value
  495. hours := allocationMap[key].Minutes() / 60.0
  496. allocationMap[key].CPUCoreHours = cpuCores * hours
  497. node, err := res.GetString("node")
  498. if err != nil {
  499. log.Warningf("CostModel.ComputeAllocation: CPU allocation query result missing 'node': %s", key)
  500. continue
  501. }
  502. allocationMap[key].Properties.SetNode(node)
  503. }
  504. }
  505. func applyCPUCoresRequested(allocationMap map[containerKey]*kubecost.Allocation, resCPUCoresRequested []*prom.QueryResult) {
  506. for _, res := range resCPUCoresRequested {
  507. key, err := resultContainerKey(res, "cluster_id", "namespace", "pod", "container")
  508. if err != nil {
  509. log.Warningf("CostModel.ComputeAllocation: CPU request query result missing field: %s", err)
  510. continue
  511. }
  512. _, ok := allocationMap[key]
  513. if !ok {
  514. continue
  515. }
  516. allocationMap[key].CPUCoreRequestAverage = res.Values[0].Value
  517. // CPU allocation is less than requests, so set CPUCoreHours to
  518. // request level.
  519. // TODO niko/computeallocation why is this happening?
  520. if allocationMap[key].CPUCores() < res.Values[0].Value {
  521. allocationMap[key].CPUCoreHours = res.Values[0].Value * (allocationMap[key].Minutes() / 60.0)
  522. }
  523. node, err := res.GetString("node")
  524. if err != nil {
  525. log.Warningf("CostModel.ComputeAllocation: CPU request query result missing 'node': %s", key)
  526. continue
  527. }
  528. allocationMap[key].Properties.SetNode(node)
  529. }
  530. }
  531. func applyCPUCoresUsed(allocationMap map[containerKey]*kubecost.Allocation, resCPUCoresUsed []*prom.QueryResult) {
  532. for _, res := range resCPUCoresUsed {
  533. key, err := resultContainerKey(res, "cluster_id", "namespace", "pod_name", "container_name")
  534. if err != nil {
  535. log.Warningf("CostModel.ComputeAllocation: CPU usage query result missing field: %s", err)
  536. continue
  537. }
  538. _, ok := allocationMap[key]
  539. if !ok {
  540. log.Warningf("CostModel.ComputeAllocation: unidentified CPU usage query result: %s", key)
  541. continue
  542. }
  543. allocationMap[key].CPUCoreUsageAverage = res.Values[0].Value
  544. }
  545. }
  546. func applyRAMBytesAllocated(allocationMap map[containerKey]*kubecost.Allocation, resRAMBytesAllocated []*prom.QueryResult) {
  547. for _, res := range resRAMBytesAllocated {
  548. key, err := resultContainerKey(res, "cluster_id", "namespace", "pod", "container")
  549. if err != nil {
  550. log.Warningf("CostModel.ComputeAllocation: RAM allocation query result missing field: %s", err)
  551. continue
  552. }
  553. _, ok := allocationMap[key]
  554. if !ok {
  555. log.Warningf("CostModel.ComputeAllocation: unidentified RAM allocation query result: %s", key)
  556. continue
  557. }
  558. ramBytes := res.Values[0].Value
  559. hours := allocationMap[key].Minutes() / 60.0
  560. allocationMap[key].RAMByteHours = ramBytes * hours
  561. node, err := res.GetString("node")
  562. if err != nil {
  563. log.Warningf("CostModel.ComputeAllocation: RAM allocation query result missing 'node': %s", key)
  564. continue
  565. }
  566. allocationMap[key].Properties.SetNode(node)
  567. }
  568. }
  569. func applyRAMBytesRequested(allocationMap map[containerKey]*kubecost.Allocation, resRAMBytesRequested []*prom.QueryResult) {
  570. for _, res := range resRAMBytesRequested {
  571. key, err := resultContainerKey(res, "cluster_id", "namespace", "pod", "container")
  572. if err != nil {
  573. log.Warningf("CostModel.ComputeAllocation: RAM request query result missing field: %s", err)
  574. continue
  575. }
  576. _, ok := allocationMap[key]
  577. if !ok {
  578. continue
  579. }
  580. allocationMap[key].RAMBytesRequestAverage = res.Values[0].Value
  581. // RAM allocation is less than requests, so set RAMByteHours to
  582. // request level.
  583. // TODO niko/computeallocation why is this happening?
  584. if allocationMap[key].RAMBytes() < res.Values[0].Value {
  585. allocationMap[key].RAMByteHours = res.Values[0].Value * (allocationMap[key].Minutes() / 60.0)
  586. }
  587. node, err := res.GetString("node")
  588. if err != nil {
  589. log.Warningf("CostModel.ComputeAllocation: RAM request query result missing 'node': %s", key)
  590. continue
  591. }
  592. allocationMap[key].Properties.SetNode(node)
  593. }
  594. }
  595. func applyRAMBytesUsed(allocationMap map[containerKey]*kubecost.Allocation, resRAMBytesUsed []*prom.QueryResult) {
  596. for _, res := range resRAMBytesUsed {
  597. key, err := resultContainerKey(res, "cluster_id", "namespace", "pod_name", "container_name")
  598. if err != nil {
  599. log.Warningf("CostModel.ComputeAllocation: RAM usage query result missing field: %s", err)
  600. continue
  601. }
  602. _, ok := allocationMap[key]
  603. if !ok {
  604. log.Warningf("CostModel.ComputeAllocation: unidentified RAM usage query result: %s", key)
  605. continue
  606. }
  607. allocationMap[key].RAMBytesUsageAverage = res.Values[0].Value
  608. }
  609. }
  610. func applyGPUsRequested(allocationMap map[containerKey]*kubecost.Allocation, resGPUsRequested []*prom.QueryResult) {
  611. for _, res := range resGPUsRequested {
  612. key, err := resultContainerKey(res, "cluster_id", "namespace", "pod", "container")
  613. if err != nil {
  614. log.Warningf("CostModel.ComputeAllocation: GPU allocation query result missing field: %s", err)
  615. continue
  616. }
  617. _, ok := allocationMap[key]
  618. if !ok {
  619. log.Warningf("CostModel.ComputeAllocation: unidentified GPU allocation query result: %s", key)
  620. continue
  621. }
  622. // TODO niko/computeallocation remove log
  623. log.Infof("CostModel.ComputeAllocation: GPU results: %s=%f", key, res.Values[0].Value)
  624. hrs := allocationMap[key].Minutes() / 60.0
  625. allocationMap[key].GPUHours = res.Values[0].Value * hrs
  626. }
  627. }
  628. func applyNetworkAllocation(allocationMap map[containerKey]*kubecost.Allocation, podAllocation map[podKey][]*kubecost.Allocation, resNetworkGiB []*prom.QueryResult, resNetworkCostPerGiB []*prom.QueryResult) {
  629. costPerGiBByCluster := map[string]float64{}
  630. for _, res := range resNetworkCostPerGiB {
  631. cluster, err := res.GetString("cluster_id")
  632. if err != nil {
  633. cluster = env.GetClusterID()
  634. }
  635. costPerGiBByCluster[cluster] = res.Values[0].Value
  636. }
  637. for _, res := range resNetworkGiB {
  638. podKey, err := resultPodKey(res, "cluster_id", "namespace", "pod_name")
  639. if err != nil {
  640. log.Warningf("CostModel.ComputeAllocation: Network allocation query result missing field: %s", err)
  641. continue
  642. }
  643. allocs, ok := podAllocation[podKey]
  644. if !ok {
  645. log.Warningf("CostModel.ComputeAllocation: Network allocation query result for unidentified pod allocations: %s", podKey)
  646. continue
  647. }
  648. for _, alloc := range allocs {
  649. gib := res.Values[0].Value
  650. costPerGiB := costPerGiBByCluster[podKey.Cluster]
  651. alloc.NetworkCost = gib * costPerGiB
  652. }
  653. }
  654. }
  655. func resToNamespaceLabels(resNamespaceLabels []*prom.QueryResult) map[string]map[string]string {
  656. namespaceLabels := map[string]map[string]string{}
  657. for _, res := range resNamespaceLabels {
  658. namespace, err := res.GetString("namespace")
  659. if err != nil {
  660. continue
  661. }
  662. if _, ok := namespaceLabels[namespace]; !ok {
  663. namespaceLabels[namespace] = map[string]string{}
  664. }
  665. for k, l := range res.GetLabels() {
  666. namespaceLabels[namespace][k] = l
  667. }
  668. }
  669. return namespaceLabels
  670. }
  671. func resToPodLabels(resPodLabels []*prom.QueryResult) map[podKey]map[string]string {
  672. podLabels := map[podKey]map[string]string{}
  673. for _, res := range resPodLabels {
  674. podKey, err := resultPodKey(res, "cluster_id", "namespace", "pod")
  675. if err != nil {
  676. continue
  677. }
  678. if _, ok := podLabels[podKey]; !ok {
  679. podLabels[podKey] = map[string]string{}
  680. }
  681. for k, l := range res.GetLabels() {
  682. podLabels[podKey][k] = l
  683. }
  684. }
  685. return podLabels
  686. }
  687. func resToNamespaceAnnotations(resNamespaceAnnotations []*prom.QueryResult) map[string]map[string]string {
  688. namespaceAnnotations := map[string]map[string]string{}
  689. for _, res := range resNamespaceAnnotations {
  690. namespace, err := res.GetString("namespace")
  691. if err != nil {
  692. continue
  693. }
  694. if _, ok := namespaceAnnotations[namespace]; !ok {
  695. namespaceAnnotations[namespace] = map[string]string{}
  696. }
  697. for k, l := range res.GetAnnotations() {
  698. namespaceAnnotations[namespace][k] = l
  699. }
  700. }
  701. return namespaceAnnotations
  702. }
  703. func resToPodAnnotations(resPodAnnotations []*prom.QueryResult) map[podKey]map[string]string {
  704. podAnnotations := map[podKey]map[string]string{}
  705. for _, res := range resPodAnnotations {
  706. podKey, err := resultPodKey(res, "cluster_id", "namespace", "pod")
  707. if err != nil {
  708. continue
  709. }
  710. if _, ok := podAnnotations[podKey]; !ok {
  711. podAnnotations[podKey] = map[string]string{}
  712. }
  713. for k, l := range res.GetAnnotations() {
  714. podAnnotations[podKey][k] = l
  715. }
  716. }
  717. return podAnnotations
  718. }
  719. func applyLabels(allocationMap map[containerKey]*kubecost.Allocation, namespaceLabels map[string]map[string]string, podLabels map[podKey]map[string]string) {
  720. for key, alloc := range allocationMap {
  721. allocLabels, err := alloc.Properties.GetLabels()
  722. if err != nil {
  723. allocLabels = map[string]string{}
  724. }
  725. // Apply namespace labels first, then pod labels so that pod labels
  726. // overwrite namespace labels.
  727. if labels, ok := namespaceLabels[key.Namespace]; ok {
  728. for k, v := range labels {
  729. allocLabels[k] = v
  730. }
  731. }
  732. podKey := newPodKey(key.Cluster, key.Namespace, key.Pod)
  733. if labels, ok := podLabels[podKey]; ok {
  734. for k, v := range labels {
  735. allocLabels[k] = v
  736. }
  737. }
  738. alloc.Properties.SetLabels(allocLabels)
  739. }
  740. }
  741. func applyAnnotations(allocationMap map[containerKey]*kubecost.Allocation, namespaceAnnotations map[string]map[string]string, podAnnotations map[podKey]map[string]string) {
  742. for key, alloc := range allocationMap {
  743. allocAnnotations, err := alloc.Properties.GetAnnotations()
  744. if err != nil {
  745. allocAnnotations = map[string]string{}
  746. }
  747. // Apply namespace annotations first, then pod annotations so that
  748. // pod labels overwrite namespace labels.
  749. if labels, ok := namespaceAnnotations[key.Namespace]; ok {
  750. for k, v := range labels {
  751. allocAnnotations[k] = v
  752. }
  753. }
  754. podKey := newPodKey(key.Cluster, key.Namespace, key.Pod)
  755. if labels, ok := podAnnotations[podKey]; ok {
  756. for k, v := range labels {
  757. allocAnnotations[k] = v
  758. }
  759. }
  760. alloc.Properties.SetAnnotations(allocAnnotations)
  761. }
  762. }
  763. func getServiceLabels(resServiceLabels []*prom.QueryResult) map[serviceKey]map[string]string {
  764. serviceLabels := map[serviceKey]map[string]string{}
  765. for _, res := range resServiceLabels {
  766. serviceKey, err := resultServiceKey(res, "cluster_id", "namespace", "service")
  767. if err != nil {
  768. continue
  769. }
  770. if _, ok := serviceLabels[serviceKey]; !ok {
  771. serviceLabels[serviceKey] = map[string]string{}
  772. }
  773. for k, l := range res.GetLabels() {
  774. serviceLabels[serviceKey][k] = l
  775. }
  776. }
  777. return serviceLabels
  778. }
  779. func resToDeploymentLabels(resDeploymentLabels []*prom.QueryResult) map[controllerKey]map[string]string {
  780. deploymentLabels := map[controllerKey]map[string]string{}
  781. for _, res := range resDeploymentLabels {
  782. controllerKey, err := resultDeploymentKey(res, "cluster_id", "namespace", "deployment")
  783. if err != nil {
  784. continue
  785. }
  786. if _, ok := deploymentLabels[controllerKey]; !ok {
  787. deploymentLabels[controllerKey] = map[string]string{}
  788. }
  789. for k, l := range res.GetLabels() {
  790. deploymentLabels[controllerKey][k] = l
  791. }
  792. }
  793. return deploymentLabels
  794. }
  795. func resToStatefulSetLabels(resStatefulSetLabels []*prom.QueryResult) map[controllerKey]map[string]string {
  796. statefulSetLabels := map[controllerKey]map[string]string{}
  797. for _, res := range resStatefulSetLabels {
  798. controllerKey, err := resultStatefulSetKey(res, "cluster_id", "namespace", "statefulSet")
  799. if err != nil {
  800. continue
  801. }
  802. if _, ok := statefulSetLabels[controllerKey]; !ok {
  803. statefulSetLabels[controllerKey] = map[string]string{}
  804. }
  805. for k, l := range res.GetLabels() {
  806. statefulSetLabels[controllerKey][k] = l
  807. }
  808. }
  809. return statefulSetLabels
  810. }
  811. func labelsToPodControllerMap(podLabels map[podKey]map[string]string, controllerLabels map[controllerKey]map[string]string) map[podKey]controllerKey {
  812. podControllerMap := map[podKey]controllerKey{}
  813. // For each controller, turn the labels into a selector and attempt to
  814. // match it with each set of pod labels. A match indicates that the pod
  815. // belongs to the controller.
  816. for cKey, cLabels := range controllerLabels {
  817. selector := labels.Set(cLabels).AsSelectorPreValidated()
  818. for pKey, pLabels := range podLabels {
  819. // If the pod is in a different cluster or namespace, there is
  820. // no need to compare the labels.
  821. if cKey.Cluster != pKey.Cluster || cKey.Namespace != pKey.Namespace {
  822. continue
  823. }
  824. podLabelSet := labels.Set(pLabels)
  825. if selector.Matches(podLabelSet) {
  826. // TODO niko/computeallocation does this need to be one-to-many? In that case, we'd
  827. // need a different Allocation schema
  828. if _, ok := podControllerMap[pKey]; ok {
  829. log.Warningf("CostModel.ComputeAllocation: PodControllerMap match already exists: %s matches %s and %s", pKey, podControllerMap[pKey], cKey)
  830. }
  831. podControllerMap[pKey] = cKey
  832. }
  833. }
  834. }
  835. return podControllerMap
  836. }
  837. func resToPodDaemonSetMap(resDaemonSetLabels []*prom.QueryResult) map[podKey]controllerKey {
  838. daemonSetLabels := map[podKey]controllerKey{}
  839. for _, res := range resDaemonSetLabels {
  840. controllerKey, err := resultDaemonSetKey(res, "cluster_id", "namespace", "owner_name")
  841. if err != nil {
  842. continue
  843. }
  844. pod, err := res.GetString("pod")
  845. if err != nil {
  846. log.Warningf("CostModel.ComputeAllocation: DaemonSetLabel result without pod: %s", controllerKey)
  847. }
  848. podKey := newPodKey(controllerKey.Cluster, controllerKey.Namespace, pod)
  849. daemonSetLabels[podKey] = controllerKey
  850. }
  851. return daemonSetLabels
  852. }
  853. func resToPodJobMap(resJobLabels []*prom.QueryResult) map[podKey]controllerKey {
  854. jobLabels := map[podKey]controllerKey{}
  855. for _, res := range resJobLabels {
  856. controllerKey, err := resultJobKey(res, "cluster_id", "namespace", "owner_name")
  857. if err != nil {
  858. continue
  859. }
  860. pod, err := res.GetString("pod")
  861. if err != nil {
  862. log.Warningf("CostModel.ComputeAllocation: JobLabel result without pod: %s", controllerKey)
  863. }
  864. podKey := newPodKey(controllerKey.Cluster, controllerKey.Namespace, pod)
  865. jobLabels[podKey] = controllerKey
  866. }
  867. return jobLabels
  868. }
  869. func applyServicesToPods(allocationMap map[containerKey]*kubecost.Allocation, podLabels map[podKey]map[string]string, serviceLabels map[serviceKey]map[string]string) {
  870. podServicesMap := map[podKey][]serviceKey{}
  871. // For each service, turn the labels into a selector and attempt to
  872. // match it with each set of pod labels. A match indicates that the pod
  873. // belongs to the service.
  874. for sKey, sLabels := range serviceLabels {
  875. selector := labels.Set(sLabels).AsSelectorPreValidated()
  876. for pKey, pLabels := range podLabels {
  877. // If the pod is in a different cluster or namespace, there is
  878. // no need to compare the labels.
  879. if sKey.Cluster != pKey.Cluster || sKey.Namespace != pKey.Namespace {
  880. continue
  881. }
  882. podLabelSet := labels.Set(pLabels)
  883. if selector.Matches(podLabelSet) {
  884. if _, ok := podServicesMap[pKey]; !ok {
  885. podServicesMap[pKey] = []serviceKey{}
  886. }
  887. podServicesMap[pKey] = append(podServicesMap[pKey], sKey)
  888. }
  889. }
  890. }
  891. // For each allocation, attempt to find and apply the list of services
  892. // associated with the allocation's pod.
  893. for key, alloc := range allocationMap {
  894. pKey := newPodKey(key.Cluster, key.Namespace, key.Pod)
  895. if sKeys, ok := podServicesMap[pKey]; ok {
  896. services := []string{}
  897. for _, sKey := range sKeys {
  898. services = append(services, sKey.Service)
  899. }
  900. alloc.Properties.SetServices(services)
  901. }
  902. }
  903. }
  904. func applyControllersToPods(allocationMap map[containerKey]*kubecost.Allocation, podControllerMap map[podKey]controllerKey) {
  905. for key, alloc := range allocationMap {
  906. podKey := newPodKey(key.Cluster, key.Namespace, key.Pod)
  907. if controllerKey, ok := podControllerMap[podKey]; ok {
  908. alloc.Properties.SetControllerKind(controllerKey.ControllerKind)
  909. alloc.Properties.SetController(controllerKey.Controller)
  910. }
  911. }
  912. }
  913. func applyNodeCostPerCPUHr(nodeMap map[nodeKey]*Node, resNodeCostPerCPUHr []*prom.QueryResult) {
  914. for _, res := range resNodeCostPerCPUHr {
  915. cluster, err := res.GetString("cluster_id")
  916. if err != nil {
  917. cluster = env.GetClusterID()
  918. }
  919. node, err := res.GetString("node")
  920. if err != nil {
  921. log.Warningf("CostModel.ComputeAllocation: Node CPU cost query result missing field: %s", err)
  922. continue
  923. }
  924. instanceType, err := res.GetString("instance_type")
  925. if err != nil {
  926. log.Warningf("CostModel.ComputeAllocation: Node CPU cost query result missing field: %s", err)
  927. continue
  928. }
  929. key := newNodeKey(cluster, node)
  930. if _, ok := nodeMap[key]; !ok {
  931. nodeMap[key] = &Node{
  932. Name: node,
  933. NodeType: instanceType,
  934. }
  935. }
  936. nodeMap[key].CostPerCPUHr = res.Values[0].Value
  937. }
  938. }
  939. func applyNodeCostPerRAMGiBHr(nodeMap map[nodeKey]*Node, resNodeCostPerRAMGiBHr []*prom.QueryResult) {
  940. for _, res := range resNodeCostPerRAMGiBHr {
  941. cluster, err := res.GetString("cluster_id")
  942. if err != nil {
  943. cluster = env.GetClusterID()
  944. }
  945. node, err := res.GetString("node")
  946. if err != nil {
  947. log.Warningf("CostModel.ComputeAllocation: Node RAM cost query result missing field: %s", err)
  948. continue
  949. }
  950. instanceType, err := res.GetString("instance_type")
  951. if err != nil {
  952. log.Warningf("CostModel.ComputeAllocation: Node RAM cost query result missing field: %s", err)
  953. continue
  954. }
  955. key := newNodeKey(cluster, node)
  956. if _, ok := nodeMap[key]; !ok {
  957. nodeMap[key] = &Node{
  958. Name: node,
  959. NodeType: instanceType,
  960. }
  961. }
  962. nodeMap[key].CostPerRAMGiBHr = res.Values[0].Value
  963. }
  964. }
  965. func applyNodeCostPerGPUHr(nodeMap map[nodeKey]*Node, resNodeCostPerGPUHr []*prom.QueryResult) {
  966. for _, res := range resNodeCostPerGPUHr {
  967. cluster, err := res.GetString("cluster_id")
  968. if err != nil {
  969. cluster = env.GetClusterID()
  970. }
  971. node, err := res.GetString("node")
  972. if err != nil {
  973. log.Warningf("CostModel.ComputeAllocation: Node GPU cost query result missing field: %s", err)
  974. continue
  975. }
  976. instanceType, err := res.GetString("instance_type")
  977. if err != nil {
  978. log.Warningf("CostModel.ComputeAllocation: Node GPU cost query result missing field: %s", err)
  979. continue
  980. }
  981. key := newNodeKey(cluster, node)
  982. if _, ok := nodeMap[key]; !ok {
  983. nodeMap[key] = &Node{
  984. Name: node,
  985. NodeType: instanceType,
  986. }
  987. }
  988. nodeMap[key].CostPerGPUHr = res.Values[0].Value
  989. }
  990. }
  991. func applyNodeSpot(nodeMap map[nodeKey]*Node, resNodeIsSpot []*prom.QueryResult) {
  992. for _, res := range resNodeIsSpot {
  993. cluster, err := res.GetString("cluster_id")
  994. if err != nil {
  995. cluster = env.GetClusterID()
  996. }
  997. node, err := res.GetString("node")
  998. if err != nil {
  999. log.Warningf("CostModel.ComputeAllocation: Node spot query result missing field: %s", err)
  1000. continue
  1001. }
  1002. key := newNodeKey(cluster, node)
  1003. if _, ok := nodeMap[key]; !ok {
  1004. log.Warningf("CostModel.ComputeAllocation: Node spot query result for missing node: %s", key)
  1005. continue
  1006. }
  1007. nodeMap[key].Preemptible = res.Values[0].Value > 0
  1008. }
  1009. }
  1010. func applyNodeDiscount(nodeMap map[nodeKey]*Node, cm *CostModel) {
  1011. if cm == nil {
  1012. return
  1013. }
  1014. c, err := cm.Provider.GetConfig()
  1015. if err != nil {
  1016. log.Errorf("CostModel.ComputeAllocation: applyNodeDiscount: %s", err)
  1017. return
  1018. }
  1019. discount, err := ParsePercentString(c.Discount)
  1020. if err != nil {
  1021. log.Errorf("CostModel.ComputeAllocation: applyNodeDiscount: %s", err)
  1022. return
  1023. }
  1024. negotiatedDiscount, err := ParsePercentString(c.NegotiatedDiscount)
  1025. if err != nil {
  1026. log.Errorf("CostModel.ComputeAllocation: applyNodeDiscount: %s", err)
  1027. return
  1028. }
  1029. for _, node := range nodeMap {
  1030. // TODO niko/computeallocation take RI into account?
  1031. node.Discount = cm.Provider.CombinedDiscountForNode(node.NodeType, node.Preemptible, discount, negotiatedDiscount)
  1032. node.CostPerCPUHr *= (1.0 - node.Discount)
  1033. node.CostPerRAMGiBHr *= (1.0 - node.Discount)
  1034. }
  1035. }
  1036. func buildPVMap(pvMap map[pvKey]*PV, resPVCostPerGiBHour []*prom.QueryResult) {
  1037. for _, res := range resPVCostPerGiBHour {
  1038. cluster, err := res.GetString("cluster_id")
  1039. if err != nil {
  1040. cluster = env.GetClusterID()
  1041. }
  1042. name, err := res.GetString("volumename")
  1043. if err != nil {
  1044. log.Warningf("CostModel.ComputeAllocation: PV cost without volumename")
  1045. continue
  1046. }
  1047. key := newPVKey(cluster, name)
  1048. pvMap[key] = &PV{
  1049. Cluster: cluster,
  1050. Name: name,
  1051. CostPerGiBHour: res.Values[0].Value,
  1052. }
  1053. }
  1054. }
  1055. func applyPVBytes(pvMap map[pvKey]*PV, resPVBytes []*prom.QueryResult) {
  1056. for _, res := range resPVBytes {
  1057. key, err := resultPVKey(res, "cluster_id", "persistentvolume")
  1058. if err != nil {
  1059. log.Warningf("CostModel.ComputeAllocation: PV bytes query result missing field: %s", err)
  1060. continue
  1061. }
  1062. if _, ok := pvMap[key]; !ok {
  1063. log.Warningf("CostModel.ComputeAllocation: PV bytes result for missing PV: %s", err)
  1064. continue
  1065. }
  1066. pvMap[key].Bytes = res.Values[0].Value
  1067. }
  1068. }
  1069. func buildPVCMap(window kubecost.Window, pvcMap map[pvcKey]*PVC, pvMap map[pvKey]*PV, resPVCInfo []*prom.QueryResult) {
  1070. for _, res := range resPVCInfo {
  1071. cluster, err := res.GetString("cluster_id")
  1072. if err != nil {
  1073. cluster = env.GetClusterID()
  1074. }
  1075. values, err := res.GetStrings("persistentvolumeclaim", "storageclass", "volumename", "namespace")
  1076. if err != nil {
  1077. log.Warningf("CostModel.ComputeAllocation: PVC info query result missing field: %s", err)
  1078. continue
  1079. }
  1080. namespace := values["namespace"]
  1081. name := values["persistentvolumeclaim"]
  1082. volume := values["volumename"]
  1083. storageClass := values["storageclass"]
  1084. pvKey := newPVKey(cluster, volume)
  1085. pvcKey := newPVCKey(cluster, namespace, name)
  1086. // pvcStart and pvcEnd are the timestamps of the first and last minutes
  1087. // the PVC was running, respectively. We subtract 1m from pvcStart
  1088. // because this point will actually represent the end of the first
  1089. // minute. We don't subtract from pvcEnd because it already represents
  1090. // the end of the last minute.
  1091. var pvcStart, pvcEnd time.Time
  1092. for _, datum := range res.Values {
  1093. t := time.Unix(int64(datum.Timestamp), 0)
  1094. if pvcStart.IsZero() && datum.Value > 0 && window.Contains(t) {
  1095. pvcStart = t
  1096. }
  1097. if datum.Value > 0 && window.Contains(t) {
  1098. pvcEnd = t
  1099. }
  1100. }
  1101. if pvcStart.IsZero() || pvcEnd.IsZero() {
  1102. log.Warningf("CostModel.ComputeAllocation: PVC %s has no running time", pvcKey)
  1103. }
  1104. pvcStart = pvcStart.Add(-time.Minute)
  1105. if _, ok := pvMap[pvKey]; !ok {
  1106. log.Warningf("CostModel.ComputeAllocation: PV missing for PVC info query result: %s", pvKey)
  1107. continue
  1108. }
  1109. pvMap[pvKey].StorageClass = storageClass
  1110. if _, ok := pvcMap[pvcKey]; !ok {
  1111. pvcMap[pvcKey] = &PVC{}
  1112. }
  1113. pvcMap[pvcKey].Name = name
  1114. pvcMap[pvcKey].Namespace = namespace
  1115. pvcMap[pvcKey].Volume = pvMap[pvKey]
  1116. pvcMap[pvcKey].Start = pvcStart
  1117. pvcMap[pvcKey].End = pvcEnd
  1118. }
  1119. }
  1120. func applyPVCBytesRequested(pvcMap map[pvcKey]*PVC, resPVCBytesRequested []*prom.QueryResult) {
  1121. for _, res := range resPVCBytesRequested {
  1122. key, err := resultPVCKey(res, "cluster_id", "namespace", "persistentvolumeclaim")
  1123. if err != nil {
  1124. log.Warningf("CostModel.ComputeAllocation: PVC bytes requested query result missing field: %s", err)
  1125. continue
  1126. }
  1127. if _, ok := pvcMap[key]; !ok {
  1128. log.Warningf("CostModel.ComputeAllocation: PVC bytes requested result for missing PVC: %s", key)
  1129. continue
  1130. }
  1131. pvcMap[key].Bytes = res.Values[0].Value
  1132. }
  1133. }
  1134. func buildPodPVCMap(podPVCMap map[podKey][]*PVC, pvMap map[pvKey]*PV, pvcMap map[pvcKey]*PVC, podAllocation map[podKey][]*kubecost.Allocation, resPodPVCAllocation []*prom.QueryResult) {
  1135. for _, res := range resPodPVCAllocation {
  1136. cluster, err := res.GetString("cluster_id")
  1137. if err != nil {
  1138. cluster = env.GetClusterID()
  1139. }
  1140. values, err := res.GetStrings("persistentvolume", "persistentvolumeclaim", "pod", "namespace")
  1141. if err != nil {
  1142. log.Warningf("CostModel.ComputeAllocation: PVC allocation query result missing field: %s", err)
  1143. continue
  1144. }
  1145. namespace := values["namespace"]
  1146. pod := values["pod"]
  1147. name := values["persistentvolumeclaim"]
  1148. volume := values["persistentvolume"]
  1149. podKey := newPodKey(cluster, namespace, pod)
  1150. pvKey := newPVKey(cluster, volume)
  1151. pvcKey := newPVCKey(cluster, namespace, name)
  1152. if _, ok := pvMap[pvKey]; !ok {
  1153. log.Warningf("CostModel.ComputeAllocation: PV missing for PVC allocation query result: %s", pvKey)
  1154. continue
  1155. }
  1156. if _, ok := podPVCMap[podKey]; !ok {
  1157. podPVCMap[podKey] = []*PVC{}
  1158. }
  1159. pvc, ok := pvcMap[pvcKey]
  1160. if !ok {
  1161. log.Warningf("CostModel.ComputeAllocation: PVC missing for PVC allocation query: %s", pvcKey)
  1162. continue
  1163. }
  1164. // TODO niko/computeallocation is this working?
  1165. pvc.Count = len(podAllocation[podKey])
  1166. podPVCMap[podKey] = append(podPVCMap[podKey], pvc)
  1167. }
  1168. }
  1169. func applyUnmountedPVs(window kubecost.Window, allocationMap map[containerKey]*kubecost.Allocation, pvMap map[pvKey]*PV, pvcMap map[pvcKey]*PVC) {
  1170. unmountedPVBytes := map[string]float64{}
  1171. unmountedPVCost := map[string]float64{}
  1172. for _, pv := range pvMap {
  1173. mounted := false
  1174. for _, pvc := range pvcMap {
  1175. if pvc.Volume == nil {
  1176. continue
  1177. }
  1178. if pvc.Volume == pv {
  1179. mounted = true
  1180. break
  1181. }
  1182. }
  1183. if !mounted {
  1184. gib := pv.Bytes / 1024 / 1024 / 1024
  1185. hrs := window.Minutes() / 60.0
  1186. cost := pv.CostPerGiBHour * gib * hrs
  1187. unmountedPVCost[pv.Cluster] += cost
  1188. unmountedPVBytes[pv.Cluster] += pv.Bytes
  1189. }
  1190. }
  1191. for cluster, amount := range unmountedPVCost {
  1192. container := "unmounted-pvs"
  1193. pod := "unmounted-pvs"
  1194. namespace := "" // TODO niko/computeallocation what about this?
  1195. node := "" // TODO niko/computeallocation what about this?
  1196. containerKey := newContainerKey(cluster, namespace, pod, container)
  1197. allocationMap[containerKey] = &kubecost.Allocation{
  1198. Name: fmt.Sprintf("%s/%s/%s/%s", cluster, namespace, pod, container),
  1199. Properties: kubecost.Properties{
  1200. kubecost.ClusterProp: cluster,
  1201. kubecost.NodeProp: node,
  1202. kubecost.NamespaceProp: namespace,
  1203. kubecost.PodProp: pod,
  1204. kubecost.ContainerProp: container,
  1205. },
  1206. Window: window.Clone(),
  1207. Start: *window.Start(),
  1208. End: *window.End(),
  1209. PVByteHours: unmountedPVBytes[cluster] * window.Minutes() / 60.0,
  1210. PVCost: amount,
  1211. TotalCost: amount,
  1212. }
  1213. }
  1214. }
  1215. type containerKey struct {
  1216. Cluster string
  1217. Namespace string
  1218. Pod string
  1219. Container string
  1220. }
  1221. func (k containerKey) String() string {
  1222. return fmt.Sprintf("%s/%s/%s/%s", k.Cluster, k.Namespace, k.Pod, k.Container)
  1223. }
  1224. func newContainerKey(cluster, namespace, pod, container string) containerKey {
  1225. return containerKey{
  1226. Cluster: cluster,
  1227. Namespace: namespace,
  1228. Pod: pod,
  1229. Container: container,
  1230. }
  1231. }
  1232. func resultContainerKey(res *prom.QueryResult, clusterLabel, namespaceLabel, podLabel, containerLabel string) (containerKey, error) {
  1233. key := containerKey{}
  1234. cluster, err := res.GetString(clusterLabel)
  1235. if err != nil {
  1236. cluster = env.GetClusterID()
  1237. }
  1238. key.Cluster = cluster
  1239. namespace, err := res.GetString(namespaceLabel)
  1240. if err != nil {
  1241. return key, err
  1242. }
  1243. key.Namespace = namespace
  1244. pod, err := res.GetString(podLabel)
  1245. if err != nil {
  1246. return key, err
  1247. }
  1248. key.Pod = pod
  1249. container, err := res.GetString(containerLabel)
  1250. if err != nil {
  1251. return key, err
  1252. }
  1253. key.Container = container
  1254. return key, nil
  1255. }
  1256. type podKey struct {
  1257. Cluster string
  1258. Namespace string
  1259. Pod string
  1260. }
  1261. func (k podKey) String() string {
  1262. return fmt.Sprintf("%s/%s/%s", k.Cluster, k.Namespace, k.Pod)
  1263. }
  1264. func newPodKey(cluster, namespace, pod string) podKey {
  1265. return podKey{
  1266. Cluster: cluster,
  1267. Namespace: namespace,
  1268. Pod: pod,
  1269. }
  1270. }
  1271. func resultPodKey(res *prom.QueryResult, clusterLabel, namespaceLabel, podLabel string) (podKey, error) {
  1272. key := podKey{}
  1273. cluster, err := res.GetString(clusterLabel)
  1274. if err != nil {
  1275. cluster = env.GetClusterID()
  1276. }
  1277. key.Cluster = cluster
  1278. namespace, err := res.GetString(namespaceLabel)
  1279. if err != nil {
  1280. return key, err
  1281. }
  1282. key.Namespace = namespace
  1283. pod, err := res.GetString(podLabel)
  1284. if err != nil {
  1285. return key, err
  1286. }
  1287. key.Pod = pod
  1288. return key, nil
  1289. }
  1290. type controllerKey struct {
  1291. Cluster string
  1292. Namespace string
  1293. ControllerKind string
  1294. Controller string
  1295. }
  1296. func (k controllerKey) String() string {
  1297. return fmt.Sprintf("%s/%s/%s/%s", k.Cluster, k.Namespace, k.ControllerKind, k.Controller)
  1298. }
  1299. func newControllerKey(cluster, namespace, controllerKind, controller string) controllerKey {
  1300. return controllerKey{
  1301. Cluster: cluster,
  1302. Namespace: namespace,
  1303. ControllerKind: controllerKind,
  1304. Controller: controller,
  1305. }
  1306. }
  1307. func resultControllerKey(controllerKind string, res *prom.QueryResult, clusterLabel, namespaceLabel, controllerLabel string) (controllerKey, error) {
  1308. key := controllerKey{}
  1309. cluster, err := res.GetString(clusterLabel)
  1310. if err != nil {
  1311. cluster = env.GetClusterID()
  1312. }
  1313. key.Cluster = cluster
  1314. namespace, err := res.GetString(namespaceLabel)
  1315. if err != nil {
  1316. return key, err
  1317. }
  1318. key.Namespace = namespace
  1319. controller, err := res.GetString(controllerLabel)
  1320. if err != nil {
  1321. return key, err
  1322. }
  1323. key.Controller = controller
  1324. key.ControllerKind = controllerKind
  1325. return key, nil
  1326. }
  1327. func resultDeploymentKey(res *prom.QueryResult, clusterLabel, namespaceLabel, controllerLabel string) (controllerKey, error) {
  1328. return resultControllerKey("deployment", res, clusterLabel, namespaceLabel, controllerLabel)
  1329. }
  1330. func resultStatefulSetKey(res *prom.QueryResult, clusterLabel, namespaceLabel, controllerLabel string) (controllerKey, error) {
  1331. return resultControllerKey("statefulset", res, clusterLabel, namespaceLabel, controllerLabel)
  1332. }
  1333. func resultDaemonSetKey(res *prom.QueryResult, clusterLabel, namespaceLabel, controllerLabel string) (controllerKey, error) {
  1334. return resultControllerKey("daemonset", res, clusterLabel, namespaceLabel, controllerLabel)
  1335. }
  1336. func resultJobKey(res *prom.QueryResult, clusterLabel, namespaceLabel, controllerLabel string) (controllerKey, error) {
  1337. return resultControllerKey("job", res, clusterLabel, namespaceLabel, controllerLabel)
  1338. }
  1339. type serviceKey struct {
  1340. Cluster string
  1341. Namespace string
  1342. Service string
  1343. }
  1344. func (k serviceKey) String() string {
  1345. return fmt.Sprintf("%s/%s/%s", k.Cluster, k.Namespace, k.Service)
  1346. }
  1347. func newServiceKey(cluster, namespace, service string) serviceKey {
  1348. return serviceKey{
  1349. Cluster: cluster,
  1350. Namespace: namespace,
  1351. Service: service,
  1352. }
  1353. }
  1354. func resultServiceKey(res *prom.QueryResult, clusterLabel, namespaceLabel, serviceLabel string) (serviceKey, error) {
  1355. key := serviceKey{}
  1356. cluster, err := res.GetString(clusterLabel)
  1357. if err != nil {
  1358. cluster = env.GetClusterID()
  1359. }
  1360. key.Cluster = cluster
  1361. namespace, err := res.GetString(namespaceLabel)
  1362. if err != nil {
  1363. return key, err
  1364. }
  1365. key.Namespace = namespace
  1366. service, err := res.GetString(serviceLabel)
  1367. if err != nil {
  1368. return key, err
  1369. }
  1370. key.Service = service
  1371. return key, nil
  1372. }
  1373. type nodeKey struct {
  1374. Cluster string
  1375. Node string
  1376. }
  1377. func (k nodeKey) String() string {
  1378. return fmt.Sprintf("%s/%s", k.Cluster, k.Node)
  1379. }
  1380. func newNodeKey(cluster, node string) nodeKey {
  1381. return nodeKey{
  1382. Cluster: cluster,
  1383. Node: node,
  1384. }
  1385. }
  1386. func resultNodeKey(res *prom.QueryResult, clusterLabel, nodeLabel string) (nodeKey, error) {
  1387. key := nodeKey{}
  1388. cluster, err := res.GetString(clusterLabel)
  1389. if err != nil {
  1390. cluster = env.GetClusterID()
  1391. }
  1392. key.Cluster = cluster
  1393. node, err := res.GetString(nodeLabel)
  1394. if err != nil {
  1395. return key, err
  1396. }
  1397. key.Node = node
  1398. return key, nil
  1399. }
  1400. type pvcKey struct {
  1401. Cluster string
  1402. Namespace string
  1403. PersistentVolumeClaim string
  1404. }
  1405. func (k pvcKey) String() string {
  1406. return fmt.Sprintf("%s/%s/%s", k.Cluster, k.Namespace, k.PersistentVolumeClaim)
  1407. }
  1408. func newPVCKey(cluster, namespace, persistentVolumeClaim string) pvcKey {
  1409. return pvcKey{
  1410. Cluster: cluster,
  1411. Namespace: namespace,
  1412. PersistentVolumeClaim: persistentVolumeClaim,
  1413. }
  1414. }
  1415. func resultPVCKey(res *prom.QueryResult, clusterLabel, namespaceLabel, pvcLabel string) (pvcKey, error) {
  1416. key := pvcKey{}
  1417. cluster, err := res.GetString(clusterLabel)
  1418. if err != nil {
  1419. cluster = env.GetClusterID()
  1420. }
  1421. key.Cluster = cluster
  1422. namespace, err := res.GetString(namespaceLabel)
  1423. if err != nil {
  1424. return key, err
  1425. }
  1426. key.Namespace = namespace
  1427. pvc, err := res.GetString(pvcLabel)
  1428. if err != nil {
  1429. return key, err
  1430. }
  1431. key.PersistentVolumeClaim = pvc
  1432. return key, nil
  1433. }
  1434. type pvKey struct {
  1435. Cluster string
  1436. PersistentVolume string
  1437. }
  1438. func (k pvKey) String() string {
  1439. return fmt.Sprintf("%s/%s", k.Cluster, k.PersistentVolume)
  1440. }
  1441. func newPVKey(cluster, persistentVolume string) pvKey {
  1442. return pvKey{
  1443. Cluster: cluster,
  1444. PersistentVolume: persistentVolume,
  1445. }
  1446. }
  1447. func resultPVKey(res *prom.QueryResult, clusterLabel, persistentVolumeLabel string) (pvKey, error) {
  1448. key := pvKey{}
  1449. cluster, err := res.GetString(clusterLabel)
  1450. if err != nil {
  1451. cluster = env.GetClusterID()
  1452. }
  1453. key.Cluster = cluster
  1454. persistentVolume, err := res.GetString(persistentVolumeLabel)
  1455. if err != nil {
  1456. return key, err
  1457. }
  1458. key.PersistentVolume = persistentVolume
  1459. return key, nil
  1460. }