clustercache.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  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. scrapeResults = append(scrapeResults, metric.Update{
  152. Name: metric.NamespaceInfo,
  153. Labels: namespaceInfo,
  154. AdditionalInfo: namespaceInfo,
  155. Value: 0,
  156. })
  157. // namespace labels
  158. labelNames, labelValues := promutil.KubeLabelsToLabels(namespace.Labels)
  159. namespaceLabels := util.ToMap(labelNames, labelValues)
  160. scrapeResults = append(scrapeResults, metric.Update{
  161. Name: metric.KubeNamespaceLabels,
  162. Labels: namespaceInfo,
  163. Value: 0,
  164. AdditionalInfo: namespaceLabels,
  165. })
  166. // namespace annotations
  167. annotationNames, annotationValues := promutil.KubeAnnotationsToLabels(namespace.Annotations)
  168. namespaceAnnotations := util.ToMap(annotationNames, annotationValues)
  169. scrapeResults = append(scrapeResults, metric.Update{
  170. Name: metric.KubeNamespaceAnnotations,
  171. Labels: namespaceInfo,
  172. Value: 0,
  173. AdditionalInfo: namespaceAnnotations,
  174. })
  175. }
  176. events.Dispatch(event.ScrapeEvent{
  177. ScraperName: event.KubernetesClusterScraperName,
  178. ScrapeType: event.NamespaceScraperType,
  179. Targets: len(namespaces),
  180. Errors: nil,
  181. })
  182. return scrapeResults
  183. }
  184. func (ccs *ClusterCacheScraper) ScrapePods() []metric.Update {
  185. pods := ccs.clusterCache.GetAllPods()
  186. return ccs.scrapePods(pods)
  187. }
  188. func (ccs *ClusterCacheScraper) scrapePods(pods []*clustercache.Pod) []metric.Update {
  189. var scrapeResults []metric.Update
  190. for _, pod := range pods {
  191. podInfo := map[string]string{
  192. source.PodLabel: pod.Name,
  193. source.NamespaceLabel: pod.Namespace,
  194. source.UIDLabel: string(pod.UID),
  195. source.NodeLabel: pod.Spec.NodeName,
  196. source.InstanceLabel: pod.Spec.NodeName,
  197. }
  198. // pod labels
  199. labelNames, labelValues := promutil.KubeLabelsToLabels(pod.Labels)
  200. podLabels := util.ToMap(labelNames, labelValues)
  201. scrapeResults = append(scrapeResults, metric.Update{
  202. Name: metric.KubePodLabels,
  203. Labels: podInfo,
  204. Value: 0,
  205. AdditionalInfo: podLabels,
  206. })
  207. // pod annotations
  208. annotationNames, annotationValues := promutil.KubeAnnotationsToLabels(pod.Annotations)
  209. podAnnotations := util.ToMap(annotationNames, annotationValues)
  210. scrapeResults = append(scrapeResults, metric.Update{
  211. Name: metric.KubePodAnnotations,
  212. Labels: podInfo,
  213. Value: 0,
  214. AdditionalInfo: podAnnotations,
  215. })
  216. // Pod owner metric
  217. for _, owner := range pod.OwnerReferences {
  218. ownerInfo := maps.Clone(podInfo)
  219. ownerInfo[source.OwnerKindLabel] = owner.Kind
  220. ownerInfo[source.OwnerNameLabel] = owner.Name
  221. scrapeResults = append(scrapeResults, metric.Update{
  222. Name: metric.KubePodOwner,
  223. Labels: ownerInfo,
  224. Value: 0,
  225. })
  226. }
  227. // Container Status
  228. for _, status := range pod.Status.ContainerStatuses {
  229. if status.State.Running != nil {
  230. containerInfo := maps.Clone(podInfo)
  231. containerInfo[source.ContainerLabel] = status.Name
  232. scrapeResults = append(scrapeResults, metric.Update{
  233. Name: metric.KubePodContainerStatusRunning,
  234. Labels: containerInfo,
  235. Value: 0,
  236. })
  237. }
  238. }
  239. for _, container := range pod.Spec.Containers {
  240. containerInfo := maps.Clone(podInfo)
  241. containerInfo[source.ContainerLabel] = container.Name
  242. // Requests
  243. if container.Resources.Requests != nil {
  244. // sorting keys here for testing purposes
  245. keys := maps.Keys(container.Resources.Requests)
  246. slices.Sort(keys)
  247. for _, resourceName := range keys {
  248. quantity := container.Resources.Requests[resourceName]
  249. resource, unit, value := toResourceUnitValue(resourceName, quantity)
  250. // failed to parse the resource type
  251. if resource == "" {
  252. log.DedupedWarningf(5, "Failed to parse resource units and quantity for resource: %s", resourceName)
  253. continue
  254. }
  255. resourceRequestInfo := maps.Clone(containerInfo)
  256. resourceRequestInfo[source.ResourceLabel] = resource
  257. resourceRequestInfo[source.UnitLabel] = unit
  258. scrapeResults = append(scrapeResults, metric.Update{
  259. Name: metric.KubePodContainerResourceRequests,
  260. Labels: resourceRequestInfo,
  261. Value: value,
  262. })
  263. }
  264. }
  265. // Limits
  266. if container.Resources.Limits != nil {
  267. // sorting keys here for testing purposes
  268. keys := maps.Keys(container.Resources.Limits)
  269. slices.Sort(keys)
  270. for _, resourceName := range keys {
  271. quantity := container.Resources.Limits[resourceName]
  272. resource, unit, value := toResourceUnitValue(resourceName, quantity)
  273. // failed to parse the resource type
  274. if resource == "" {
  275. log.DedupedWarningf(5, "Failed to parse resource units and quantity for resource: %s", resourceName)
  276. continue
  277. }
  278. resourceLimitInfo := maps.Clone(containerInfo)
  279. resourceLimitInfo[source.ResourceLabel] = resource
  280. resourceLimitInfo[source.UnitLabel] = unit
  281. scrapeResults = append(scrapeResults, metric.Update{
  282. Name: metric.KubePodContainerResourceLimits,
  283. Labels: resourceLimitInfo,
  284. Value: value,
  285. })
  286. }
  287. }
  288. }
  289. }
  290. events.Dispatch(event.ScrapeEvent{
  291. ScraperName: event.KubernetesClusterScraperName,
  292. ScrapeType: event.PodScraperType,
  293. Targets: len(pods),
  294. Errors: nil,
  295. })
  296. return scrapeResults
  297. }
  298. func (ccs *ClusterCacheScraper) ScrapePVCs() []metric.Update {
  299. pvcs := ccs.clusterCache.GetAllPersistentVolumeClaims()
  300. return ccs.scrapePVCs(pvcs)
  301. }
  302. func (ccs *ClusterCacheScraper) scrapePVCs(pvcs []*clustercache.PersistentVolumeClaim) []metric.Update {
  303. var scrapeResults []metric.Update
  304. for _, pvc := range pvcs {
  305. pvcInfo := map[string]string{
  306. source.PVCLabel: pvc.Name,
  307. source.NamespaceLabel: pvc.Namespace,
  308. source.UIDLabel: string(pvc.UID),
  309. source.VolumeNameLabel: pvc.Spec.VolumeName,
  310. source.StorageClassLabel: getPersistentVolumeClaimClass(pvc),
  311. }
  312. scrapeResults = append(scrapeResults, metric.Update{
  313. Name: metric.KubePersistentVolumeClaimInfo,
  314. Labels: pvcInfo,
  315. Value: 0,
  316. })
  317. if storage, ok := pvc.Spec.Resources.Requests[v1.ResourceStorage]; ok {
  318. scrapeResults = append(scrapeResults, metric.Update{
  319. Name: metric.KubePersistentVolumeClaimResourceRequestsStorageBytes,
  320. Labels: pvcInfo,
  321. Value: float64(storage.Value()),
  322. })
  323. }
  324. }
  325. events.Dispatch(event.ScrapeEvent{
  326. ScraperName: event.KubernetesClusterScraperName,
  327. ScrapeType: event.PvcScraperType,
  328. Targets: len(pvcs),
  329. Errors: nil,
  330. })
  331. return scrapeResults
  332. }
  333. func (ccs *ClusterCacheScraper) ScrapePVs() []metric.Update {
  334. pvs := ccs.clusterCache.GetAllPersistentVolumes()
  335. return ccs.scrapePVs(pvs)
  336. }
  337. func (ccs *ClusterCacheScraper) scrapePVs(pvs []*clustercache.PersistentVolume) []metric.Update {
  338. var scrapeResults []metric.Update
  339. for _, pv := range pvs {
  340. providerID := pv.Name
  341. // if a more accurate provider ID is available, use that
  342. if pv.Spec.CSI != nil && pv.Spec.CSI.VolumeHandle != "" {
  343. providerID = pv.Spec.CSI.VolumeHandle
  344. }
  345. pvInfo := map[string]string{
  346. source.PVLabel: pv.Name,
  347. source.UIDLabel: string(pv.UID),
  348. source.StorageClassLabel: pv.Spec.StorageClassName,
  349. source.ProviderIDLabel: providerID,
  350. }
  351. scrapeResults = append(scrapeResults, metric.Update{
  352. Name: metric.KubecostPVInfo,
  353. Labels: pvInfo,
  354. Value: 0,
  355. })
  356. if storage, ok := pv.Spec.Capacity[v1.ResourceStorage]; ok {
  357. scrapeResults = append(scrapeResults, metric.Update{
  358. Name: metric.KubePersistentVolumeCapacityBytes,
  359. Labels: pvInfo,
  360. Value: float64(storage.Value()),
  361. })
  362. }
  363. }
  364. events.Dispatch(event.ScrapeEvent{
  365. ScraperName: event.KubernetesClusterScraperName,
  366. ScrapeType: event.PvScraperType,
  367. Targets: len(pvs),
  368. Errors: nil,
  369. })
  370. return scrapeResults
  371. }
  372. func (ccs *ClusterCacheScraper) ScrapeServices() []metric.Update {
  373. services := ccs.clusterCache.GetAllServices()
  374. return ccs.scrapeServices(services)
  375. }
  376. func (ccs *ClusterCacheScraper) scrapeServices(services []*clustercache.Service) []metric.Update {
  377. var scrapeResults []metric.Update
  378. for _, service := range services {
  379. serviceInfo := map[string]string{
  380. source.ServiceLabel: service.Name,
  381. source.NamespaceLabel: service.Namespace,
  382. source.UIDLabel: string(service.UID),
  383. }
  384. // service labels
  385. labelNames, labelValues := promutil.KubeLabelsToLabels(service.SpecSelector)
  386. serviceLabels := util.ToMap(labelNames, labelValues)
  387. scrapeResults = append(scrapeResults, metric.Update{
  388. Name: metric.ServiceSelectorLabels,
  389. Labels: serviceInfo,
  390. Value: 0,
  391. AdditionalInfo: serviceLabels,
  392. })
  393. }
  394. events.Dispatch(event.ScrapeEvent{
  395. ScraperName: event.KubernetesClusterScraperName,
  396. ScrapeType: event.ServiceScraperType,
  397. Targets: len(services),
  398. Errors: nil,
  399. })
  400. return scrapeResults
  401. }
  402. func (ccs *ClusterCacheScraper) ScrapeStatefulSets() []metric.Update {
  403. statefulSets := ccs.clusterCache.GetAllStatefulSets()
  404. return ccs.scrapeStatefulSets(statefulSets)
  405. }
  406. func (ccs *ClusterCacheScraper) scrapeStatefulSets(statefulSets []*clustercache.StatefulSet) []metric.Update {
  407. var scrapeResults []metric.Update
  408. for _, statefulSet := range statefulSets {
  409. statefulSetInfo := map[string]string{
  410. source.StatefulSetLabel: statefulSet.Name,
  411. source.NamespaceLabel: statefulSet.Namespace,
  412. source.UIDLabel: string(statefulSet.UID),
  413. }
  414. // statefulSet labels
  415. labelNames, labelValues := promutil.KubeLabelsToLabels(statefulSet.SpecSelector.MatchLabels)
  416. statefulSetLabels := util.ToMap(labelNames, labelValues)
  417. scrapeResults = append(scrapeResults, metric.Update{
  418. Name: metric.StatefulSetMatchLabels,
  419. Labels: statefulSetInfo,
  420. Value: 0,
  421. AdditionalInfo: statefulSetLabels,
  422. })
  423. }
  424. events.Dispatch(event.ScrapeEvent{
  425. ScraperName: event.KubernetesClusterScraperName,
  426. ScrapeType: event.StatefulSetScraperType,
  427. Targets: len(statefulSets),
  428. Errors: nil,
  429. })
  430. return scrapeResults
  431. }
  432. func (ccs *ClusterCacheScraper) ScrapeReplicaSets() []metric.Update {
  433. replicaSets := ccs.clusterCache.GetAllReplicaSets()
  434. return ccs.scrapeReplicaSets(replicaSets)
  435. }
  436. func (ccs *ClusterCacheScraper) scrapeReplicaSets(replicaSets []*clustercache.ReplicaSet) []metric.Update {
  437. var scrapeResults []metric.Update
  438. for _, replicaSet := range replicaSets {
  439. replicaSetInfo := map[string]string{
  440. source.ReplicaSetLabel: replicaSet.Name,
  441. source.NamespaceLabel: replicaSet.Namespace,
  442. source.UIDLabel: string(replicaSet.UID),
  443. }
  444. // this specific metric exports a special <none> value for name and kind
  445. // if there are no owners
  446. if len(replicaSet.OwnerReferences) == 0 {
  447. ownerInfo := maps.Clone(replicaSetInfo)
  448. ownerInfo[source.OwnerKindLabel] = source.NoneLabelValue
  449. ownerInfo[source.OwnerNameLabel] = source.NoneLabelValue
  450. scrapeResults = append(scrapeResults, metric.Update{
  451. Name: metric.KubeReplicasetOwner,
  452. Labels: ownerInfo,
  453. Value: 0,
  454. })
  455. } else {
  456. for _, owner := range replicaSet.OwnerReferences {
  457. ownerInfo := maps.Clone(replicaSetInfo)
  458. ownerInfo[source.OwnerKindLabel] = owner.Kind
  459. ownerInfo[source.OwnerNameLabel] = owner.Name
  460. scrapeResults = append(scrapeResults, metric.Update{
  461. Name: metric.KubeReplicasetOwner,
  462. Labels: ownerInfo,
  463. Value: 0,
  464. })
  465. }
  466. }
  467. }
  468. events.Dispatch(event.ScrapeEvent{
  469. ScraperName: event.KubernetesClusterScraperName,
  470. ScrapeType: event.ReplicaSetScraperType,
  471. Targets: len(replicaSets),
  472. Errors: nil,
  473. })
  474. return scrapeResults
  475. }
  476. func (ccs *ClusterCacheScraper) ScrapeResourceQuotas() []metric.Update {
  477. resourceQuotas := ccs.clusterCache.GetAllResourceQuotas()
  478. return ccs.scrapeResourceQuotas(resourceQuotas)
  479. }
  480. func (ccs *ClusterCacheScraper) scrapeResourceQuotas(resourceQuotas []*clustercache.ResourceQuota) []metric.Update {
  481. var scrapeResults []metric.Update
  482. processResource := func(baseLabels map[string]string, name v1.ResourceName, quantity resource.Quantity, metricName string) metric.Update {
  483. resource, unit, value := toResourceUnitValue(name, quantity)
  484. labels := maps.Clone(baseLabels)
  485. labels[source.ResourceLabel] = resource
  486. labels[source.UnitLabel] = unit
  487. return metric.Update{
  488. Name: metricName,
  489. Labels: labels,
  490. Value: value,
  491. }
  492. }
  493. for _, resourceQuota := range resourceQuotas {
  494. resourceQuotaInfo := map[string]string{
  495. source.ResourceQuotaLabel: resourceQuota.Name,
  496. source.NamespaceLabel: resourceQuota.Namespace,
  497. source.UIDLabel: string(resourceQuota.UID),
  498. }
  499. scrapeResults = append(scrapeResults, metric.Update{
  500. Name: metric.ResourceQuotaInfo,
  501. Labels: resourceQuotaInfo,
  502. AdditionalInfo: resourceQuotaInfo,
  503. Value: 0,
  504. })
  505. if resourceQuota.Spec.Hard != nil {
  506. // CPU/memory requests can also be aliased as "cpu" and "memory". For now, however, only scrape the complete names
  507. // https://kubernetes.io/docs/concepts/policy/resource-quotas/#compute-resource-quota
  508. if quantity, ok := resourceQuota.Spec.Hard[v1.ResourceRequestsCPU]; ok {
  509. scrapeResults = append(scrapeResults, processResource(resourceQuotaInfo, v1.ResourceCPU, quantity, metric.KubeResourceQuotaSpecResourceRequests))
  510. }
  511. if quantity, ok := resourceQuota.Spec.Hard[v1.ResourceRequestsMemory]; ok {
  512. scrapeResults = append(scrapeResults, processResource(resourceQuotaInfo, v1.ResourceMemory, quantity, metric.KubeResourceQuotaSpecResourceRequests))
  513. }
  514. if quantity, ok := resourceQuota.Spec.Hard[v1.ResourceLimitsCPU]; ok {
  515. scrapeResults = append(scrapeResults, processResource(resourceQuotaInfo, v1.ResourceCPU, quantity, metric.KubeResourceQuotaSpecResourceLimits))
  516. }
  517. if quantity, ok := resourceQuota.Spec.Hard[v1.ResourceLimitsMemory]; ok {
  518. scrapeResults = append(scrapeResults, processResource(resourceQuotaInfo, v1.ResourceMemory, quantity, metric.KubeResourceQuotaSpecResourceLimits))
  519. }
  520. }
  521. if resourceQuota.Status.Used != nil {
  522. if quantity, ok := resourceQuota.Status.Used[v1.ResourceRequestsCPU]; ok {
  523. scrapeResults = append(scrapeResults, processResource(resourceQuotaInfo, v1.ResourceCPU, quantity, metric.KubeResourceQuotaStatusUsedResourceRequests))
  524. }
  525. if quantity, ok := resourceQuota.Status.Used[v1.ResourceRequestsMemory]; ok {
  526. scrapeResults = append(scrapeResults, processResource(resourceQuotaInfo, v1.ResourceMemory, quantity, metric.KubeResourceQuotaStatusUsedResourceRequests))
  527. }
  528. if quantity, ok := resourceQuota.Status.Used[v1.ResourceLimitsCPU]; ok {
  529. scrapeResults = append(scrapeResults, processResource(resourceQuotaInfo, v1.ResourceCPU, quantity, metric.KubeResourceQuotaStatusUsedResourceLimits))
  530. }
  531. if quantity, ok := resourceQuota.Status.Used[v1.ResourceLimitsMemory]; ok {
  532. scrapeResults = append(scrapeResults, processResource(resourceQuotaInfo, v1.ResourceMemory, quantity, metric.KubeResourceQuotaStatusUsedResourceLimits))
  533. }
  534. }
  535. }
  536. events.Dispatch(event.ScrapeEvent{
  537. ScraperName: event.KubernetesClusterScraperName,
  538. ScrapeType: event.ResourceQuotaScraperType,
  539. Targets: len(resourceQuotas),
  540. Errors: nil,
  541. })
  542. return scrapeResults
  543. }
  544. // getPersistentVolumeClaimClass returns StorageClassName. If no storage class was
  545. // requested, it returns "".
  546. func getPersistentVolumeClaimClass(claim *clustercache.PersistentVolumeClaim) string {
  547. // Use beta annotation first
  548. if class, found := claim.Annotations[v1.BetaStorageClassAnnotation]; found {
  549. return class
  550. }
  551. if claim.Spec.StorageClassName != nil {
  552. return *claim.Spec.StorageClassName
  553. }
  554. // Special non-empty string to indicate absence of storage class.
  555. return ""
  556. }
  557. // toResourceUnitValue accepts a resource name and quantity and returns the sanitized resource, the unit, and the value in the units.
  558. // Returns an empty string for resource and unit if there was a failure.
  559. func toResourceUnitValue(resourceName v1.ResourceName, quantity resource.Quantity) (resource string, unit string, value float64) {
  560. resource = promutil.SanitizeLabelName(string(resourceName))
  561. switch resourceName {
  562. case v1.ResourceCPU:
  563. unit = "core"
  564. value = float64(quantity.MilliValue()) / 1000
  565. return
  566. case v1.ResourceStorage:
  567. fallthrough
  568. case v1.ResourceEphemeralStorage:
  569. fallthrough
  570. case v1.ResourceMemory:
  571. unit = "byte"
  572. value = float64(quantity.Value())
  573. return
  574. case v1.ResourcePods:
  575. unit = "integer"
  576. value = float64(quantity.Value())
  577. return
  578. default:
  579. if isHugePageResourceName(resourceName) || isAttachableVolumeResourceName(resourceName) {
  580. unit = "byte"
  581. value = float64(quantity.Value())
  582. return
  583. }
  584. if isExtendedResourceName(resourceName) {
  585. unit = "integer"
  586. value = float64(quantity.Value())
  587. return
  588. }
  589. }
  590. resource = ""
  591. unit = ""
  592. value = 0.0
  593. return
  594. }
  595. // isHugePageResourceName checks for a huge page container resource name
  596. func isHugePageResourceName(name v1.ResourceName) bool {
  597. return strings.HasPrefix(string(name), v1.ResourceHugePagesPrefix)
  598. }
  599. // isAttachableVolumeResourceName checks for attached volume container resource name
  600. func isAttachableVolumeResourceName(name v1.ResourceName) bool {
  601. return strings.HasPrefix(string(name), v1.ResourceAttachableVolumesPrefix)
  602. }
  603. // isExtendedResourceName checks for extended container resource name
  604. func isExtendedResourceName(name v1.ResourceName) bool {
  605. if isNativeResource(name) || strings.HasPrefix(string(name), v1.DefaultResourceRequestsPrefix) {
  606. return false
  607. }
  608. // Ensure it satisfies the rules in IsQualifiedName() after converted into quota resource name
  609. nameForQuota := fmt.Sprintf("%s%s", v1.DefaultResourceRequestsPrefix, string(name))
  610. if errs := validation.IsQualifiedName(nameForQuota); len(errs) != 0 {
  611. return false
  612. }
  613. return true
  614. }
  615. // isNativeResource checks for a kubernetes.io/ prefixed resource name
  616. func isNativeResource(name v1.ResourceName) bool {
  617. return !strings.Contains(string(name), "/") || isPrefixedNativeResource(name)
  618. }
  619. func isPrefixedNativeResource(name v1.ResourceName) bool {
  620. return strings.Contains(string(name), v1.ResourceDefaultNamespacePrefix)
  621. }