kubemetrics.go 8.5 KB

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