metrics.go 19 KB

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