metrics.go 19 KB

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