kubemetrics.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. package metrics
  2. import (
  3. "fmt"
  4. "strings"
  5. "sync"
  6. "github.com/opencost/opencost/core/pkg/util/promutil"
  7. "github.com/opencost/opencost/pkg/clustercache"
  8. "github.com/prometheus/client_golang/prometheus"
  9. batchv1 "k8s.io/api/batch/v1"
  10. v1 "k8s.io/api/core/v1"
  11. "k8s.io/apimachinery/pkg/api/resource"
  12. "k8s.io/apimachinery/pkg/util/validation"
  13. )
  14. //--------------------------------------------------------------------------
  15. // Kube Metric Registration
  16. //--------------------------------------------------------------------------
  17. // initializer
  18. var kubeMetricInit sync.Once
  19. // KubeMetricsOpts represents our Kubernetes metrics emission options.
  20. type KubeMetricsOpts struct {
  21. EmitKubecostControllerMetrics bool
  22. EmitNamespaceAnnotations bool
  23. EmitPodAnnotations bool
  24. EmitKubeStateMetrics bool
  25. EmitKubeStateMetricsV1Only bool
  26. }
  27. // DefaultKubeMetricsOpts returns KubeMetricsOpts with default values set
  28. func DefaultKubeMetricsOpts() *KubeMetricsOpts {
  29. return &KubeMetricsOpts{
  30. EmitKubecostControllerMetrics: true,
  31. EmitNamespaceAnnotations: false,
  32. EmitPodAnnotations: false,
  33. EmitKubeStateMetrics: true,
  34. EmitKubeStateMetricsV1Only: false,
  35. }
  36. }
  37. // InitKubeMetrics initializes kubernetes metric emission using the provided options.
  38. func InitKubeMetrics(clusterCache clustercache.ClusterCache, metricsConfig *MetricsConfig, opts *KubeMetricsOpts) {
  39. if opts == nil {
  40. opts = DefaultKubeMetricsOpts()
  41. }
  42. kubeMetricInit.Do(func() {
  43. if opts.EmitKubecostControllerMetrics {
  44. prometheus.MustRegister(KubecostServiceCollector{
  45. KubeClusterCache: clusterCache,
  46. metricsConfig: *metricsConfig,
  47. })
  48. prometheus.MustRegister(KubecostDeploymentCollector{
  49. KubeClusterCache: clusterCache,
  50. metricsConfig: *metricsConfig,
  51. })
  52. prometheus.MustRegister(KubecostStatefulsetCollector{
  53. KubeClusterCache: clusterCache,
  54. metricsConfig: *metricsConfig,
  55. })
  56. }
  57. if opts.EmitPodAnnotations {
  58. prometheus.MustRegister(KubecostPodCollector{
  59. KubeClusterCache: clusterCache,
  60. metricsConfig: *metricsConfig,
  61. })
  62. }
  63. if opts.EmitNamespaceAnnotations {
  64. prometheus.MustRegister(KubecostNamespaceCollector{
  65. KubeClusterCache: clusterCache,
  66. metricsConfig: *metricsConfig,
  67. })
  68. }
  69. if opts.EmitKubeStateMetrics {
  70. prometheus.MustRegister(KubeNodeCollector{
  71. KubeClusterCache: clusterCache,
  72. metricsConfig: *metricsConfig,
  73. })
  74. prometheus.MustRegister(KubeNamespaceCollector{
  75. KubeClusterCache: clusterCache,
  76. metricsConfig: *metricsConfig,
  77. })
  78. prometheus.MustRegister(KubeDeploymentCollector{
  79. KubeClusterCache: clusterCache,
  80. metricsConfig: *metricsConfig,
  81. })
  82. prometheus.MustRegister(KubePodCollector{
  83. KubeClusterCache: clusterCache,
  84. metricsConfig: *metricsConfig,
  85. })
  86. prometheus.MustRegister(KubePVCollector{
  87. KubeClusterCache: clusterCache,
  88. metricsConfig: *metricsConfig,
  89. })
  90. prometheus.MustRegister(KubePVCCollector{
  91. KubeClusterCache: clusterCache,
  92. metricsConfig: *metricsConfig,
  93. })
  94. prometheus.MustRegister(KubeJobCollector{
  95. KubeClusterCache: clusterCache,
  96. metricsConfig: *metricsConfig,
  97. })
  98. } else if opts.EmitKubeStateMetricsV1Only {
  99. // We still need the kubecost_pv_info metric to look up storageclass on legacy clusters.
  100. forceDisabled := []string{"kube_persistentvolume_capacity_bytes", "kube_persistentvolume_status_phase"}
  101. metricsConfig.DisabledMetrics = append(metricsConfig.DisabledMetrics, forceDisabled...)
  102. prometheus.MustRegister(KubeNodeCollector{
  103. KubeClusterCache: clusterCache,
  104. metricsConfig: *metricsConfig,
  105. })
  106. prometheus.MustRegister(KubeNamespaceCollector{
  107. KubeClusterCache: clusterCache,
  108. metricsConfig: *metricsConfig,
  109. })
  110. prometheus.MustRegister(KubePodLabelsCollector{
  111. KubeClusterCache: clusterCache,
  112. metricsConfig: *metricsConfig,
  113. })
  114. prometheus.MustRegister(KubePVCollector{
  115. KubeClusterCache: clusterCache,
  116. metricsConfig: *metricsConfig,
  117. })
  118. } else {
  119. // We still need the kubecost_pv_info metric to look up storageclass on legacy clusters.
  120. forceDisabled := []string{"kube_persistentvolume_capacity_bytes", "kube_persistentvolume_status_phase"}
  121. metricsConfig.DisabledMetrics = append(metricsConfig.DisabledMetrics, forceDisabled...)
  122. prometheus.MustRegister(KubePVCollector{
  123. KubeClusterCache: clusterCache,
  124. metricsConfig: *metricsConfig,
  125. })
  126. }
  127. })
  128. }
  129. //--------------------------------------------------------------------------
  130. // Kube Metric Helpers
  131. //--------------------------------------------------------------------------
  132. // getPersistentVolumeClaimClass returns StorageClassName. If no storage class was
  133. // requested, it returns "".
  134. func getPersistentVolumeClaimClass(claim *v1.PersistentVolumeClaim) string {
  135. // Use beta annotation first
  136. if class, found := claim.Annotations[v1.BetaStorageClassAnnotation]; found {
  137. return class
  138. }
  139. if claim.Spec.StorageClassName != nil {
  140. return *claim.Spec.StorageClassName
  141. }
  142. // Special non-empty string to indicate absence of storage class.
  143. return ""
  144. }
  145. // toResourceUnitValue accepts a resource name and quantity and returns the sanitized resource, the unit, and the value in the units.
  146. // Returns an empty string for resource and unit if there was a failure.
  147. func toResourceUnitValue(resourceName v1.ResourceName, quantity resource.Quantity) (resource string, unit string, value float64) {
  148. resource = promutil.SanitizeLabelName(string(resourceName))
  149. switch resourceName {
  150. case v1.ResourceCPU:
  151. unit = "core"
  152. value = float64(quantity.MilliValue()) / 1000
  153. return
  154. case v1.ResourceStorage:
  155. fallthrough
  156. case v1.ResourceEphemeralStorage:
  157. fallthrough
  158. case v1.ResourceMemory:
  159. unit = "byte"
  160. value = float64(quantity.Value())
  161. return
  162. case v1.ResourcePods:
  163. unit = "integer"
  164. value = float64(quantity.Value())
  165. return
  166. default:
  167. if isHugePageResourceName(resourceName) || isAttachableVolumeResourceName(resourceName) {
  168. unit = "byte"
  169. value = float64(quantity.Value())
  170. return
  171. }
  172. if isExtendedResourceName(resourceName) {
  173. unit = "integer"
  174. value = float64(quantity.Value())
  175. return
  176. }
  177. }
  178. resource = ""
  179. unit = ""
  180. value = 0.0
  181. return
  182. }
  183. // isHugePageResourceName checks for a huge page container resource name
  184. func isHugePageResourceName(name v1.ResourceName) bool {
  185. return strings.HasPrefix(string(name), v1.ResourceHugePagesPrefix)
  186. }
  187. // isAttachableVolumeResourceName checks for attached volume container resource name
  188. func isAttachableVolumeResourceName(name v1.ResourceName) bool {
  189. return strings.HasPrefix(string(name), v1.ResourceAttachableVolumesPrefix)
  190. }
  191. // isExtendedResourceName checks for extended container resource name
  192. func isExtendedResourceName(name v1.ResourceName) bool {
  193. if isNativeResource(name) || strings.HasPrefix(string(name), v1.DefaultResourceRequestsPrefix) {
  194. return false
  195. }
  196. // Ensure it satisfies the rules in IsQualifiedName() after converted into quota resource name
  197. nameForQuota := fmt.Sprintf("%s%s", v1.DefaultResourceRequestsPrefix, string(name))
  198. if errs := validation.IsQualifiedName(nameForQuota); len(errs) != 0 {
  199. return false
  200. }
  201. return true
  202. }
  203. // isNativeResource checks for a kubernetes.io/ prefixed resource name
  204. func isNativeResource(name v1.ResourceName) bool {
  205. return !strings.Contains(string(name), "/") || isPrefixedNativeResource(name)
  206. }
  207. func isPrefixedNativeResource(name v1.ResourceName) bool {
  208. return strings.Contains(string(name), v1.ResourceDefaultNamespacePrefix)
  209. }
  210. func failureReason(jc *batchv1.JobCondition, reason string) bool {
  211. if jc == nil {
  212. return false
  213. }
  214. return jc.Reason == reason
  215. }
  216. // boolFloat64 converts a boolean input into a 1 or 0
  217. func boolFloat64(b bool) float64 {
  218. if b {
  219. return 1
  220. }
  221. return 0
  222. }
  223. // toStringPtr is used to create a new string pointer from iteration vars
  224. func toStringPtr(s string) *string { return &s }