metrics.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  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. type SimpleIngress struct {
  25. Name string `json:"name"`
  26. Namespace string `json:"namespace"`
  27. }
  28. // GetIngressesWithNGINXAnnotation gets an array of names for all ingresses controlled by
  29. // NGINX
  30. func GetIngressesWithNGINXAnnotation(clientset kubernetes.Interface) ([]SimpleIngress, error) {
  31. ingressList, err := clientset.NetworkingV1beta1().Ingresses("").List(context.TODO(), metav1.ListOptions{})
  32. if err != nil {
  33. return nil, err
  34. }
  35. res := make([]SimpleIngress, 0)
  36. for _, ingress := range ingressList.Items {
  37. if ingressAnn, found := ingress.ObjectMeta.Annotations["kubernetes.io/ingress.class"]; found {
  38. if ingressAnn == "nginx" {
  39. res = append(res, SimpleIngress{
  40. Name: ingress.ObjectMeta.Name,
  41. Namespace: ingress.ObjectMeta.Namespace,
  42. })
  43. }
  44. }
  45. }
  46. return res, nil
  47. }
  48. type QueryOpts struct {
  49. Metric string `schema:"metric"`
  50. ShouldSum bool `schema:"shouldsum"`
  51. PodList []string `schema:"pods"`
  52. Namespace string `schema:"namespace"`
  53. StartRange uint `schema:"startrange"`
  54. EndRange uint `schema:"endrange"`
  55. Resolution string `schema:"resolution"`
  56. }
  57. func QueryPrometheus(
  58. clientset kubernetes.Interface,
  59. service *v1.Service,
  60. opts *QueryOpts,
  61. ) ([]byte, error) {
  62. if len(service.Spec.Ports) == 0 {
  63. return nil, fmt.Errorf("prometheus service has no exposed ports to query")
  64. }
  65. podSelector := fmt.Sprintf(`namespace="%s",pod=~"%s",container!="POD",container!=""`, opts.Namespace, strings.Join(opts.PodList, "|"))
  66. query := ""
  67. if opts.Metric == "cpu" {
  68. query = fmt.Sprintf("rate(container_cpu_usage_seconds_total{%s}[5m])", podSelector)
  69. } else if opts.Metric == "memory" {
  70. query = fmt.Sprintf("container_memory_usage_bytes{%s}", podSelector)
  71. } else if opts.Metric == "network" {
  72. netPodSelector := fmt.Sprintf(`namespace="%s",pod=~"%s",container="POD"`, opts.Namespace, strings.Join(opts.PodList, "|"))
  73. query = fmt.Sprintf("rate(container_network_receive_bytes_total{%s}[5m])", netPodSelector)
  74. } else if opts.Metric == "nginx:errors" {
  75. num := fmt.Sprintf(`sum(rate(nginx_ingress_controller_requests{status=~"5.*",namespace="%s",ingress=~"%s"}[5m]) OR on() vector(0))`, opts.Namespace, strings.Join(opts.PodList, "|"))
  76. denom := fmt.Sprintf(`sum(rate(nginx_ingress_controller_requests{namespace="%s",ingress=~"%s"}[5m]) > 0)`, opts.Namespace, strings.Join(opts.PodList, "|"))
  77. query = fmt.Sprintf(`%s / %s * 100 OR on() vector(0)`, num, denom)
  78. }
  79. if opts.ShouldSum {
  80. query = fmt.Sprintf("sum(%s)", query)
  81. }
  82. queryParams := map[string]string{
  83. "query": query,
  84. "start": fmt.Sprintf("%d", opts.StartRange),
  85. "end": fmt.Sprintf("%d", opts.EndRange),
  86. "step": opts.Resolution,
  87. }
  88. resp := clientset.CoreV1().Services(service.Namespace).ProxyGet(
  89. "http",
  90. service.Name,
  91. fmt.Sprintf("%d", service.Spec.Ports[0].Port),
  92. "/api/v1/query_range",
  93. queryParams,
  94. )
  95. rawQuery, err := resp.DoRaw(context.TODO())
  96. if err != nil {
  97. return nil, err
  98. }
  99. return parseQuery(rawQuery, opts.Metric)
  100. }
  101. type promRawQuery struct {
  102. Data struct {
  103. Result []struct {
  104. Metric struct {
  105. Pod string `json:"pod,omitempty"`
  106. } `json:"metric,omitempty"`
  107. Values [][]interface{} `json:"values"`
  108. } `json:"result"`
  109. } `json:"data"`
  110. }
  111. type promParsedSingletonQueryResult struct {
  112. Date interface{} `json:"date,omitempty"`
  113. CPU interface{} `json:"cpu,omitempty"`
  114. Memory interface{} `json:"memory,omitempty"`
  115. Bytes interface{} `json:"bytes,omitempty"`
  116. ErrorPct interface{} `json:"error_pct,omitempty"`
  117. }
  118. type promParsedSingletonQuery struct {
  119. Pod string `json:"pod,omitempty"`
  120. Results []promParsedSingletonQueryResult `json:"results"`
  121. }
  122. func parseQuery(rawQuery []byte, metric string) ([]byte, error) {
  123. rawQueryObj := &promRawQuery{}
  124. json.Unmarshal(rawQuery, rawQueryObj)
  125. res := make([]*promParsedSingletonQuery, 0)
  126. for _, result := range rawQueryObj.Data.Result {
  127. singleton := &promParsedSingletonQuery{
  128. Pod: result.Metric.Pod,
  129. }
  130. singletonResults := make([]promParsedSingletonQueryResult, 0)
  131. for _, values := range result.Values {
  132. singletonResult := &promParsedSingletonQueryResult{
  133. Date: values[0],
  134. }
  135. if metric == "cpu" {
  136. singletonResult.CPU = values[1]
  137. } else if metric == "memory" {
  138. singletonResult.Memory = values[1]
  139. } else if metric == "network" {
  140. singletonResult.Bytes = values[1]
  141. } else if metric == "nginx:errors" {
  142. singletonResult.ErrorPct = values[1]
  143. }
  144. singletonResults = append(singletonResults, *singletonResult)
  145. }
  146. singleton.Results = singletonResults
  147. res = append(res, singleton)
  148. }
  149. return json.Marshal(res)
  150. }