clustercache.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  1. package scrape
  2. import (
  3. "fmt"
  4. "slices"
  5. "strings"
  6. "github.com/kubecost/events"
  7. "github.com/opencost/opencost/core/pkg/clustercache"
  8. "github.com/opencost/opencost/core/pkg/log"
  9. "github.com/opencost/opencost/core/pkg/source"
  10. "github.com/opencost/opencost/core/pkg/util/promutil"
  11. "github.com/opencost/opencost/modules/collector-source/pkg/event"
  12. "github.com/opencost/opencost/modules/collector-source/pkg/metric"
  13. "github.com/opencost/opencost/modules/collector-source/pkg/util"
  14. "golang.org/x/exp/maps"
  15. v1 "k8s.io/api/core/v1"
  16. "k8s.io/apimachinery/pkg/api/resource"
  17. "k8s.io/apimachinery/pkg/util/validation"
  18. )
  19. type ClusterCacheScraper struct {
  20. clusterCache clustercache.ClusterCache
  21. }
  22. func newClusterCacheScraper(clusterCache clustercache.ClusterCache) Scraper {
  23. return &ClusterCacheScraper{
  24. clusterCache: clusterCache,
  25. }
  26. }
  27. func (ccs *ClusterCacheScraper) Scrape() []metric.Update {
  28. scrapeFuncs := []ScrapeFunc{
  29. ccs.ScrapeNodes,
  30. ccs.ScrapeDeployments,
  31. ccs.ScrapeNamespaces,
  32. ccs.ScrapePods,
  33. ccs.ScrapePVCs,
  34. ccs.ScrapePVs,
  35. ccs.ScrapeServices,
  36. ccs.ScrapeStatefulSets,
  37. ccs.ScrapeReplicaSets,
  38. ccs.ScrapeResourceQuotas,
  39. }
  40. return concurrentScrape(scrapeFuncs...)
  41. }
  42. func (ccs *ClusterCacheScraper) ScrapeNodes() []metric.Update {
  43. nodes := ccs.clusterCache.GetAllNodes()
  44. return ccs.scrapeNodes(nodes)
  45. }
  46. func (ccs *ClusterCacheScraper) scrapeNodes(nodes []*clustercache.Node) []metric.Update {
  47. var scrapeResults []metric.Update
  48. for _, node := range nodes {
  49. nodeInfo := map[string]string{
  50. source.NodeLabel: node.Name,
  51. source.ProviderIDLabel: node.SpecProviderID,
  52. source.UIDLabel: string(node.UID),
  53. }
  54. // Node Capacity
  55. if node.Status.Capacity != nil {
  56. if quantity, ok := node.Status.Capacity[v1.ResourceCPU]; ok {
  57. _, _, value := toResourceUnitValue(v1.ResourceCPU, quantity)
  58. scrapeResults = append(scrapeResults, metric.Update{
  59. Name: metric.KubeNodeStatusCapacityCPUCores,
  60. Labels: nodeInfo,
  61. Value: value,
  62. })
  63. }
  64. if quantity, ok := node.Status.Capacity[v1.ResourceMemory]; ok {
  65. _, _, value := toResourceUnitValue(v1.ResourceMemory, quantity)
  66. scrapeResults = append(scrapeResults, metric.Update{
  67. Name: metric.KubeNodeStatusCapacityMemoryBytes,
  68. Labels: nodeInfo,
  69. Value: value,
  70. })
  71. }
  72. }
  73. // Node Allocatable Resources
  74. if node.Status.Allocatable != nil {
  75. if quantity, ok := node.Status.Allocatable[v1.ResourceCPU]; ok {
  76. _, _, value := toResourceUnitValue(v1.ResourceCPU, quantity)
  77. scrapeResults = append(scrapeResults, metric.Update{
  78. Name: metric.KubeNodeStatusAllocatableCPUCores,
  79. Labels: nodeInfo,
  80. Value: value,
  81. })
  82. }
  83. if quantity, ok := node.Status.Allocatable[v1.ResourceMemory]; ok {
  84. _, _, value := toResourceUnitValue(v1.ResourceMemory, quantity)
  85. scrapeResults = append(scrapeResults, metric.Update{
  86. Name: metric.KubeNodeStatusAllocatableMemoryBytes,
  87. Labels: nodeInfo,
  88. Value: value,
  89. })
  90. }
  91. }
  92. // node labels
  93. labelNames, labelValues := promutil.KubeLabelsToLabels(node.Labels)
  94. nodeLabels := util.ToMap(labelNames, labelValues)
  95. scrapeResults = append(scrapeResults, metric.Update{
  96. Name: metric.KubeNodeLabels,
  97. Labels: nodeInfo,
  98. Value: 0,
  99. AdditionalInfo: nodeLabels,
  100. })
  101. }
  102. events.Dispatch(event.ScrapeEvent{
  103. ScraperName: event.KubernetesClusterScraperName,
  104. ScrapeType: event.NodeScraperType,
  105. Targets: len(nodes),
  106. Errors: nil,
  107. })
  108. return scrapeResults
  109. }
  110. func (ccs *ClusterCacheScraper) ScrapeDeployments() []metric.Update {
  111. deployments := ccs.clusterCache.GetAllDeployments()
  112. return ccs.scrapeDeployments(deployments)
  113. }
  114. func (ccs *ClusterCacheScraper) scrapeDeployments(deployments []*clustercache.Deployment) []metric.Update {
  115. var scrapeResults []metric.Update
  116. for _, deployment := range deployments {
  117. deploymentInfo := map[string]string{
  118. source.DeploymentLabel: deployment.Name,
  119. source.NamespaceLabel: deployment.Namespace,
  120. source.UIDLabel: string(deployment.UID),
  121. }
  122. // deployment labels
  123. labelNames, labelValues := promutil.KubeLabelsToLabels(deployment.MatchLabels)
  124. deploymentLabels := util.ToMap(labelNames, labelValues)
  125. scrapeResults = append(scrapeResults, metric.Update{
  126. Name: metric.DeploymentMatchLabels,
  127. Labels: deploymentInfo,
  128. Value: 0,
  129. AdditionalInfo: deploymentLabels,
  130. })
  131. }
  132. events.Dispatch(event.ScrapeEvent{
  133. ScraperName: event.KubernetesClusterScraperName,
  134. ScrapeType: event.DeploymentScraperType,
  135. Targets: len(deployments),
  136. Errors: nil,
  137. })
  138. return scrapeResults
  139. }
  140. func (ccs *ClusterCacheScraper) ScrapeNamespaces() []metric.Update {
  141. namespaces := ccs.clusterCache.GetAllNamespaces()
  142. return ccs.scrapeNamespaces(namespaces)
  143. }
  144. func (ccs *ClusterCacheScraper) scrapeNamespaces(namespaces []*clustercache.Namespace) []metric.Update {
  145. var scrapeResults []metric.Update
  146. for _, namespace := range namespaces {
  147. namespaceInfo := map[string]string{
  148. source.NamespaceLabel: namespace.Name,
  149. source.UIDLabel: string(namespace.UID),
  150. }
  151. // namespace labels
  152. labelNames, labelValues := promutil.KubeLabelsToLabels(namespace.Labels)
  153. namespaceLabels := util.ToMap(labelNames, labelValues)
  154. scrapeResults = append(scrapeResults, metric.Update{
  155. Name: metric.KubeNamespaceLabels,
  156. Labels: namespaceInfo,
  157. Value: 0,
  158. AdditionalInfo: namespaceLabels,
  159. })
  160. // namespace annotations
  161. annotationNames, annotationValues := promutil.KubeAnnotationsToLabels(namespace.Annotations)
  162. namespaceAnnotations := util.ToMap(annotationNames, annotationValues)
  163. scrapeResults = append(scrapeResults, metric.Update{
  164. Name: metric.KubeNamespaceAnnotations,
  165. Labels: namespaceInfo,
  166. Value: 0,
  167. AdditionalInfo: namespaceAnnotations,
  168. })
  169. }
  170. events.Dispatch(event.ScrapeEvent{
  171. ScraperName: event.KubernetesClusterScraperName,
  172. ScrapeType: event.NamespaceScraperType,
  173. Targets: len(namespaces),
  174. Errors: nil,
  175. })
  176. return scrapeResults
  177. }
  178. func (ccs *ClusterCacheScraper) ScrapePods() []metric.Update {
  179. pods := ccs.clusterCache.GetAllPods()
  180. return ccs.scrapePods(pods)
  181. }
  182. func (ccs *ClusterCacheScraper) scrapePods(pods []*clustercache.Pod) []metric.Update {
  183. var scrapeResults []metric.Update
  184. for _, pod := range pods {
  185. podInfo := map[string]string{
  186. source.PodLabel: pod.Name,
  187. source.NamespaceLabel: pod.Namespace,
  188. source.UIDLabel: string(pod.UID),
  189. source.NodeLabel: pod.Spec.NodeName,
  190. source.InstanceLabel: pod.Spec.NodeName,
  191. }
  192. // pod labels
  193. labelNames, labelValues := promutil.KubeLabelsToLabels(pod.Labels)
  194. podLabels := util.ToMap(labelNames, labelValues)
  195. scrapeResults = append(scrapeResults, metric.Update{
  196. Name: metric.KubePodLabels,
  197. Labels: podInfo,
  198. Value: 0,
  199. AdditionalInfo: podLabels,
  200. })
  201. // pod annotations
  202. annotationNames, annotationValues := promutil.KubeAnnotationsToLabels(pod.Annotations)
  203. podAnnotations := util.ToMap(annotationNames, annotationValues)
  204. scrapeResults = append(scrapeResults, metric.Update{
  205. Name: metric.KubePodAnnotations,
  206. Labels: podInfo,
  207. Value: 0,
  208. AdditionalInfo: podAnnotations,
  209. })
  210. // Pod owner metric
  211. for _, owner := range pod.OwnerReferences {
  212. ownerInfo := maps.Clone(podInfo)
  213. ownerInfo[source.OwnerKindLabel] = owner.Kind
  214. ownerInfo[source.OwnerNameLabel] = owner.Name
  215. scrapeResults = append(scrapeResults, metric.Update{
  216. Name: metric.KubePodOwner,
  217. Labels: ownerInfo,
  218. Value: 0,
  219. })
  220. }
  221. // Container Status
  222. for _, status := range pod.Status.ContainerStatuses {
  223. if status.State.Running != nil {
  224. containerInfo := maps.Clone(podInfo)
  225. containerInfo[source.ContainerLabel] = status.Name
  226. scrapeResults = append(scrapeResults, metric.Update{
  227. Name: metric.KubePodContainerStatusRunning,
  228. Labels: containerInfo,
  229. Value: 0,
  230. })
  231. }
  232. }
  233. for _, container := range pod.Spec.Containers {
  234. containerInfo := maps.Clone(podInfo)
  235. containerInfo[source.ContainerLabel] = container.Name
  236. // Requests
  237. if container.Resources.Requests != nil {
  238. // sorting keys here for testing purposes
  239. keys := maps.Keys(container.Resources.Requests)
  240. slices.Sort(keys)
  241. for _, resourceName := range keys {
  242. quantity := container.Resources.Requests[resourceName]
  243. resource, unit, value := toResourceUnitValue(resourceName, quantity)
  244. // failed to parse the resource type
  245. if resource == "" {
  246. log.DedupedWarningf(5, "Failed to parse resource units and quantity for resource: %s", resourceName)
  247. continue
  248. }
  249. resourceRequestInfo := maps.Clone(containerInfo)
  250. resourceRequestInfo[source.ResourceLabel] = resource
  251. resourceRequestInfo[source.UnitLabel] = unit
  252. scrapeResults = append(scrapeResults, metric.Update{
  253. Name: metric.KubePodContainerResourceRequests,
  254. Labels: resourceRequestInfo,
  255. Value: value,
  256. })
  257. }
  258. }
  259. // Limits
  260. if container.Resources.Limits != nil {
  261. // sorting keys here for testing purposes
  262. keys := maps.Keys(container.Resources.Limits)
  263. slices.Sort(keys)
  264. for _, resourceName := range keys {
  265. quantity := container.Resources.Limits[resourceName]
  266. resource, unit, value := toResourceUnitValue(resourceName, quantity)
  267. // failed to parse the resource type
  268. if resource == "" {
  269. log.DedupedWarningf(5, "Failed to parse resource units and quantity for resource: %s", resourceName)
  270. continue
  271. }
  272. resourceLimitInfo := maps.Clone(containerInfo)
  273. resourceLimitInfo[source.ResourceLabel] = resource
  274. resourceLimitInfo[source.UnitLabel] = unit
  275. scrapeResults = append(scrapeResults, metric.Update{
  276. Name: metric.KubePodContainerResourceLimits,
  277. Labels: resourceLimitInfo,
  278. Value: value,
  279. })
  280. }
  281. }
  282. }
  283. }
  284. events.Dispatch(event.ScrapeEvent{
  285. ScraperName: event.KubernetesClusterScraperName,
  286. ScrapeType: event.PodScraperType,
  287. Targets: len(pods),
  288. Errors: nil,
  289. })
  290. return scrapeResults
  291. }
  292. func (ccs *ClusterCacheScraper) ScrapePVCs() []metric.Update {
  293. pvcs := ccs.clusterCache.GetAllPersistentVolumeClaims()
  294. return ccs.scrapePVCs(pvcs)
  295. }
  296. func (ccs *ClusterCacheScraper) scrapePVCs(pvcs []*clustercache.PersistentVolumeClaim) []metric.Update {
  297. var scrapeResults []metric.Update
  298. for _, pvc := range pvcs {
  299. pvcInfo := map[string]string{
  300. source.PVCLabel: pvc.Name,
  301. source.NamespaceLabel: pvc.Namespace,
  302. source.UIDLabel: string(pvc.UID),
  303. source.VolumeNameLabel: pvc.Spec.VolumeName,
  304. source.StorageClassLabel: getPersistentVolumeClaimClass(pvc),
  305. }
  306. scrapeResults = append(scrapeResults, metric.Update{
  307. Name: metric.KubePersistentVolumeClaimInfo,
  308. Labels: pvcInfo,
  309. Value: 0,
  310. })
  311. if storage, ok := pvc.Spec.Resources.Requests[v1.ResourceStorage]; ok {
  312. scrapeResults = append(scrapeResults, metric.Update{
  313. Name: metric.KubePersistentVolumeClaimResourceRequestsStorageBytes,
  314. Labels: pvcInfo,
  315. Value: float64(storage.Value()),
  316. })
  317. }
  318. }
  319. events.Dispatch(event.ScrapeEvent{
  320. ScraperName: event.KubernetesClusterScraperName,
  321. ScrapeType: event.PvcScraperType,
  322. Targets: len(pvcs),
  323. Errors: nil,
  324. })
  325. return scrapeResults
  326. }
  327. func (ccs *ClusterCacheScraper) ScrapePVs() []metric.Update {
  328. pvs := ccs.clusterCache.GetAllPersistentVolumes()
  329. return ccs.scrapePVs(pvs)
  330. }
  331. func (ccs *ClusterCacheScraper) scrapePVs(pvs []*clustercache.PersistentVolume) []metric.Update {
  332. var scrapeResults []metric.Update
  333. for _, pv := range pvs {
  334. providerID := pv.Name
  335. // if a more accurate provider ID is available, use that
  336. if pv.Spec.CSI != nil && pv.Spec.CSI.VolumeHandle != "" {
  337. providerID = pv.Spec.CSI.VolumeHandle
  338. }
  339. pvInfo := map[string]string{
  340. source.PVLabel: pv.Name,
  341. source.UIDLabel: string(pv.UID),
  342. source.StorageClassLabel: pv.Spec.StorageClassName,
  343. source.ProviderIDLabel: providerID,
  344. }
  345. scrapeResults = append(scrapeResults, metric.Update{
  346. Name: metric.KubecostPVInfo,
  347. Labels: pvInfo,
  348. Value: 0,
  349. })
  350. if storage, ok := pv.Spec.Capacity[v1.ResourceStorage]; ok {
  351. scrapeResults = append(scrapeResults, metric.Update{
  352. Name: metric.KubePersistentVolumeCapacityBytes,
  353. Labels: pvInfo,
  354. Value: float64(storage.Value()),
  355. })
  356. }
  357. }
  358. events.Dispatch(event.ScrapeEvent{
  359. ScraperName: event.KubernetesClusterScraperName,
  360. ScrapeType: event.PvScraperType,
  361. Targets: len(pvs),
  362. Errors: nil,
  363. })
  364. return scrapeResults
  365. }
  366. func (ccs *ClusterCacheScraper) ScrapeServices() []metric.Update {
  367. services := ccs.clusterCache.GetAllServices()
  368. return ccs.scrapeServices(services)
  369. }
  370. func (ccs *ClusterCacheScraper) scrapeServices(services []*clustercache.Service) []metric.Update {
  371. var scrapeResults []metric.Update
  372. for _, service := range services {
  373. serviceInfo := map[string]string{
  374. source.ServiceLabel: service.Name,
  375. source.NamespaceLabel: service.Namespace,
  376. source.UIDLabel: string(service.UID),
  377. }
  378. // service labels
  379. labelNames, labelValues := promutil.KubeLabelsToLabels(service.SpecSelector)
  380. serviceLabels := util.ToMap(labelNames, labelValues)
  381. scrapeResults = append(scrapeResults, metric.Update{
  382. Name: metric.ServiceSelectorLabels,
  383. Labels: serviceInfo,
  384. Value: 0,
  385. AdditionalInfo: serviceLabels,
  386. })
  387. }
  388. events.Dispatch(event.ScrapeEvent{
  389. ScraperName: event.KubernetesClusterScraperName,
  390. ScrapeType: event.ServiceScraperType,
  391. Targets: len(services),
  392. Errors: nil,
  393. })
  394. return scrapeResults
  395. }
  396. func (ccs *ClusterCacheScraper) ScrapeStatefulSets() []metric.Update {
  397. statefulSets := ccs.clusterCache.GetAllStatefulSets()
  398. return ccs.scrapeStatefulSets(statefulSets)
  399. }
  400. func (ccs *ClusterCacheScraper) scrapeStatefulSets(statefulSets []*clustercache.StatefulSet) []metric.Update {
  401. var scrapeResults []metric.Update
  402. for _, statefulSet := range statefulSets {
  403. statefulSetInfo := map[string]string{
  404. source.StatefulSetLabel: statefulSet.Name,
  405. source.NamespaceLabel: statefulSet.Namespace,
  406. source.UIDLabel: string(statefulSet.UID),
  407. }
  408. // statefulSet labels
  409. labelNames, labelValues := promutil.KubeLabelsToLabels(statefulSet.SpecSelector.MatchLabels)
  410. statefulSetLabels := util.ToMap(labelNames, labelValues)
  411. scrapeResults = append(scrapeResults, metric.Update{
  412. Name: metric.StatefulSetMatchLabels,
  413. Labels: statefulSetInfo,
  414. Value: 0,
  415. AdditionalInfo: statefulSetLabels,
  416. })
  417. }
  418. events.Dispatch(event.ScrapeEvent{
  419. ScraperName: event.KubernetesClusterScraperName,
  420. ScrapeType: event.StatefulSetScraperType,
  421. Targets: len(statefulSets),
  422. Errors: nil,
  423. })
  424. return scrapeResults
  425. }
  426. func (ccs *ClusterCacheScraper) ScrapeReplicaSets() []metric.Update {
  427. replicaSets := ccs.clusterCache.GetAllReplicaSets()
  428. return ccs.scrapeReplicaSets(replicaSets)
  429. }
  430. func (ccs *ClusterCacheScraper) scrapeReplicaSets(replicaSets []*clustercache.ReplicaSet) []metric.Update {
  431. var scrapeResults []metric.Update
  432. for _, replicaSet := range replicaSets {
  433. replicaSetInfo := map[string]string{
  434. source.ReplicaSetLabel: replicaSet.Name,
  435. source.NamespaceLabel: replicaSet.Namespace,
  436. source.UIDLabel: string(replicaSet.UID),
  437. }
  438. // this specific metric exports a special <none> value for name and kind
  439. // if there are no owners
  440. if len(replicaSet.OwnerReferences) == 0 {
  441. ownerInfo := maps.Clone(replicaSetInfo)
  442. ownerInfo[source.OwnerKindLabel] = source.NoneLabelValue
  443. ownerInfo[source.OwnerNameLabel] = source.NoneLabelValue
  444. scrapeResults = append(scrapeResults, metric.Update{
  445. Name: metric.KubeReplicasetOwner,
  446. Labels: ownerInfo,
  447. Value: 0,
  448. })
  449. } else {
  450. for _, owner := range replicaSet.OwnerReferences {
  451. ownerInfo := maps.Clone(replicaSetInfo)
  452. ownerInfo[source.OwnerKindLabel] = owner.Kind
  453. ownerInfo[source.OwnerNameLabel] = owner.Name
  454. scrapeResults = append(scrapeResults, metric.Update{
  455. Name: metric.KubeReplicasetOwner,
  456. Labels: ownerInfo,
  457. Value: 0,
  458. })
  459. }
  460. }
  461. }
  462. events.Dispatch(event.ScrapeEvent{
  463. ScraperName: event.KubernetesClusterScraperName,
  464. ScrapeType: event.ReplicaSetScraperType,
  465. Targets: len(replicaSets),
  466. Errors: nil,
  467. })
  468. return scrapeResults
  469. }
  470. func (ccs *ClusterCacheScraper) ScrapeResourceQuotas() []metric.Update {
  471. resourceQuotas := ccs.clusterCache.GetAllResourceQuotas()
  472. return ccs.scrapeResourceQuotas(resourceQuotas)
  473. }
  474. func (ccs *ClusterCacheScraper) scrapeResourceQuotas(resourceQuotas []*clustercache.ResourceQuota) []metric.Update {
  475. var scrapeResults []metric.Update
  476. processResource := func(baseLabels map[string]string, name v1.ResourceName, quantity resource.Quantity, metricName string) metric.Update {
  477. resource, unit, value := toResourceUnitValue(name, quantity)
  478. labels := maps.Clone(baseLabels)
  479. labels[source.ResourceLabel] = resource
  480. labels[source.UnitLabel] = unit
  481. return metric.Update{
  482. Name: metricName,
  483. Labels: labels,
  484. Value: value,
  485. }
  486. }
  487. for _, resourceQuota := range resourceQuotas {
  488. resourceQuotaInfo := map[string]string{
  489. source.ResourceQuotaLabel: resourceQuota.Name,
  490. source.NamespaceLabel: resourceQuota.Namespace,
  491. source.UIDLabel: string(resourceQuota.UID),
  492. }
  493. if resourceQuota.Spec.Hard != nil {
  494. // CPU/memory requests can also be aliased as "cpu" and "memory". For now, however, only scrape the complete names
  495. // https://kubernetes.io/docs/concepts/policy/resource-quotas/#compute-resource-quota
  496. if quantity, ok := resourceQuota.Spec.Hard[v1.ResourceRequestsCPU]; ok {
  497. scrapeResults = append(scrapeResults, processResource(resourceQuotaInfo, v1.ResourceCPU, quantity, metric.KubeResourceQuotaSpecResourceRequests))
  498. }
  499. if quantity, ok := resourceQuota.Spec.Hard[v1.ResourceRequestsMemory]; ok {
  500. scrapeResults = append(scrapeResults, processResource(resourceQuotaInfo, v1.ResourceMemory, quantity, metric.KubeResourceQuotaSpecResourceRequests))
  501. }
  502. if quantity, ok := resourceQuota.Spec.Hard[v1.ResourceLimitsCPU]; ok {
  503. scrapeResults = append(scrapeResults, processResource(resourceQuotaInfo, v1.ResourceCPU, quantity, metric.KubeResourceQuotaSpecResourceLimits))
  504. }
  505. if quantity, ok := resourceQuota.Spec.Hard[v1.ResourceLimitsMemory]; ok {
  506. scrapeResults = append(scrapeResults, processResource(resourceQuotaInfo, v1.ResourceMemory, quantity, metric.KubeResourceQuotaSpecResourceLimits))
  507. }
  508. }
  509. if resourceQuota.Status.Used != nil {
  510. if quantity, ok := resourceQuota.Status.Used[v1.ResourceRequestsCPU]; ok {
  511. scrapeResults = append(scrapeResults, processResource(resourceQuotaInfo, v1.ResourceCPU, quantity, metric.KubeResourceQuotaStatusUsedResourceRequests))
  512. }
  513. if quantity, ok := resourceQuota.Status.Used[v1.ResourceRequestsMemory]; ok {
  514. scrapeResults = append(scrapeResults, processResource(resourceQuotaInfo, v1.ResourceMemory, quantity, metric.KubeResourceQuotaStatusUsedResourceRequests))
  515. }
  516. if quantity, ok := resourceQuota.Status.Used[v1.ResourceLimitsCPU]; ok {
  517. scrapeResults = append(scrapeResults, processResource(resourceQuotaInfo, v1.ResourceCPU, quantity, metric.KubeResourceQuotaStatusUsedResourceLimits))
  518. }
  519. if quantity, ok := resourceQuota.Status.Used[v1.ResourceLimitsMemory]; ok {
  520. scrapeResults = append(scrapeResults, processResource(resourceQuotaInfo, v1.ResourceMemory, quantity, metric.KubeResourceQuotaStatusUsedResourceLimits))
  521. }
  522. }
  523. }
  524. events.Dispatch(event.ScrapeEvent{
  525. ScraperName: event.KubernetesClusterScraperName,
  526. ScrapeType: event.ResourceQuotaScraperType,
  527. Targets: len(resourceQuotas),
  528. Errors: nil,
  529. })
  530. return scrapeResults
  531. }
  532. // getPersistentVolumeClaimClass returns StorageClassName. If no storage class was
  533. // requested, it returns "".
  534. func getPersistentVolumeClaimClass(claim *clustercache.PersistentVolumeClaim) string {
  535. // Use beta annotation first
  536. if class, found := claim.Annotations[v1.BetaStorageClassAnnotation]; found {
  537. return class
  538. }
  539. if claim.Spec.StorageClassName != nil {
  540. return *claim.Spec.StorageClassName
  541. }
  542. // Special non-empty string to indicate absence of storage class.
  543. return ""
  544. }
  545. // toResourceUnitValue accepts a resource name and quantity and returns the sanitized resource, the unit, and the value in the units.
  546. // Returns an empty string for resource and unit if there was a failure.
  547. func toResourceUnitValue(resourceName v1.ResourceName, quantity resource.Quantity) (resource string, unit string, value float64) {
  548. resource = promutil.SanitizeLabelName(string(resourceName))
  549. switch resourceName {
  550. case v1.ResourceCPU:
  551. unit = "core"
  552. value = float64(quantity.MilliValue()) / 1000
  553. return
  554. case v1.ResourceStorage:
  555. fallthrough
  556. case v1.ResourceEphemeralStorage:
  557. fallthrough
  558. case v1.ResourceMemory:
  559. unit = "byte"
  560. value = float64(quantity.Value())
  561. return
  562. case v1.ResourcePods:
  563. unit = "integer"
  564. value = float64(quantity.Value())
  565. return
  566. default:
  567. if isHugePageResourceName(resourceName) || isAttachableVolumeResourceName(resourceName) {
  568. unit = "byte"
  569. value = float64(quantity.Value())
  570. return
  571. }
  572. if isExtendedResourceName(resourceName) {
  573. unit = "integer"
  574. value = float64(quantity.Value())
  575. return
  576. }
  577. }
  578. resource = ""
  579. unit = ""
  580. value = 0.0
  581. return
  582. }
  583. // isHugePageResourceName checks for a huge page container resource name
  584. func isHugePageResourceName(name v1.ResourceName) bool {
  585. return strings.HasPrefix(string(name), v1.ResourceHugePagesPrefix)
  586. }
  587. // isAttachableVolumeResourceName checks for attached volume container resource name
  588. func isAttachableVolumeResourceName(name v1.ResourceName) bool {
  589. return strings.HasPrefix(string(name), v1.ResourceAttachableVolumesPrefix)
  590. }
  591. // isExtendedResourceName checks for extended container resource name
  592. func isExtendedResourceName(name v1.ResourceName) bool {
  593. if isNativeResource(name) || strings.HasPrefix(string(name), v1.DefaultResourceRequestsPrefix) {
  594. return false
  595. }
  596. // Ensure it satisfies the rules in IsQualifiedName() after converted into quota resource name
  597. nameForQuota := fmt.Sprintf("%s%s", v1.DefaultResourceRequestsPrefix, string(name))
  598. if errs := validation.IsQualifiedName(nameForQuota); len(errs) != 0 {
  599. return false
  600. }
  601. return true
  602. }
  603. // isNativeResource checks for a kubernetes.io/ prefixed resource name
  604. func isNativeResource(name v1.ResourceName) bool {
  605. return !strings.Contains(string(name), "/") || isPrefixedNativeResource(name)
  606. }
  607. func isPrefixedNativeResource(name v1.ResourceName) bool {
  608. return strings.Contains(string(name), v1.ResourceDefaultNamespacePrefix)
  609. }