costmodel.go 27 KB

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