metrics.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. package prometheus
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "strings"
  7. v1 "k8s.io/api/core/v1"
  8. "k8s.io/client-go/kubernetes"
  9. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  10. )
  11. // GetPrometheusService returns the prometheus service name. The prometheus-community/prometheus chart @ v15.5.3 uses non-FQDN labels, unlike v22.6.2. This function checks for both labels.
  12. func GetPrometheusService(clientset kubernetes.Interface) (*v1.Service, bool, error) {
  13. redundantServices, err := clientset.CoreV1().Services("").List(context.TODO(), metav1.ListOptions{
  14. LabelSelector: "app=prometheus,component=server,heritage=Helm",
  15. })
  16. if err != nil {
  17. return nil, false, err
  18. }
  19. upgradedServices, err := clientset.CoreV1().Services("").List(context.TODO(), metav1.ListOptions{
  20. LabelSelector: "app.kubernetes.io/component=server,app.kubernetes.io/instance=prometheus,app.kubernetes.io/managed-by=Helm",
  21. })
  22. if err != nil {
  23. return nil, false, err
  24. }
  25. if len(redundantServices.Items) > 0 {
  26. return &redundantServices.Items[0], true, nil
  27. }
  28. if len(upgradedServices.Items) > 0 {
  29. return &upgradedServices.Items[0], true, nil
  30. }
  31. return nil, false, err
  32. }
  33. // getKubeStateMetricsService returns the prometheus service name
  34. func getKubeStateMetricsService(clientset kubernetes.Interface) (*v1.Service, bool, error) {
  35. services, err := clientset.CoreV1().Services("").List(context.TODO(), metav1.ListOptions{
  36. LabelSelector: "app.kubernetes.io/name=kube-state-metrics",
  37. })
  38. if err != nil {
  39. return nil, false, err
  40. }
  41. if len(services.Items) == 0 {
  42. return nil, false, nil
  43. }
  44. return &services.Items[0], true, nil
  45. }
  46. type SimpleIngress struct {
  47. Name string `json:"name"`
  48. Namespace string `json:"namespace"`
  49. }
  50. // GetIngressesWithNGINXAnnotation gets an array of names for all ingresses controlled by
  51. // NGINX
  52. func GetIngressesWithNGINXAnnotation(clientset kubernetes.Interface) ([]SimpleIngress, error) {
  53. res := make([]SimpleIngress, 0)
  54. foundMap := make(map[string]bool)
  55. v1beta1IngressList, v1beta1Err := clientset.NetworkingV1beta1().Ingresses("").List(context.TODO(), metav1.ListOptions{})
  56. v1IngressList, v1Err := clientset.NetworkingV1().Ingresses("").List(context.TODO(), metav1.ListOptions{})
  57. if v1beta1Err != nil && v1Err != nil {
  58. return nil, fmt.Errorf("List ingresses error: %s, %s", v1beta1Err.Error(), v1Err.Error())
  59. }
  60. if v1beta1Err == nil && len(v1beta1IngressList.Items) > 0 {
  61. for _, ingress := range v1beta1IngressList.Items {
  62. ingressAnn, found := ingress.ObjectMeta.Annotations["kubernetes.io/ingress.class"]
  63. uid := fmt.Sprintf("%s/%s", ingress.ObjectMeta.Namespace, ingress.ObjectMeta.Name)
  64. if _, exists := foundMap[uid]; !exists && ((found && ingressAnn == "nginx") || *ingress.Spec.IngressClassName == "nginx") {
  65. res = append(res, SimpleIngress{
  66. Name: ingress.ObjectMeta.Name,
  67. Namespace: ingress.ObjectMeta.Namespace,
  68. })
  69. foundMap[uid] = true
  70. }
  71. }
  72. }
  73. if v1Err == nil && len(v1IngressList.Items) > 0 {
  74. for _, ingress := range v1IngressList.Items {
  75. ingressAnn, found := ingress.ObjectMeta.Annotations["kubernetes.io/ingress.class"]
  76. uid := fmt.Sprintf("%s/%s", ingress.ObjectMeta.Namespace, ingress.ObjectMeta.Name)
  77. if _, exists := foundMap[uid]; !exists && ((found && ingressAnn == "nginx") || *ingress.Spec.IngressClassName == "nginx") {
  78. res = append(res, SimpleIngress{
  79. Name: ingress.ObjectMeta.Name,
  80. Namespace: ingress.ObjectMeta.Namespace,
  81. })
  82. foundMap[uid] = true
  83. }
  84. }
  85. }
  86. return res, nil
  87. }
  88. type QueryOpts struct {
  89. Metric string `schema:"metric"`
  90. ShouldSum bool `schema:"shouldsum"`
  91. Kind string `schema:"kind"`
  92. PodList []string `schema:"pods"`
  93. Name string `schema:"name"`
  94. Namespace string `schema:"namespace"`
  95. StartRange uint `schema:"startrange"`
  96. EndRange uint `schema:"endrange"`
  97. Resolution string `schema:"resolution"`
  98. Percentile float64 `schema:"percentile"`
  99. }
  100. func QueryPrometheus(
  101. clientset kubernetes.Interface,
  102. service *v1.Service,
  103. opts *QueryOpts,
  104. ) ([]*promParsedSingletonQuery, error) {
  105. if len(service.Spec.Ports) == 0 {
  106. return nil, fmt.Errorf("prometheus service has no exposed ports to query")
  107. }
  108. selectionRegex, err := getSelectionRegex(opts.Kind, opts.Name)
  109. if err != nil {
  110. return nil, err
  111. }
  112. var podSelector string
  113. if len(opts.PodList) > 0 {
  114. podSelector = fmt.Sprintf(`namespace="%s",pod=~"%s",container!="POD",container!=""`, opts.Namespace, strings.Join(opts.PodList, "|"))
  115. } else {
  116. podSelector = fmt.Sprintf(`namespace="%s",pod=~"%s",container!="POD",container!=""`, opts.Namespace, selectionRegex)
  117. }
  118. query := ""
  119. if opts.Metric == "cpu" {
  120. query = fmt.Sprintf("rate(container_cpu_usage_seconds_total{%s}[5m])", podSelector)
  121. } else if opts.Metric == "memory" {
  122. query = fmt.Sprintf("container_memory_usage_bytes{%s}", podSelector)
  123. } else if opts.Metric == "network" {
  124. netPodSelector := fmt.Sprintf(`namespace="%s",pod=~"%s"`, opts.Namespace, selectionRegex)
  125. query = fmt.Sprintf("rate(container_network_receive_bytes_total{%s}[5m])", netPodSelector)
  126. } else if opts.Metric == "nginx:errors" {
  127. num := fmt.Sprintf(`(sum(rate(nginx_ingress_controller_requests{status=~"5.*",exported_namespace="%s",ingress=~"%s"}[5m]) OR sum(rate(nginx_ingress_controller_requests{status=~"5.*",namespace="%s",ingress=~"%s"}[5m])) OR on() vector(0))`, opts.Namespace, selectionRegex, opts.Namespace, selectionRegex)
  128. denom := fmt.Sprintf(`(sum(rate(nginx_ingress_controller_requests{exported_namespace="%s",ingress=~"%s"}[5m]) OR sum(rate(nginx_ingress_controller_requests{namespace="%s",ingress=~"%s"}[5m])) > 0)`, opts.Namespace, selectionRegex, opts.Namespace, selectionRegex)
  129. query = fmt.Sprintf(`%s / %s * 100 OR on() vector(0)`, num, denom)
  130. } else if opts.Metric == "nginx:latency" {
  131. num := fmt.Sprintf(`(sum(rate(nginx_ingress_controller_request_duration_seconds_sum{exported_namespace=~"%s",ingress=~"%s"}[5m]) OR sum(rate(nginx_ingress_controller_request_duration_seconds_sum{namespace=~"%s",ingress=~"%s"}[5m])) OR on() vector(0))`, opts.Namespace, selectionRegex, opts.Namespace, selectionRegex)
  132. denom := fmt.Sprintf(`(sum(rate(nginx_ingress_controller_request_duration_seconds_count{exported_namespace=~"%s",ingress=~"%s"}[5m])) OR sum(rate(nginx_ingress_controller_request_duration_seconds_count{namespace=~"%s",ingress=~"%s"}[5m])))`, opts.Namespace, selectionRegex, opts.Namespace, selectionRegex)
  133. query = fmt.Sprintf(`%s / %s OR on() vector(0)`, num, denom)
  134. } else if opts.Metric == "nginx:latency-histogram" {
  135. query = fmt.Sprintf(`histogram_quantile(%f, (sum(rate(nginx_ingress_controller_request_duration_seconds_bucket{status!="404",status!="500",exported_namespace=~"%s",ingress=~"%s"}[5m])) OR sum(rate(nginx_ingress_controller_request_duration_seconds_bucket{status!="404",status!="500",namespace=~"%s",ingress=~"%s"}[5m]))) by (le, ingress))`, opts.Percentile, opts.Namespace, selectionRegex, opts.Namespace, selectionRegex)
  136. } else if opts.Metric == "cpu_hpa_threshold" {
  137. // get the name of the kube hpa metric
  138. metricName, hpaMetricName := getKubeHPAMetricName(clientset, service, opts, "spec_target_metric")
  139. cpuMetricName := getKubeCPUMetricName(clientset, service, opts)
  140. ksmSvc, found, _ := getKubeStateMetricsService(clientset)
  141. appLabel := ""
  142. if found {
  143. appLabel = ksmSvc.ObjectMeta.Labels["app.kubernetes.io/instance"]
  144. }
  145. query = createHPAAbsoluteCPUThresholdQuery(cpuMetricName, metricName, selectionRegex, opts.Name, opts.Namespace, appLabel, hpaMetricName)
  146. } else if opts.Metric == "memory_hpa_threshold" {
  147. metricName, hpaMetricName := getKubeHPAMetricName(clientset, service, opts, "spec_target_metric")
  148. memMetricName := getKubeMemoryMetricName(clientset, service, opts)
  149. ksmSvc, found, _ := getKubeStateMetricsService(clientset)
  150. appLabel := ""
  151. if found {
  152. appLabel = ksmSvc.ObjectMeta.Labels["app.kubernetes.io/instance"]
  153. }
  154. query = createHPAAbsoluteMemoryThresholdQuery(memMetricName, metricName, selectionRegex, opts.Name, opts.Namespace, appLabel, hpaMetricName)
  155. } else if opts.Metric == "hpa_replicas" {
  156. metricName, hpaMetricName := getKubeHPAMetricName(clientset, service, opts, "status_current_replicas")
  157. ksmSvc, found, _ := getKubeStateMetricsService(clientset)
  158. appLabel := ""
  159. if found {
  160. appLabel = ksmSvc.ObjectMeta.Labels["app.kubernetes.io/instance"]
  161. }
  162. query = createHPACurrentReplicasQuery(metricName, opts.Name, opts.Namespace, appLabel, hpaMetricName)
  163. }
  164. if opts.ShouldSum {
  165. query = fmt.Sprintf("sum(%s)", query)
  166. }
  167. queryParams := map[string]string{
  168. "query": query,
  169. "start": fmt.Sprintf("%d", opts.StartRange),
  170. "end": fmt.Sprintf("%d", opts.EndRange),
  171. "step": opts.Resolution,
  172. }
  173. resp := clientset.CoreV1().Services(service.Namespace).ProxyGet(
  174. "http",
  175. service.Name,
  176. fmt.Sprintf("%d", service.Spec.Ports[0].Port),
  177. "/api/v1/query_range",
  178. queryParams,
  179. )
  180. rawQuery, err := resp.DoRaw(context.TODO())
  181. if err != nil {
  182. // in this case, it's very likely that prometheus doesn't contain any data for the given labels
  183. if strings.Contains(err.Error(), "rejected our request for an unknown reason") {
  184. return []*promParsedSingletonQuery{}, nil
  185. }
  186. return nil, err
  187. }
  188. return parseQuery(rawQuery, opts.Metric)
  189. }
  190. type promRawQuery struct {
  191. Data struct {
  192. Result []struct {
  193. Metric struct {
  194. Pod string `json:"pod,omitempty"`
  195. } `json:"metric,omitempty"`
  196. Values [][]interface{} `json:"values"`
  197. } `json:"result"`
  198. } `json:"data"`
  199. }
  200. type promParsedSingletonQueryResult struct {
  201. Date interface{} `json:"date,omitempty"`
  202. CPU interface{} `json:"cpu,omitempty"`
  203. Replicas interface{} `json:"replicas,omitempty"`
  204. Memory interface{} `json:"memory,omitempty"`
  205. Bytes interface{} `json:"bytes,omitempty"`
  206. ErrorPct interface{} `json:"error_pct,omitempty"`
  207. Latency interface{} `json:"latency,omitempty"`
  208. }
  209. type promParsedSingletonQuery struct {
  210. Pod string `json:"pod,omitempty"`
  211. Results []promParsedSingletonQueryResult `json:"results"`
  212. }
  213. func parseQuery(rawQuery []byte, metric string) ([]*promParsedSingletonQuery, error) {
  214. rawQueryObj := &promRawQuery{}
  215. err := json.Unmarshal(rawQuery, rawQueryObj)
  216. if err != nil {
  217. return nil, err
  218. }
  219. res := make([]*promParsedSingletonQuery, 0)
  220. for _, result := range rawQueryObj.Data.Result {
  221. singleton := &promParsedSingletonQuery{
  222. Pod: result.Metric.Pod,
  223. }
  224. singletonResults := make([]promParsedSingletonQueryResult, 0)
  225. for _, values := range result.Values {
  226. singletonResult := &promParsedSingletonQueryResult{
  227. Date: values[0],
  228. }
  229. if metric == "cpu" {
  230. singletonResult.CPU = values[1]
  231. } else if metric == "memory" {
  232. singletonResult.Memory = values[1]
  233. } else if metric == "network" {
  234. singletonResult.Bytes = values[1]
  235. } else if metric == "nginx:errors" {
  236. singletonResult.ErrorPct = values[1]
  237. } else if metric == "cpu_hpa_threshold" {
  238. singletonResult.CPU = values[1]
  239. } else if metric == "memory_hpa_threshold" {
  240. singletonResult.Memory = values[1]
  241. } else if metric == "hpa_replicas" {
  242. singletonResult.Replicas = values[1]
  243. } else if metric == "nginx:latency" || metric == "nginx:latency-histogram" {
  244. singletonResult.Latency = values[1]
  245. }
  246. singletonResults = append(singletonResults, *singletonResult)
  247. }
  248. singleton.Results = singletonResults
  249. res = append(res, singleton)
  250. }
  251. return res, nil
  252. }
  253. func getSelectionRegex(kind, name string) (string, error) {
  254. var suffix string
  255. switch strings.ToLower(kind) {
  256. case "deployment":
  257. suffix = "[a-z0-9]+(-[a-z0-9]+)*"
  258. case "statefulset":
  259. suffix = "[0-9]+"
  260. case "job":
  261. suffix = "[a-z0-9]+"
  262. case "cronjob":
  263. suffix = "[a-z0-9]+-[a-z0-9]+"
  264. case "ingress":
  265. return name, nil
  266. case "daemonset":
  267. suffix = "[a-z0-9]+"
  268. default:
  269. return "", fmt.Errorf("not a supported controller to query for metrics")
  270. }
  271. return fmt.Sprintf("%s-%s", name, suffix), nil
  272. }
  273. func createHPAAbsoluteCPUThresholdQuery(cpuMetricName, metricName, podSelectionRegex, hpaName, namespace, appLabel, hpaMetricName string) string {
  274. kubeMetricsPodSelectorOne := getKubeMetricsPodSelector(podSelectionRegex, namespace, "namespace")
  275. kubeMetricsPodSelectorTwo := getKubeMetricsPodSelector(podSelectionRegex, namespace, "exported_namespace")
  276. kubeMetricsHPASelectorOne := fmt.Sprintf(
  277. `%s="%s",namespace="%s",metric_name="cpu",metric_target_type="utilization"`,
  278. hpaMetricName,
  279. hpaName,
  280. namespace,
  281. )
  282. kubeMetricsHPASelectorTwo := fmt.Sprintf(
  283. `%s="%s",exported_namespace="%s",metric_name="cpu",metric_target_type="utilization"`,
  284. hpaMetricName,
  285. hpaName,
  286. namespace,
  287. )
  288. if cpuMetricName == "kube_pod_container_resource_requests" {
  289. kubeMetricsPodSelectorOne += `,resource="cpu",unit="core"`
  290. kubeMetricsPodSelectorTwo += `,resource="cpu",unit="core"`
  291. }
  292. // the kube-state-metrics queries are less prone to error if the field app_kubernetes_io_instance is matched
  293. // as well
  294. if appLabel != "" {
  295. kubeMetricsPodSelectorOne += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
  296. kubeMetricsPodSelectorTwo += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
  297. kubeMetricsHPASelectorOne += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
  298. kubeMetricsHPASelectorTwo += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
  299. }
  300. requestCPUOne := fmt.Sprintf(
  301. `sum by (%s) (label_replace(%s{%s},"%s", "%s", "", ""))`,
  302. hpaMetricName,
  303. cpuMetricName,
  304. kubeMetricsPodSelectorOne,
  305. hpaMetricName,
  306. hpaName,
  307. )
  308. targetCPUUtilThresholdOne := fmt.Sprintf(
  309. `%s{%s} / 100`,
  310. metricName,
  311. kubeMetricsHPASelectorOne,
  312. )
  313. requestCPUTwo := fmt.Sprintf(
  314. `sum by (%s) (label_replace(%s{%s},"%s", "%s", "", ""))`,
  315. hpaMetricName,
  316. cpuMetricName,
  317. kubeMetricsPodSelectorTwo,
  318. hpaMetricName,
  319. hpaName,
  320. )
  321. targetCPUUtilThresholdTwo := fmt.Sprintf(
  322. `%s{%s} / 100`,
  323. metricName,
  324. kubeMetricsHPASelectorTwo,
  325. )
  326. return fmt.Sprintf(
  327. `(%s * on(%s) %s) or (%s * on(%s) %s)`,
  328. requestCPUOne, hpaMetricName, targetCPUUtilThresholdOne,
  329. requestCPUTwo, hpaMetricName, targetCPUUtilThresholdTwo,
  330. )
  331. }
  332. func createHPAAbsoluteMemoryThresholdQuery(memMetricName, metricName, podSelectionRegex, hpaName, namespace, appLabel, hpaMetricName string) string {
  333. kubeMetricsPodSelectorOne := getKubeMetricsPodSelector(podSelectionRegex, namespace, "namespace")
  334. kubeMetricsPodSelectorTwo := getKubeMetricsPodSelector(podSelectionRegex, namespace, "exported_namespace")
  335. kubeMetricsHPASelectorOne := fmt.Sprintf(
  336. `%s="%s",namespace="%s",metric_name="memory",metric_target_type="utilization"`,
  337. hpaMetricName,
  338. hpaName,
  339. namespace,
  340. )
  341. kubeMetricsHPASelectorTwo := fmt.Sprintf(
  342. `%s="%s",exported_namespace="%s",metric_name="memory",metric_target_type="utilization"`,
  343. hpaMetricName,
  344. hpaName,
  345. namespace,
  346. )
  347. if memMetricName == "kube_pod_container_resource_requests" {
  348. kubeMetricsPodSelectorOne += `,resource="memory",unit="byte"`
  349. kubeMetricsPodSelectorTwo += `,resource="memory",unit="byte"`
  350. }
  351. // the kube-state-metrics queries are less prone to error if the field app_kubernetes_io_instance is matched
  352. // as well
  353. if appLabel != "" {
  354. kubeMetricsPodSelectorOne += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
  355. kubeMetricsPodSelectorTwo += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
  356. kubeMetricsHPASelectorOne += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
  357. kubeMetricsHPASelectorTwo += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
  358. }
  359. requestMemOne := fmt.Sprintf(
  360. `sum by (%s) (label_replace(%s{%s},"%s", "%s", "", ""))`,
  361. hpaMetricName,
  362. memMetricName,
  363. kubeMetricsPodSelectorOne,
  364. hpaMetricName,
  365. hpaName,
  366. )
  367. targetMemUtilThresholdOne := fmt.Sprintf(
  368. `%s{%s} / 100`,
  369. metricName,
  370. kubeMetricsHPASelectorOne,
  371. )
  372. requestMemTwo := fmt.Sprintf(
  373. `sum by (%s) (label_replace(%s{%s},"%s", "%s", "", ""))`,
  374. hpaMetricName,
  375. memMetricName,
  376. kubeMetricsPodSelectorTwo,
  377. hpaMetricName,
  378. hpaName,
  379. )
  380. targetMemUtilThresholdTwo := fmt.Sprintf(
  381. `%s{%s} / 100`,
  382. metricName,
  383. kubeMetricsHPASelectorTwo,
  384. )
  385. return fmt.Sprintf(
  386. `(%s * on(%s) %s) or (%s * on(%s) %s)`,
  387. requestMemOne, hpaMetricName, targetMemUtilThresholdOne,
  388. requestMemTwo, hpaMetricName, targetMemUtilThresholdTwo,
  389. )
  390. }
  391. func getKubeMetricsPodSelector(podSelectionRegex, namespace, namespaceLabel string) string {
  392. return fmt.Sprintf(
  393. `pod=~"%s",%s="%s",container!="POD",container!=""`,
  394. podSelectionRegex,
  395. namespaceLabel,
  396. namespace,
  397. )
  398. }
  399. func createHPACurrentReplicasQuery(metricName, hpaName, namespace, appLabel, hpaMetricName string) string {
  400. kubeMetricsHPASelectorOne := fmt.Sprintf(
  401. `%s="%s",namespace="%s"`,
  402. hpaMetricName,
  403. hpaName,
  404. namespace,
  405. )
  406. kubeMetricsHPASelectorTwo := fmt.Sprintf(
  407. `%s="%s",exported_namespace="%s"`,
  408. hpaMetricName,
  409. hpaName,
  410. namespace,
  411. )
  412. // the kube-state-metrics queries are less prone to error if the field app_kubernetes_io_instance is matched
  413. // as well
  414. if appLabel != "" {
  415. kubeMetricsHPASelectorOne += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
  416. kubeMetricsHPASelectorTwo += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
  417. }
  418. return fmt.Sprintf(
  419. `(%s{%s}) or (%s{%s})`,
  420. metricName,
  421. kubeMetricsHPASelectorOne,
  422. metricName,
  423. kubeMetricsHPASelectorTwo,
  424. )
  425. }
  426. type promRawValuesQuery struct {
  427. Status string `json:"status"`
  428. Data []string `json:"data"`
  429. }
  430. // getKubeHPAMetricName performs a "best guess" for the name of the kube HPA metric,
  431. // which was renamed to kube_horizontalpodautoscaler... in later versions of kube-state-metrics.
  432. // we query Prometheus for a list of metric names to see if any match the new query
  433. // value, otherwise we return the deprecated name.
  434. func getKubeHPAMetricName(
  435. clientset kubernetes.Interface,
  436. service *v1.Service,
  437. opts *QueryOpts,
  438. suffix string,
  439. ) (string, string) {
  440. queryParams := map[string]string{
  441. "match[]": fmt.Sprintf("kube_horizontalpodautoscaler_%s", suffix),
  442. "start": fmt.Sprintf("%d", opts.StartRange),
  443. "end": fmt.Sprintf("%d", opts.EndRange),
  444. }
  445. resp := clientset.CoreV1().Services(service.Namespace).ProxyGet(
  446. "http",
  447. service.Name,
  448. fmt.Sprintf("%d", service.Spec.Ports[0].Port),
  449. "/api/v1/label/__name__/values",
  450. queryParams,
  451. )
  452. rawQuery, err := resp.DoRaw(context.TODO())
  453. if err != nil {
  454. return fmt.Sprintf("kube_hpa_%s", suffix), "hpa"
  455. }
  456. rawQueryObj := &promRawValuesQuery{}
  457. json.Unmarshal(rawQuery, rawQueryObj)
  458. if rawQueryObj.Status == "success" && len(rawQueryObj.Data) == 1 {
  459. return fmt.Sprintf("kube_horizontalpodautoscaler_%s", suffix), "horizontalpodautoscaler"
  460. }
  461. return fmt.Sprintf("kube_hpa_%s", suffix), "hpa"
  462. }
  463. func getKubeCPUMetricName(
  464. clientset kubernetes.Interface,
  465. service *v1.Service,
  466. opts *QueryOpts,
  467. ) string {
  468. queryParams := map[string]string{
  469. "match[]": "kube_pod_container_resource_requests",
  470. "start": fmt.Sprintf("%d", opts.StartRange),
  471. "end": fmt.Sprintf("%d", opts.EndRange),
  472. }
  473. resp := clientset.CoreV1().Services(service.Namespace).ProxyGet(
  474. "http",
  475. service.Name,
  476. fmt.Sprintf("%d", service.Spec.Ports[0].Port),
  477. "/api/v1/label/__name__/values",
  478. queryParams,
  479. )
  480. rawQuery, err := resp.DoRaw(context.TODO())
  481. if err != nil {
  482. return "kube_pod_container_resource_requests_cpu_cores"
  483. }
  484. rawQueryObj := &promRawValuesQuery{}
  485. json.Unmarshal(rawQuery, rawQueryObj)
  486. if rawQueryObj.Status == "success" && len(rawQueryObj.Data) == 1 {
  487. return "kube_pod_container_resource_requests"
  488. }
  489. return "kube_pod_container_resource_requests_cpu_cores"
  490. }
  491. func getKubeMemoryMetricName(
  492. clientset kubernetes.Interface,
  493. service *v1.Service,
  494. opts *QueryOpts,
  495. ) string {
  496. queryParams := map[string]string{
  497. "match[]": "kube_pod_container_resource_requests",
  498. "start": fmt.Sprintf("%d", opts.StartRange),
  499. "end": fmt.Sprintf("%d", opts.EndRange),
  500. }
  501. resp := clientset.CoreV1().Services(service.Namespace).ProxyGet(
  502. "http",
  503. service.Name,
  504. fmt.Sprintf("%d", service.Spec.Ports[0].Port),
  505. "/api/v1/label/__name__/values",
  506. queryParams,
  507. )
  508. rawQuery, err := resp.DoRaw(context.TODO())
  509. if err != nil {
  510. return "kube_pod_container_resource_requests_memory_bytes"
  511. }
  512. rawQueryObj := &promRawValuesQuery{}
  513. json.Unmarshal(rawQuery, rawQueryObj)
  514. if rawQueryObj.Status == "success" && len(rawQueryObj.Data) == 1 {
  515. return "kube_pod_container_resource_requests"
  516. }
  517. return "kube_pod_container_resource_requests_memory_bytes"
  518. }