costmodel.go 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  1. package costmodel
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "log"
  7. "math"
  8. "net/http"
  9. "strconv"
  10. "time"
  11. costAnalyzerCloud "github.com/kubecost/cost-model/cloud"
  12. prometheusClient "github.com/prometheus/client_golang/api"
  13. "k8s.io/api/core/v1"
  14. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  15. "k8s.io/apimachinery/pkg/labels"
  16. "k8s.io/client-go/kubernetes"
  17. )
  18. const (
  19. statusAPIError = 422
  20. apiPrefix = "/api/v1"
  21. epAlertManagers = apiPrefix + "/alertmanagers"
  22. epQuery = apiPrefix + "/query"
  23. epQueryRange = apiPrefix + "/query_range"
  24. epLabelValues = apiPrefix + "/label/:name/values"
  25. epSeries = apiPrefix + "/series"
  26. epTargets = apiPrefix + "/targets"
  27. epSnapshot = apiPrefix + "/admin/tsdb/snapshot"
  28. epDeleteSeries = apiPrefix + "/admin/tsdb/delete_series"
  29. epCleanTombstones = apiPrefix + "/admin/tsdb/clean_tombstones"
  30. epConfig = apiPrefix + "/status/config"
  31. epFlags = apiPrefix + "/status/flags"
  32. )
  33. type CostData struct {
  34. Name string `json:"name"`
  35. PodName string `json:"podName"`
  36. NodeName string `json:"nodeName"`
  37. NodeData *costAnalyzerCloud.Node `json:"node"`
  38. Namespace string `json:"namespace"`
  39. Deployments []string `json:"deployments"`
  40. Services []string `json:"services"`
  41. Daemonsets []string `json:"daemonsets"`
  42. Statefulsets []string `json:"statefulsets"`
  43. Jobs []string `json:"jobs"`
  44. RAMReq []*Vector `json:"ramreq"`
  45. RAMUsed []*Vector `json:"ramused"`
  46. CPUReq []*Vector `json:"cpureq"`
  47. CPUUsed []*Vector `json:"cpuused"`
  48. RAMAllocation []*Vector `json:"ramallocated"`
  49. CPUAllocation []*Vector `json:"cpuallocated"`
  50. GPUReq []*Vector `json:"gpureq"`
  51. PVData []*PersistentVolumeData `json:"pvData"`
  52. Labels map[string]string `json:"labels"`
  53. }
  54. type Vector struct {
  55. Timestamp float64 `json:"timestamp"`
  56. Value float64 `json:"value"`
  57. }
  58. func ComputeCostData(cli prometheusClient.Client, clientset *kubernetes.Clientset, cloud costAnalyzerCloud.Provider, window string) (map[string]*CostData, error) {
  59. queryRAMRequests := `avg(label_replace(label_replace(avg((count_over_time(kube_pod_container_resource_requests_memory_bytes{container!="",container!="POD"}[` + window + `]) * avg_over_time(kube_pod_container_resource_requests_memory_bytes{container!="",container!="POD"}[` + window + `]))) by (namespace,container,pod) , "container_name","$1","container","(.+)"), "pod_name","$1","pod","(.+)") ) by (namespace,container_name, pod_name)`
  60. queryRAMUsage := `sort_desc(avg(count_over_time(container_memory_usage_bytes{container_name!="",container_name!="POD"}[` + window + `]) * avg_over_time(container_memory_usage_bytes{container_name!="",container_name!="POD"}[` + window + `])) by (namespace,container_name,pod_name,instance))`
  61. queryCPURequests := `avg(label_replace(label_replace(avg((count_over_time(kube_pod_container_resource_requests_cpu_cores{container!="",container!="POD"}[` + window + `]) * avg_over_time(kube_pod_container_resource_requests_cpu_cores{container!="",container!="POD"}[` + window + `]))) by (namespace,container,pod) , "container_name","$1","container","(.+)"), "pod_name","$1","pod","(.+)") ) by (namespace,container_name, pod_name)`
  62. queryCPUUsage := `avg(rate(container_cpu_usage_seconds_total{container_name!="",container_name!="POD"}[` + window + `])) by (namespace,container_name,pod_name,instance)`
  63. queryGPURequests := `avg(label_replace(label_replace(avg((count_over_time(kube_pod_container_resource_requests{resource="nvidia_com_gpu", container!="",container!="POD"}[` + window + `]) * avg_over_time(kube_pod_container_resource_requests{resource="nvidia_com_gpu", container!="",container!="POD"}[` + window + `]))) by (namespace,container,pod) , "container_name","$1","container","(.+)"), "pod_name","$1","pod","(.+)") ) by (namespace,container_name, pod_name)`
  64. queryPVRequests := `(sum(kube_persistentvolumeclaim_info) by (persistentvolumeclaim, storageclass) + on (persistentvolumeclaim) group_right(storageclass) sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace))`
  65. normalization := `max(count_over_time(kube_pod_container_resource_requests_memory_bytes{}[` + window + `]))`
  66. resultRAMRequests, _ := query(cli, queryRAMRequests)
  67. resultRAMUsage, _ := query(cli, queryRAMUsage)
  68. resultCPURequests, _ := query(cli, queryCPURequests)
  69. resultCPUUsage, _ := query(cli, queryCPUUsage)
  70. resultGPURequests, _ := query(cli, queryGPURequests)
  71. resultPVRequests, _ := query(cli, queryPVRequests)
  72. normalizationResult, _ := query(cli, normalization)
  73. normalizationValue := getNormalization(normalizationResult)
  74. nodes, err := getNodeCost(clientset, cloud)
  75. if err != nil {
  76. log.Printf("Warning, no cost model available: " + err.Error())
  77. }
  78. podlist, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{})
  79. if err != nil {
  80. return nil, err
  81. }
  82. podDeploymentsMapping, err := getPodDeployments(clientset, podlist)
  83. if err != nil {
  84. return nil, err
  85. }
  86. podServicesMapping, err := getPodServices(clientset, podlist)
  87. if err != nil {
  88. return nil, err
  89. }
  90. pvClaimMapping := getPVInfoVector(resultPVRequests)
  91. if err != nil {
  92. return nil, err
  93. }
  94. containerNameCost := make(map[string]*CostData)
  95. for _, pod := range podlist.Items {
  96. podName := pod.GetObjectMeta().GetName()
  97. ns := pod.GetObjectMeta().GetNamespace()
  98. labels := pod.GetObjectMeta().GetLabels()
  99. nodeName := pod.Spec.NodeName
  100. var nodeData *costAnalyzerCloud.Node
  101. if _, ok := nodes[nodeName]; ok {
  102. nodeData = nodes[nodeName]
  103. }
  104. var podDeployments []string
  105. if _, ok := podDeploymentsMapping[ns]; ok {
  106. if ds, ok := podDeploymentsMapping[ns][pod.GetObjectMeta().GetName()]; ok {
  107. podDeployments = ds
  108. } else {
  109. podDeployments = []string{}
  110. }
  111. }
  112. var podPVs []*PersistentVolumeData
  113. podClaims := pod.Spec.Volumes
  114. for _, vol := range podClaims {
  115. if vol.PersistentVolumeClaim != nil {
  116. name := vol.PersistentVolumeClaim.ClaimName
  117. if pvClaim, ok := pvClaimMapping[ns+","+name]; ok {
  118. podPVs = append(podPVs, pvClaim)
  119. }
  120. }
  121. }
  122. var podServices []string
  123. if _, ok := podServicesMapping[ns]; ok {
  124. if svcs, ok := podServicesMapping[ns][pod.GetObjectMeta().GetName()]; ok {
  125. podServices = svcs
  126. } else {
  127. podServices = []string{}
  128. }
  129. }
  130. for i, container := range pod.Spec.Containers {
  131. containerName := container.Name
  132. RAMReqV := findContainerMetric(resultRAMRequests, containerName, podName, ns)
  133. RAMReqV.Value = RAMReqV.Value / normalizationValue
  134. RAMUsedV := findContainerMetric(resultRAMUsage, containerName, podName, ns)
  135. RAMUsedV.Value = RAMUsedV.Value / normalizationValue
  136. CPUReqV := findContainerMetric(resultCPURequests, containerName, podName, ns)
  137. CPUReqV.Value = CPUReqV.Value / normalizationValue
  138. GPUReqV := findContainerMetric(resultGPURequests, containerName, podName, ns)
  139. GPUReqV.Value = GPUReqV.Value / normalizationValue
  140. var pvReq []*PersistentVolumeData
  141. if i == 0 { // avoid duplicating by just assigning all claims to the first container.
  142. pvReq = podPVs
  143. }
  144. costs := &CostData{
  145. Name: containerName,
  146. PodName: podName,
  147. NodeName: nodeName,
  148. Namespace: ns,
  149. Deployments: podDeployments,
  150. Services: podServices,
  151. Daemonsets: getDaemonsetsOfPod(pod),
  152. Jobs: getJobsOfPod(pod),
  153. Statefulsets: getStatefulSetsOfPod(pod),
  154. NodeData: nodeData,
  155. RAMReq: []*Vector{RAMReqV},
  156. RAMUsed: []*Vector{RAMUsedV},
  157. CPUReq: []*Vector{CPUReqV},
  158. CPUUsed: []*Vector{findContainerMetric(resultCPUUsage, containerName, podName, ns)},
  159. GPUReq: []*Vector{GPUReqV},
  160. PVData: pvReq,
  161. Labels: labels,
  162. }
  163. costs.CPUAllocation = getContainerAllocation(costs.CPUReq, costs.CPUUsed)
  164. costs.RAMAllocation = getContainerAllocation(costs.RAMReq, costs.RAMUsed)
  165. containerNameCost[ns+","+podName+","+containerName] = costs
  166. }
  167. }
  168. return containerNameCost, err
  169. }
  170. func getContainerAllocation(req []*Vector, used []*Vector) []*Vector {
  171. if req == nil || len(req) == 0 {
  172. return used
  173. }
  174. if used == nil || len(used) == 0 {
  175. return req
  176. }
  177. var allocation []*Vector
  178. for i, reqV := range req {
  179. usedV := used[i]
  180. allocation = append(allocation, &Vector{
  181. Timestamp: usedV.Timestamp,
  182. Value: math.Max(usedV.Value, reqV.Value),
  183. })
  184. }
  185. return allocation
  186. }
  187. func getNodeCost(clientset *kubernetes.Clientset, cloud costAnalyzerCloud.Provider) (map[string]*costAnalyzerCloud.Node, error) {
  188. nodeList, err := clientset.CoreV1().Nodes().List(metav1.ListOptions{})
  189. if err != nil {
  190. return nil, err
  191. }
  192. nodes := make(map[string]*costAnalyzerCloud.Node)
  193. for _, n := range nodeList.Items {
  194. name := n.GetObjectMeta().GetName()
  195. labels := n.GetObjectMeta().GetLabels()
  196. cnode, err := cloud.NodePricing(cloud.GetKey(labels))
  197. if err != nil {
  198. log.Printf("Error getting node. Error: " + err.Error())
  199. }
  200. var cpu float64
  201. if cnode.VCPU == "" {
  202. cpu = float64(n.Status.Capacity.Cpu().Value())
  203. cnode.VCPU = n.Status.Capacity.Cpu().String()
  204. } else {
  205. cpu, _ = strconv.ParseFloat(cnode.VCPU, 64)
  206. }
  207. var ram float64
  208. log.Printf("CNODE RAM : %s", cnode.RAM)
  209. if cnode.RAM == "" {
  210. log.Printf("RAMSTRING: %s", n.Status.Capacity.Memory().String())
  211. cnode.RAM = n.Status.Capacity.Memory().String()
  212. ram = float64(n.Status.Capacity.Memory().Value())
  213. } else {
  214. ram, _ = strconv.ParseFloat(cnode.RAM, 64)
  215. }
  216. log.Printf("RAM USAGE: %f", ram)
  217. if cnode.RAMCost == "" { // We couldn't find a ramcost, so fix cpu and allocate ram accordingly
  218. basePrice, _ := strconv.ParseFloat(cnode.BaseCPUPrice, 64)
  219. log.Printf("BASEPRICE: %f", basePrice)
  220. totalCPUPrice := basePrice * cpu
  221. log.Printf("TOTALCPUPRICE: %f", basePrice)
  222. var nodePrice float64
  223. if cnode.Cost != "" {
  224. log.Printf("Use given nodeprice as whole node price")
  225. nodePrice, _ = strconv.ParseFloat(cnode.Cost, 64)
  226. } else {
  227. log.Printf("Use cpuprice as whole node price")
  228. nodePrice, _ = strconv.ParseFloat(cnode.VCPUCost, 64) // all the price was allocated the the CPU
  229. }
  230. log.Printf("NODEPRICE: %f", basePrice)
  231. ramPrice := (nodePrice - totalCPUPrice) / (ram / 1024 / 1024 / 1024)
  232. if ramPrice < 0 {
  233. ramPrice = 0
  234. }
  235. cnode.RAMCost = fmt.Sprintf("%f", ramPrice)
  236. log.Printf(cnode.RAMCost)
  237. }
  238. nodes[name] = cnode
  239. }
  240. return nodes, nil
  241. }
  242. func getPodServices(clientset *kubernetes.Clientset, podList *v1.PodList) (map[string]map[string][]string, error) {
  243. servicesList, err := clientset.Core().Services("").List(metav1.ListOptions{})
  244. if err != nil {
  245. return nil, err
  246. }
  247. podServicesMapping := make(map[string]map[string][]string)
  248. for _, service := range servicesList.Items {
  249. namespace := service.GetObjectMeta().GetNamespace()
  250. name := service.GetObjectMeta().GetName()
  251. if _, ok := podServicesMapping[namespace]; !ok {
  252. podServicesMapping[namespace] = make(map[string][]string)
  253. }
  254. s := labels.Set(service.Spec.Selector).AsSelectorPreValidated()
  255. if err != nil {
  256. log.Printf("Error doing service label conversion: " + err.Error())
  257. }
  258. for _, pod := range podList.Items {
  259. labelSet := labels.Set(pod.GetObjectMeta().GetLabels())
  260. if s.Matches(labelSet) && pod.GetObjectMeta().GetNamespace() == namespace {
  261. services, ok := podServicesMapping[namespace][pod.GetObjectMeta().GetName()]
  262. if ok {
  263. podServicesMapping[namespace][pod.GetObjectMeta().GetName()] = append(services, name)
  264. } else {
  265. podServicesMapping[namespace][pod.GetObjectMeta().GetName()] = []string{name}
  266. }
  267. }
  268. }
  269. }
  270. return podServicesMapping, nil
  271. }
  272. func getPodDeployments(clientset *kubernetes.Clientset, podList *v1.PodList) (map[string]map[string][]string, error) {
  273. deploymentsList, err := clientset.AppsV1().Deployments("").List(metav1.ListOptions{})
  274. if err != nil {
  275. return nil, err
  276. }
  277. podDeploymentsMapping := make(map[string]map[string][]string) // namespace: podName: [deploymentNames]
  278. for _, deployment := range deploymentsList.Items {
  279. namespace := deployment.GetObjectMeta().GetNamespace()
  280. name := deployment.GetObjectMeta().GetName()
  281. if _, ok := podDeploymentsMapping[namespace]; !ok {
  282. podDeploymentsMapping[namespace] = make(map[string][]string)
  283. }
  284. s, err := metav1.LabelSelectorAsSelector(deployment.Spec.Selector)
  285. if err != nil {
  286. log.Printf("Error doing deployment label conversion: " + err.Error())
  287. }
  288. for _, pod := range podList.Items {
  289. labelSet := labels.Set(pod.GetObjectMeta().GetLabels())
  290. if s.Matches(labelSet) && pod.GetObjectMeta().GetNamespace() == namespace {
  291. deployments, ok := podDeploymentsMapping[namespace][pod.GetObjectMeta().GetName()]
  292. if ok {
  293. podDeploymentsMapping[namespace][pod.GetObjectMeta().GetName()] = append(deployments, name)
  294. } else {
  295. podDeploymentsMapping[namespace][pod.GetObjectMeta().GetName()] = []string{name}
  296. }
  297. }
  298. }
  299. }
  300. return podDeploymentsMapping, nil
  301. }
  302. func ComputeCostDataRange(cli prometheusClient.Client, clientset *kubernetes.Clientset, cloud costAnalyzerCloud.Provider,
  303. startString, endString, windowString string) (map[string]*CostData, error) {
  304. queryRAMRequests := `avg(label_replace(label_replace(avg((count_over_time(kube_pod_container_resource_requests_memory_bytes{container!="",container!="POD"}[` + windowString + `]) * avg_over_time(kube_pod_container_resource_requests_memory_bytes{container!="",container!="POD"}[` + windowString + `]))) by (namespace,container,pod) , "container_name","$1","container","(.+)"), "pod_name","$1","pod","(.+)") ) by (namespace,container_name, pod_name)`
  305. queryRAMUsage := `sort_desc(avg(count_over_time(container_memory_usage_bytes{container_name!="",container_name!="POD"}[` + windowString + `]) * avg_over_time(container_memory_usage_bytes{container_name!="",container_name!="POD"}[` + windowString + `])) by (namespace,container_name,pod_name,instance))`
  306. queryCPURequests := `avg(label_replace(label_replace(avg((count_over_time(kube_pod_container_resource_requests_cpu_cores{container!="",container!="POD"}[` + windowString + `]) * avg_over_time(kube_pod_container_resource_requests_cpu_cores{container!="",container!="POD"}[` + windowString + `]))) by (namespace,container,pod) , "container_name","$1","container","(.+)"), "pod_name","$1","pod","(.+)") ) by (namespace,container_name, pod_name)`
  307. queryCPUUsage := `avg(rate(container_cpu_usage_seconds_total{container_name!="",container_name!="POD"}[` + windowString + `])) by (namespace,container_name,pod_name,instance)`
  308. queryGPURequests := `avg(label_replace(label_replace(avg((count_over_time(kube_pod_container_resource_requests{resource="nvidia_com_gpu", container!="",container!="POD"}[` + windowString + `]) * avg_over_time(kube_pod_container_resource_requests{resource="nvidia_com_gpu", container!="",container!="POD"}[` + windowString + `]))) by (namespace,container,pod) , "container_name","$1","container","(.+)"), "pod_name","$1","pod","(.+)") ) by (namespace,container_name, pod_name)`
  309. queryPVRequests := `(sum(kube_persistentvolumeclaim_info) by (persistentvolumeclaim, storageclass) + on (persistentvolumeclaim) group_right(storageclass) sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace))`
  310. normalization := `max(count_over_time(kube_pod_container_resource_requests_memory_bytes{}[` + windowString + `]))`
  311. layout := "2006-01-02T15:04:05.000Z"
  312. start, err := time.Parse(layout, startString)
  313. if err != nil {
  314. log.Printf("Error parsing time " + startString + ". Error: " + err.Error())
  315. return nil, err
  316. }
  317. end, err := time.Parse(layout, endString)
  318. if err != nil {
  319. log.Printf("Error parsing time " + endString + ". Error: " + err.Error())
  320. return nil, err
  321. }
  322. window, err := time.ParseDuration(windowString)
  323. if err != nil {
  324. log.Printf("Error parsing time " + windowString + ". Error: " + err.Error())
  325. return nil, err
  326. }
  327. resultRAMRequests, _ := queryRange(cli, queryRAMRequests, start, end, window)
  328. resultRAMUsage, _ := queryRange(cli, queryRAMUsage, start, end, window)
  329. resultCPURequests, _ := queryRange(cli, queryCPURequests, start, end, window)
  330. resultCPUUsage, _ := queryRange(cli, queryCPUUsage, start, end, window)
  331. resultGPURequests, _ := queryRange(cli, queryGPURequests, start, end, window)
  332. resultPVRequests, _ := queryRange(cli, queryPVRequests, start, end, window)
  333. normalizationResult, _ := query(cli, normalization)
  334. normalizationValue := getNormalization(normalizationResult)
  335. nodes, err := getNodeCost(clientset, cloud)
  336. if err != nil {
  337. //return nil, err
  338. log.Printf("Warning, no cost model available: " + err.Error())
  339. }
  340. podlist, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{})
  341. if err != nil {
  342. return nil, err
  343. }
  344. podDeploymentsMapping, err := getPodDeployments(clientset, podlist)
  345. if err != nil {
  346. return nil, err
  347. }
  348. podServicesMapping, err := getPodServices(clientset, podlist)
  349. if err != nil {
  350. return nil, err
  351. }
  352. pvClaimMapping := getPVInfoVectors(resultPVRequests)
  353. if err != nil {
  354. return nil, err
  355. }
  356. containerNameCost := make(map[string]*CostData)
  357. for _, pod := range podlist.Items {
  358. podName := pod.GetObjectMeta().GetName()
  359. ns := pod.GetObjectMeta().GetNamespace()
  360. labels := pod.GetObjectMeta().GetLabels()
  361. nodeName := pod.Spec.NodeName
  362. var nodeData *costAnalyzerCloud.Node
  363. if _, ok := nodes[nodeName]; ok {
  364. nodeData = nodes[nodeName]
  365. }
  366. var podDeployments []string
  367. if _, ok := podDeploymentsMapping[ns]; ok {
  368. if ds, ok := podDeploymentsMapping[ns][pod.GetObjectMeta().GetName()]; ok {
  369. podDeployments = ds
  370. } else {
  371. podDeployments = []string{}
  372. }
  373. }
  374. var podServices []string
  375. if _, ok := podServicesMapping[ns]; ok {
  376. if svcs, ok := podServicesMapping[ns][pod.GetObjectMeta().GetName()]; ok {
  377. podServices = svcs
  378. } else {
  379. podServices = []string{}
  380. }
  381. }
  382. var podPVs []*PersistentVolumeData
  383. podClaims := pod.Spec.Volumes
  384. for _, vol := range podClaims {
  385. if vol.PersistentVolumeClaim != nil {
  386. name := vol.PersistentVolumeClaim.ClaimName
  387. if pvClaim, ok := pvClaimMapping[ns+","+name]; ok {
  388. podPVs = append(podPVs, pvClaim)
  389. }
  390. }
  391. }
  392. for i, container := range pod.Spec.Containers {
  393. containerName := container.Name
  394. RAMReqV := findContainerMetricVectors(resultRAMRequests, containerName, podName, ns)
  395. for _, v := range RAMReqV {
  396. v.Value = v.Value / normalizationValue
  397. }
  398. RAMUsedV := findContainerMetricVectors(resultRAMUsage, containerName, podName, ns)
  399. for _, v := range RAMUsedV {
  400. v.Value = v.Value / normalizationValue
  401. }
  402. CPUReqV := findContainerMetricVectors(resultCPURequests, containerName, podName, ns)
  403. for _, v := range CPUReqV {
  404. v.Value = v.Value / normalizationValue
  405. }
  406. GPUReqV := findContainerMetricVectors(resultGPURequests, containerName, podName, ns)
  407. for _, v := range GPUReqV {
  408. v.Value = v.Value / normalizationValue
  409. }
  410. var pvReq []*PersistentVolumeData
  411. if i == 0 { // avoid duplicating by just assigning all claims to the first container.
  412. pvReq = podPVs
  413. }
  414. costs := &CostData{
  415. Name: containerName,
  416. PodName: podName,
  417. NodeName: nodeName,
  418. NodeData: nodeData,
  419. Namespace: ns,
  420. Deployments: podDeployments,
  421. Services: podServices,
  422. Daemonsets: getDaemonsetsOfPod(pod),
  423. Jobs: getJobsOfPod(pod),
  424. Statefulsets: getStatefulSetsOfPod(pod),
  425. RAMReq: RAMReqV,
  426. RAMUsed: RAMUsedV,
  427. CPUReq: CPUReqV,
  428. CPUUsed: findContainerMetricVectors(resultCPUUsage, containerName, podName, ns),
  429. GPUReq: GPUReqV,
  430. PVData: pvReq,
  431. Labels: labels,
  432. }
  433. costs.RAMAllocation = getContainerAllocation(costs.RAMReq, costs.RAMUsed)
  434. costs.CPUAllocation = getContainerAllocation(costs.CPUReq, costs.CPUUsed)
  435. containerNameCost[ns+","+podName+","+containerName] = costs
  436. }
  437. }
  438. return containerNameCost, err
  439. }
  440. func getDaemonsetsOfPod(pod v1.Pod) []string {
  441. for _, ownerReference := range pod.ObjectMeta.OwnerReferences {
  442. if ownerReference.Kind == "DaemonSet" {
  443. return []string{ownerReference.Name}
  444. }
  445. }
  446. return []string{}
  447. }
  448. func getJobsOfPod(pod v1.Pod) []string {
  449. for _, ownerReference := range pod.ObjectMeta.OwnerReferences {
  450. if ownerReference.Kind == "Job" {
  451. return []string{ownerReference.Name}
  452. }
  453. }
  454. return []string{}
  455. }
  456. func getStatefulSetsOfPod(pod v1.Pod) []string {
  457. for _, ownerReference := range pod.ObjectMeta.OwnerReferences {
  458. if ownerReference.Kind == "StatefulSet" {
  459. return []string{ownerReference.Name}
  460. }
  461. }
  462. return []string{}
  463. }
  464. type PersistentVolumeData struct {
  465. Class string `json:"class"`
  466. Claim string `json:"claim"`
  467. Namespace string `json:"namespace"`
  468. Values []*Vector `json:"values"`
  469. }
  470. func getPVInfoVectors(qr interface{}) map[string]*PersistentVolumeData {
  471. pvmap := make(map[string]*PersistentVolumeData)
  472. for _, val := range qr.(map[string]interface{})["data"].(map[string]interface{})["result"].([]interface{}) {
  473. pvclaim := val.(map[string]interface{})["metric"].(map[string]interface{})["persistentvolumeclaim"]
  474. pvclass := val.(map[string]interface{})["metric"].(map[string]interface{})["storageclass"]
  475. pvnamespace := val.(map[string]interface{})["metric"].(map[string]interface{})["namespace"]
  476. values := val.(map[string]interface{})["values"].([]interface{})
  477. var vectors []*Vector
  478. for _, value := range values {
  479. strVal := value.([]interface{})[1].(string)
  480. v, _ := strconv.ParseFloat(strVal, 64)
  481. vectors = append(vectors, &Vector{
  482. Timestamp: value.([]interface{})[0].(float64),
  483. Value: v,
  484. })
  485. }
  486. key := pvnamespace.(string) + "," + pvclaim.(string)
  487. pvmap[key] = &PersistentVolumeData{
  488. Class: pvclass.(string),
  489. Claim: pvclaim.(string),
  490. Namespace: pvnamespace.(string),
  491. Values: vectors,
  492. }
  493. }
  494. return pvmap
  495. }
  496. func getPVInfoVector(qr interface{}) map[string]*PersistentVolumeData {
  497. pvmap := make(map[string]*PersistentVolumeData)
  498. log.Printf("Interface %v. If the interface is nil, prometheus is not running!", qr)
  499. for _, val := range qr.(map[string]interface{})["data"].(map[string]interface{})["result"].([]interface{}) {
  500. pvclaim := val.(map[string]interface{})["metric"].(map[string]interface{})["persistentvolumeclaim"]
  501. pvclass := val.(map[string]interface{})["metric"].(map[string]interface{})["storageclass"]
  502. pvnamespace := val.(map[string]interface{})["metric"].(map[string]interface{})["namespace"]
  503. value := val.(map[string]interface{})["value"].([]interface{})
  504. var vectors []*Vector
  505. strVal := value[1].(string)
  506. v, _ := strconv.ParseFloat(strVal, 64)
  507. vectors = append(vectors, &Vector{
  508. Timestamp: value[0].(float64),
  509. Value: v,
  510. })
  511. key := pvclaim.(string) + "," + pvnamespace.(string)
  512. pvmap[key] = &PersistentVolumeData{
  513. Class: pvclass.(string),
  514. Claim: pvclaim.(string),
  515. Namespace: pvnamespace.(string),
  516. Values: vectors,
  517. }
  518. }
  519. return pvmap
  520. }
  521. func queryRange(cli prometheusClient.Client, query string, start, end time.Time, step time.Duration) (interface{}, error) {
  522. u := cli.URL(epQueryRange, nil)
  523. q := u.Query()
  524. q.Set("query", query)
  525. q.Set("start", start.Format(time.RFC3339Nano))
  526. q.Set("end", end.Format(time.RFC3339Nano))
  527. q.Set("step", strconv.FormatFloat(step.Seconds(), 'f', 3, 64))
  528. u.RawQuery = q.Encode()
  529. req, err := http.NewRequest(http.MethodGet, u.String(), nil)
  530. if err != nil {
  531. return nil, err
  532. }
  533. _, body, err := cli.Do(context.Background(), req)
  534. if err != nil {
  535. log.Print("ERROR" + err.Error())
  536. }
  537. if err != nil {
  538. return nil, err
  539. }
  540. var toReturn interface{}
  541. err = json.Unmarshal(body, &toReturn)
  542. if err != nil {
  543. log.Print("ERROR" + err.Error())
  544. }
  545. return toReturn, err
  546. }
  547. func query(cli prometheusClient.Client, query string) (interface{}, error) {
  548. u := cli.URL(epQuery, nil)
  549. q := u.Query()
  550. q.Set("query", query)
  551. u.RawQuery = q.Encode()
  552. req, err := http.NewRequest(http.MethodGet, u.String(), nil)
  553. if err != nil {
  554. return nil, err
  555. }
  556. _, body, err := cli.Do(context.Background(), req)
  557. if err != nil {
  558. return nil, err
  559. }
  560. var toReturn interface{}
  561. err = json.Unmarshal(body, &toReturn)
  562. if err != nil {
  563. log.Print("ERROR" + err.Error())
  564. }
  565. return toReturn, err
  566. }
  567. //todo: don't cast, implement unmarshaler interface
  568. func getNormalization(qr interface{}) float64 {
  569. strNorm := qr.(map[string]interface{})["data"].(map[string]interface{})["result"].([]interface{})[0].(map[string]interface{})["value"].([]interface{})[1].(string)
  570. val, _ := strconv.ParseFloat(strNorm, 64)
  571. return val
  572. }
  573. //todo: don't cast, implement unmarshaler interface...
  574. func findContainerMetric(qr interface{}, cname string, podname string, namespace string) *Vector {
  575. for _, val := range qr.(map[string]interface{})["data"].(map[string]interface{})["result"].([]interface{}) {
  576. if val.(map[string]interface{})["metric"].(map[string]interface{})["container_name"] == cname &&
  577. val.(map[string]interface{})["metric"].(map[string]interface{})["pod_name"] == podname &&
  578. val.(map[string]interface{})["metric"].(map[string]interface{})["namespace"] == namespace {
  579. strVal := val.(map[string]interface{})["value"].([]interface{})[1].(string)
  580. value, _ := strconv.ParseFloat(strVal, 64)
  581. toReturn := &Vector{
  582. Timestamp: val.(map[string]interface{})["value"].([]interface{})[0].(float64),
  583. Value: value,
  584. }
  585. return toReturn
  586. }
  587. }
  588. return &Vector{}
  589. }
  590. func findContainerMetricVectors(qr interface{}, cname string, podname string, namespace string) []*Vector {
  591. for _, val := range qr.(map[string]interface{})["data"].(map[string]interface{})["result"].([]interface{}) {
  592. if val.(map[string]interface{})["metric"].(map[string]interface{})["container_name"] == cname &&
  593. val.(map[string]interface{})["metric"].(map[string]interface{})["pod_name"] == podname &&
  594. val.(map[string]interface{})["metric"].(map[string]interface{})["namespace"] == namespace {
  595. values := val.(map[string]interface{})["values"].([]interface{})
  596. var vectors []*Vector
  597. for _, value := range values {
  598. strVal := value.([]interface{})[1].(string)
  599. v, _ := strconv.ParseFloat(strVal, 64)
  600. vectors = append(vectors, &Vector{
  601. Timestamp: value.([]interface{})[0].(float64),
  602. Value: v,
  603. })
  604. }
  605. return vectors
  606. }
  607. }
  608. return []*Vector{}
  609. }