clustercache.go 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206
  1. package scrape
  2. import (
  3. "fmt"
  4. "slices"
  5. "strings"
  6. "sync"
  7. "github.com/kubecost/events"
  8. "github.com/opencost/opencost/core/pkg/clustercache"
  9. "github.com/opencost/opencost/core/pkg/log"
  10. "github.com/opencost/opencost/core/pkg/source"
  11. coreutil "github.com/opencost/opencost/core/pkg/util"
  12. "github.com/opencost/opencost/core/pkg/util/promutil"
  13. "github.com/opencost/opencost/modules/collector-source/pkg/event"
  14. "github.com/opencost/opencost/modules/collector-source/pkg/metric"
  15. "github.com/opencost/opencost/modules/collector-source/pkg/util"
  16. "golang.org/x/exp/maps"
  17. v1 "k8s.io/api/core/v1"
  18. "k8s.io/apimachinery/pkg/api/resource"
  19. "k8s.io/apimachinery/pkg/types"
  20. "k8s.io/apimachinery/pkg/util/validation"
  21. )
  22. // SyncMap provides thread-safe concurrent access to a generic map
  23. type SyncMap[U comparable, T any] struct {
  24. mu sync.RWMutex
  25. data map[U]T
  26. }
  27. // newSyncMap creates a new thread-safe map with the specified initial capacity
  28. func newSyncMap[U comparable, T any](size int) *SyncMap[U, T] {
  29. return &SyncMap[U, T]{
  30. data: make(map[U]T, size),
  31. }
  32. }
  33. // Set adds or updates a key-value mapping
  34. func (sm *SyncMap[U, T]) Set(key U, value T) {
  35. sm.mu.Lock()
  36. defer sm.mu.Unlock()
  37. sm.data[key] = value
  38. }
  39. // Get retrieves a value by key. Returns the value and a boolean indicating if it was found.
  40. func (sm *SyncMap[U, T]) Get(key U) (T, bool) {
  41. sm.mu.RLock()
  42. defer sm.mu.RUnlock()
  43. value, ok := sm.data[key]
  44. return value, ok
  45. }
  46. type ClusterCacheScraper struct {
  47. clusterCache clustercache.ClusterCache
  48. }
  49. func newClusterCacheScraper(clusterCache clustercache.ClusterCache) Scraper {
  50. return &ClusterCacheScraper{
  51. clusterCache: clusterCache,
  52. }
  53. }
  54. type pvcKey struct {
  55. name string
  56. namespace string
  57. }
  58. func (ccs *ClusterCacheScraper) Scrape() []metric.Update {
  59. // retrieve objects for scrape
  60. nodes := ccs.clusterCache.GetAllNodes()
  61. deployments := ccs.clusterCache.GetAllDeployments()
  62. namespaces := ccs.clusterCache.GetAllNamespaces()
  63. pods := ccs.clusterCache.GetAllPods()
  64. pvcs := ccs.clusterCache.GetAllPersistentVolumeClaims()
  65. pvs := ccs.clusterCache.GetAllPersistentVolumes()
  66. services := ccs.clusterCache.GetAllServices()
  67. statefulSets := ccs.clusterCache.GetAllStatefulSets()
  68. daemonSets := ccs.clusterCache.GetAllDaemonSets()
  69. jobs := ccs.clusterCache.GetAllJobs()
  70. cronJobs := ccs.clusterCache.GetAllCronJobs()
  71. replicaSets := ccs.clusterCache.GetAllReplicaSets()
  72. resourceQuotas := ccs.clusterCache.GetAllResourceQuotas()
  73. // create scrape indexes. While the pairs being mapped here don't have a 1 to 1 relationship in the general case,
  74. // we are assuming that in the context of a single snapshot of the cluster they are 1 to 1.
  75. nodeNameToUID := newSyncMap[string, types.UID](len(nodes))
  76. for _, node := range nodes {
  77. nodeNameToUID.Set(node.Name, node.UID)
  78. }
  79. namespaceNameToUID := newSyncMap[string, types.UID](len(namespaces))
  80. for _, ns := range namespaces {
  81. namespaceNameToUID.Set(ns.Name, ns.UID)
  82. }
  83. pvcNameToUID := newSyncMap[pvcKey, types.UID](len(pvcs))
  84. for _, pvc := range pvcs {
  85. pvcNameToUID.Set(pvcKey{
  86. name: pvc.Name,
  87. namespace: pvc.Namespace,
  88. }, pvc.UID)
  89. }
  90. pvNameToUID := newSyncMap[string, types.UID](len(pvs))
  91. for _, pv := range pvs {
  92. pvNameToUID.Set(pv.Name, pv.UID)
  93. }
  94. scrapeFuncs := []ScrapeFunc{
  95. ccs.GetScrapeNodes(nodes),
  96. ccs.GetScrapeDeployments(deployments, namespaceNameToUID),
  97. ccs.GetScrapeNamespaces(namespaces),
  98. ccs.GetScrapePods(pods, nodeNameToUID, namespaceNameToUID, pvcNameToUID),
  99. ccs.GetScrapePVCs(pvcs, namespaceNameToUID, pvNameToUID),
  100. ccs.GetScrapePVs(pvs),
  101. ccs.GetScrapeServices(services),
  102. ccs.GetScrapeStatefulSets(statefulSets, namespaceNameToUID),
  103. ccs.GetScrapeDaemonSets(daemonSets, namespaceNameToUID),
  104. ccs.GetScrapeJobs(jobs, namespaceNameToUID),
  105. ccs.GetScrapeCronJobs(cronJobs, namespaceNameToUID),
  106. ccs.GetScrapeReplicaSets(replicaSets, namespaceNameToUID),
  107. ccs.GetScrapeResourceQuotas(resourceQuotas, namespaceNameToUID),
  108. }
  109. return concurrentScrape(scrapeFuncs...)
  110. }
  111. func (ccs *ClusterCacheScraper) GetScrapeNodes(nodes []*clustercache.Node) ScrapeFunc {
  112. return func() []metric.Update {
  113. return ccs.scrapeNodes(nodes)
  114. }
  115. }
  116. func (ccs *ClusterCacheScraper) scrapeNodes(nodes []*clustercache.Node) []metric.Update {
  117. var scrapeResults []metric.Update
  118. for _, node := range nodes {
  119. nodeInfo := map[string]string{
  120. source.NodeLabel: node.Name,
  121. source.ProviderIDLabel: node.SpecProviderID,
  122. source.UIDLabel: string(node.UID),
  123. }
  124. if instanceType, ok := coreutil.GetInstanceType(node.Labels); ok {
  125. nodeInfo[source.InstanceTypeLabel] = instanceType
  126. }
  127. scrapeResults = append(scrapeResults, metric.Update{
  128. Name: metric.NodeInfo,
  129. Labels: nodeInfo,
  130. AdditionalInfo: nodeInfo,
  131. })
  132. // Node Capacity
  133. scrapeResults = scrapeResourceList(
  134. metric.NodeResourceCapacities,
  135. node.Status.Capacity,
  136. nodeInfo,
  137. scrapeResults)
  138. // This block and metric can be removed, when we stop exporting assets and allocations
  139. if node.Status.Capacity != nil {
  140. if quantity, ok := node.Status.Capacity[v1.ResourceCPU]; ok {
  141. _, _, value := toResourceUnitValue(v1.ResourceCPU, quantity)
  142. scrapeResults = append(scrapeResults, metric.Update{
  143. Name: metric.KubeNodeStatusCapacityCPUCores,
  144. Labels: nodeInfo,
  145. Value: value,
  146. })
  147. }
  148. if quantity, ok := node.Status.Capacity[v1.ResourceMemory]; ok {
  149. _, _, value := toResourceUnitValue(v1.ResourceMemory, quantity)
  150. scrapeResults = append(scrapeResults, metric.Update{
  151. Name: metric.KubeNodeStatusCapacityMemoryBytes,
  152. Labels: nodeInfo,
  153. Value: value,
  154. })
  155. }
  156. }
  157. // Node Allocatable Resources
  158. scrapeResults = scrapeResourceList(
  159. metric.NodeResourcesAllocatable,
  160. node.Status.Allocatable,
  161. nodeInfo,
  162. scrapeResults)
  163. // This block and metric can be removed, when we stop exporting assets and allocations
  164. if node.Status.Allocatable != nil {
  165. if quantity, ok := node.Status.Allocatable[v1.ResourceCPU]; ok {
  166. _, _, value := toResourceUnitValue(v1.ResourceCPU, quantity)
  167. scrapeResults = append(scrapeResults, metric.Update{
  168. Name: metric.KubeNodeStatusAllocatableCPUCores,
  169. Labels: nodeInfo,
  170. Value: value,
  171. })
  172. }
  173. if quantity, ok := node.Status.Allocatable[v1.ResourceMemory]; ok {
  174. _, _, value := toResourceUnitValue(v1.ResourceMemory, quantity)
  175. scrapeResults = append(scrapeResults, metric.Update{
  176. Name: metric.KubeNodeStatusAllocatableMemoryBytes,
  177. Labels: nodeInfo,
  178. Value: value,
  179. })
  180. }
  181. }
  182. // node labels
  183. labelNames, labelValues := promutil.KubeLabelsToLabels(node.Labels)
  184. nodeLabels := util.ToMap(labelNames, labelValues)
  185. scrapeResults = append(scrapeResults, metric.Update{
  186. Name: metric.KubeNodeLabels,
  187. Labels: nodeInfo,
  188. Value: 0,
  189. AdditionalInfo: nodeLabels,
  190. })
  191. }
  192. events.Dispatch(event.ScrapeEvent{
  193. ScraperName: event.KubernetesClusterScraperName,
  194. ScrapeType: event.NodeScraperType,
  195. Targets: len(nodes),
  196. Errors: nil,
  197. })
  198. return scrapeResults
  199. }
  200. func (ccs *ClusterCacheScraper) GetScrapeDeployments(deployments []*clustercache.Deployment, namespaceIndex *SyncMap[string, types.UID]) ScrapeFunc {
  201. return func() []metric.Update {
  202. return ccs.scrapeDeployments(deployments, namespaceIndex)
  203. }
  204. }
  205. func (ccs *ClusterCacheScraper) scrapeDeployments(deployments []*clustercache.Deployment, namespaceIndex *SyncMap[string, types.UID]) []metric.Update {
  206. var scrapeResults []metric.Update
  207. for _, deployment := range deployments {
  208. nsUID, ok := namespaceIndex.Get(deployment.Namespace)
  209. if !ok {
  210. log.Debugf("deployment namespaceUID missing from index for namespace name '%s'", deployment.Namespace)
  211. }
  212. deploymentInfo := map[string]string{
  213. source.UIDLabel: string(deployment.UID),
  214. source.NamespaceUIDLabel: string(nsUID),
  215. source.NamespaceLabel: deployment.Namespace,
  216. source.DeploymentLabel: deployment.Name,
  217. }
  218. scrapeResults = append(scrapeResults, metric.Update{
  219. Name: metric.DeploymentInfo,
  220. Labels: deploymentInfo,
  221. Value: 0,
  222. AdditionalInfo: deploymentInfo,
  223. })
  224. // deployment labels
  225. labelNames, labelValues := promutil.KubeLabelsToLabels(deployment.Labels)
  226. deploymentLabels := util.ToMap(labelNames, labelValues)
  227. scrapeResults = append(scrapeResults, metric.Update{
  228. Name: metric.DeploymentLabels,
  229. Labels: deploymentInfo,
  230. Value: 0,
  231. AdditionalInfo: deploymentLabels,
  232. })
  233. // deployment annotations
  234. annoationNames, annotationValues := promutil.KubeAnnotationsToLabels(deployment.Annotations)
  235. deploymentAnnotations := util.ToMap(annoationNames, annotationValues)
  236. scrapeResults = append(scrapeResults, metric.Update{
  237. Name: metric.DeploymentAnnotations,
  238. Labels: deploymentInfo,
  239. Value: 0,
  240. AdditionalInfo: deploymentAnnotations,
  241. })
  242. // deployment match labels
  243. matchLabelNames, matchLabelValues := promutil.KubeLabelsToLabels(deployment.MatchLabels)
  244. deploymentMatchLabels := util.ToMap(matchLabelNames, matchLabelValues)
  245. scrapeResults = append(scrapeResults, metric.Update{
  246. Name: metric.DeploymentMatchLabels,
  247. Labels: deploymentInfo,
  248. Value: 0,
  249. AdditionalInfo: deploymentMatchLabels,
  250. })
  251. }
  252. events.Dispatch(event.ScrapeEvent{
  253. ScraperName: event.KubernetesClusterScraperName,
  254. ScrapeType: event.DeploymentScraperType,
  255. Targets: len(deployments),
  256. Errors: nil,
  257. })
  258. return scrapeResults
  259. }
  260. func (ccs *ClusterCacheScraper) GetScrapeNamespaces(namespaces []*clustercache.Namespace) ScrapeFunc {
  261. return func() []metric.Update {
  262. return ccs.scrapeNamespaces(namespaces)
  263. }
  264. }
  265. func (ccs *ClusterCacheScraper) scrapeNamespaces(namespaces []*clustercache.Namespace) []metric.Update {
  266. var scrapeResults []metric.Update
  267. for _, namespace := range namespaces {
  268. namespaceInfo := map[string]string{
  269. source.NamespaceLabel: namespace.Name,
  270. source.UIDLabel: string(namespace.UID),
  271. }
  272. scrapeResults = append(scrapeResults, metric.Update{
  273. Name: metric.NamespaceInfo,
  274. Labels: namespaceInfo,
  275. AdditionalInfo: namespaceInfo,
  276. Value: 0,
  277. })
  278. // namespace labels
  279. labelNames, labelValues := promutil.KubeLabelsToLabels(namespace.Labels)
  280. namespaceLabels := util.ToMap(labelNames, labelValues)
  281. scrapeResults = append(scrapeResults, metric.Update{
  282. Name: metric.KubeNamespaceLabels,
  283. Labels: namespaceInfo,
  284. Value: 0,
  285. AdditionalInfo: namespaceLabels,
  286. })
  287. // namespace annotations
  288. annotationNames, annotationValues := promutil.KubeAnnotationsToLabels(namespace.Annotations)
  289. namespaceAnnotations := util.ToMap(annotationNames, annotationValues)
  290. scrapeResults = append(scrapeResults, metric.Update{
  291. Name: metric.KubeNamespaceAnnotations,
  292. Labels: namespaceInfo,
  293. Value: 0,
  294. AdditionalInfo: namespaceAnnotations,
  295. })
  296. }
  297. events.Dispatch(event.ScrapeEvent{
  298. ScraperName: event.KubernetesClusterScraperName,
  299. ScrapeType: event.NamespaceScraperType,
  300. Targets: len(namespaces),
  301. Errors: nil,
  302. })
  303. return scrapeResults
  304. }
  305. func (ccs *ClusterCacheScraper) GetScrapePods(
  306. pods []*clustercache.Pod,
  307. nodeIndex *SyncMap[string, types.UID],
  308. namespaceIndex *SyncMap[string, types.UID],
  309. pvcIndex *SyncMap[pvcKey, types.UID],
  310. ) ScrapeFunc {
  311. return func() []metric.Update {
  312. return ccs.scrapePods(pods, nodeIndex, namespaceIndex, pvcIndex)
  313. }
  314. }
  315. func (ccs *ClusterCacheScraper) scrapePods(
  316. pods []*clustercache.Pod,
  317. nodeIndex *SyncMap[string, types.UID],
  318. namespaceIndex *SyncMap[string, types.UID],
  319. pvcIndex *SyncMap[pvcKey, types.UID],
  320. ) []metric.Update {
  321. var scrapeResults []metric.Update
  322. for _, pod := range pods {
  323. nodeUID, ok := nodeIndex.Get(pod.Spec.NodeName)
  324. if !ok {
  325. log.Debugf("pod nodeUID missing from index for node name '%s'", pod.Spec.NodeName)
  326. }
  327. nsUID, ok := namespaceIndex.Get(pod.Namespace)
  328. if !ok {
  329. log.Debugf("pod namespaceUID missing from index for namespace name '%s'", pod.Namespace)
  330. }
  331. podInfo := map[string]string{
  332. source.UIDLabel: string(pod.UID),
  333. source.PodLabel: pod.Name,
  334. source.NamespaceUIDLabel: string(nsUID),
  335. source.NodeUIDLabel: string(nodeUID),
  336. }
  337. scrapeResults = append(scrapeResults, metric.Update{
  338. Name: metric.PodInfo,
  339. Labels: podInfo,
  340. Value: 0,
  341. AdditionalInfo: podInfo,
  342. })
  343. podInfo[source.NamespaceLabel] = pod.Namespace
  344. podInfo[source.NodeLabel] = pod.Spec.NodeName
  345. podInfo[source.InstanceLabel] = pod.Spec.NodeName
  346. // pod labels
  347. labelNames, labelValues := promutil.KubeLabelsToLabels(pod.Labels)
  348. podLabels := util.ToMap(labelNames, labelValues)
  349. scrapeResults = append(scrapeResults, metric.Update{
  350. Name: metric.KubePodLabels,
  351. Labels: podInfo,
  352. Value: 0,
  353. AdditionalInfo: podLabels,
  354. })
  355. // pod annotations
  356. annotationNames, annotationValues := promutil.KubeAnnotationsToLabels(pod.Annotations)
  357. podAnnotations := util.ToMap(annotationNames, annotationValues)
  358. scrapeResults = append(scrapeResults, metric.Update{
  359. Name: metric.KubePodAnnotations,
  360. Labels: podInfo,
  361. Value: 0,
  362. AdditionalInfo: podAnnotations,
  363. })
  364. // Pod owner metric
  365. for _, owner := range pod.OwnerReferences {
  366. controller := "false"
  367. if owner.Controller != nil && *owner.Controller {
  368. controller = "true"
  369. }
  370. ownerInfo := maps.Clone(podInfo)
  371. ownerInfo[source.OwnerKindLabel] = owner.Kind
  372. ownerInfo[source.OwnerNameLabel] = owner.Name
  373. ownerInfo[source.OwnerUIDLabel] = string(owner.UID)
  374. ownerInfo[source.ContainerLabel] = controller
  375. scrapeResults = append(scrapeResults, metric.Update{
  376. Name: metric.KubePodOwner,
  377. Labels: ownerInfo,
  378. Value: 0,
  379. })
  380. }
  381. // Container Status
  382. for _, status := range pod.Status.ContainerStatuses {
  383. if status.State.Running != nil {
  384. containerInfo := maps.Clone(podInfo)
  385. containerInfo[source.ContainerLabel] = status.Name
  386. scrapeResults = append(scrapeResults, metric.Update{
  387. Name: metric.KubePodContainerStatusRunning,
  388. Labels: containerInfo,
  389. AdditionalInfo: containerInfo,
  390. Value: 0,
  391. })
  392. }
  393. }
  394. for _, volume := range pod.Spec.Volumes {
  395. if volume.PersistentVolumeClaim != nil {
  396. pvcUID, _ := pvcIndex.Get(pvcKey{
  397. name: volume.PersistentVolumeClaim.ClaimName,
  398. namespace: pod.Namespace,
  399. })
  400. podPVCVolumeInfo := map[string]string{
  401. source.UIDLabel: string(pod.UID),
  402. source.PVCUIDLabel: string(pvcUID),
  403. source.PodVolumeNameLabel: volume.Name,
  404. }
  405. scrapeResults = append(scrapeResults, metric.Update{
  406. Name: metric.PodPVCVolume,
  407. Labels: podPVCVolumeInfo,
  408. Value: 0,
  409. })
  410. }
  411. }
  412. for _, container := range pod.Spec.Containers {
  413. containerInfo := maps.Clone(podInfo)
  414. containerInfo[source.ContainerLabel] = container.Name
  415. // Requests
  416. scrapeResults = scrapeResourceList(
  417. metric.KubePodContainerResourceRequests,
  418. container.Resources.Requests,
  419. containerInfo,
  420. scrapeResults)
  421. // Limits
  422. scrapeResults = scrapeResourceList(
  423. metric.KubePodContainerResourceLimits,
  424. container.Resources.Limits,
  425. containerInfo,
  426. scrapeResults)
  427. }
  428. }
  429. events.Dispatch(event.ScrapeEvent{
  430. ScraperName: event.KubernetesClusterScraperName,
  431. ScrapeType: event.PodScraperType,
  432. Targets: len(pods),
  433. Errors: nil,
  434. })
  435. return scrapeResults
  436. }
  437. func scrapeResourceList(metricName string, resourceList v1.ResourceList, baseLabels map[string]string, scrapeResults []metric.Update) []metric.Update {
  438. if resourceList != nil {
  439. // sorting keys here for testing purposes
  440. keys := maps.Keys(resourceList)
  441. slices.Sort(keys)
  442. for _, resourceName := range keys {
  443. quantity := resourceList[resourceName]
  444. resource, unit, value := toResourceUnitValue(resourceName, quantity)
  445. // failed to parse the resource type
  446. if resource == "" {
  447. log.DedupedWarningf(5, "Failed to parse resource units and quantity for resource: %s", resourceName)
  448. continue
  449. }
  450. resourceRequestInfo := maps.Clone(baseLabels)
  451. resourceRequestInfo[source.ResourceLabel] = resource
  452. resourceRequestInfo[source.UnitLabel] = unit
  453. scrapeResults = append(scrapeResults, metric.Update{
  454. Name: metricName,
  455. Labels: resourceRequestInfo,
  456. Value: value,
  457. })
  458. }
  459. }
  460. return scrapeResults
  461. }
  462. func (ccs *ClusterCacheScraper) GetScrapePVCs(
  463. pvcs []*clustercache.PersistentVolumeClaim,
  464. namespaceIndex *SyncMap[string, types.UID],
  465. pvIndex *SyncMap[string, types.UID],
  466. ) ScrapeFunc {
  467. return func() []metric.Update {
  468. return ccs.scrapePVCs(pvcs, namespaceIndex, pvIndex)
  469. }
  470. }
  471. func (ccs *ClusterCacheScraper) scrapePVCs(
  472. pvcs []*clustercache.PersistentVolumeClaim,
  473. namespaceIndex *SyncMap[string, types.UID],
  474. pvIndex *SyncMap[string, types.UID],
  475. ) []metric.Update {
  476. var scrapeResults []metric.Update
  477. for _, pvc := range pvcs {
  478. nsUID, ok := namespaceIndex.Get(pvc.Namespace)
  479. if !ok {
  480. log.Debugf("pvc namespaceUID missing from index for namespace name '%s'", pvc.Namespace)
  481. }
  482. pvUID, ok := pvIndex.Get(pvc.Spec.VolumeName)
  483. if !ok && pvc.Spec.VolumeName != "" {
  484. log.Debugf("pvc volume name missing from index for pv name '%s'", pvc.Spec.VolumeName)
  485. }
  486. pvcInfo := map[string]string{
  487. source.UIDLabel: string(pvc.UID),
  488. source.PVCLabel: pvc.Name,
  489. source.NamespaceUIDLabel: string(nsUID),
  490. source.NamespaceLabel: pvc.Namespace,
  491. source.VolumeNameLabel: pvc.Spec.VolumeName,
  492. source.PVUIDLabel: string(pvUID),
  493. source.StorageClassLabel: getPersistentVolumeClaimClass(pvc),
  494. }
  495. scrapeResults = append(scrapeResults, metric.Update{
  496. Name: metric.KubePersistentVolumeClaimInfo,
  497. Labels: pvcInfo,
  498. AdditionalInfo: pvcInfo,
  499. Value: 0,
  500. })
  501. if storage, ok := pvc.Spec.Resources.Requests[v1.ResourceStorage]; ok {
  502. scrapeResults = append(scrapeResults, metric.Update{
  503. Name: metric.KubePersistentVolumeClaimResourceRequestsStorageBytes,
  504. Labels: pvcInfo,
  505. Value: float64(storage.Value()),
  506. })
  507. }
  508. }
  509. events.Dispatch(event.ScrapeEvent{
  510. ScraperName: event.KubernetesClusterScraperName,
  511. ScrapeType: event.PvcScraperType,
  512. Targets: len(pvcs),
  513. Errors: nil,
  514. })
  515. return scrapeResults
  516. }
  517. func (ccs *ClusterCacheScraper) GetScrapePVs(pvs []*clustercache.PersistentVolume) ScrapeFunc {
  518. return func() []metric.Update {
  519. return ccs.scrapePVs(pvs)
  520. }
  521. }
  522. func (ccs *ClusterCacheScraper) scrapePVs(pvs []*clustercache.PersistentVolume) []metric.Update {
  523. var scrapeResults []metric.Update
  524. for _, pv := range pvs {
  525. providerID := pv.Name
  526. var csiVolumeHandle string
  527. // if a more accurate provider ID is available, use that
  528. if pv.Spec.CSI != nil && pv.Spec.CSI.VolumeHandle != "" {
  529. providerID = pv.Spec.CSI.VolumeHandle
  530. csiVolumeHandle = pv.Spec.CSI.VolumeHandle
  531. }
  532. pvInfo := map[string]string{
  533. source.UIDLabel: string(pv.UID),
  534. source.PVLabel: pv.Name,
  535. source.StorageClassLabel: pv.Spec.StorageClassName,
  536. source.ProviderIDLabel: providerID,
  537. source.CSIVolumeHandleLabel: csiVolumeHandle,
  538. }
  539. scrapeResults = append(scrapeResults, metric.Update{
  540. Name: metric.KubecostPVInfo,
  541. Labels: pvInfo,
  542. AdditionalInfo: pvInfo,
  543. Value: 0,
  544. })
  545. if storage, ok := pv.Spec.Capacity[v1.ResourceStorage]; ok {
  546. scrapeResults = append(scrapeResults, metric.Update{
  547. Name: metric.KubePersistentVolumeCapacityBytes,
  548. Labels: pvInfo,
  549. Value: float64(storage.Value()),
  550. })
  551. }
  552. }
  553. events.Dispatch(event.ScrapeEvent{
  554. ScraperName: event.KubernetesClusterScraperName,
  555. ScrapeType: event.PvScraperType,
  556. Targets: len(pvs),
  557. Errors: nil,
  558. })
  559. return scrapeResults
  560. }
  561. func (ccs *ClusterCacheScraper) GetScrapeServices(services []*clustercache.Service) ScrapeFunc {
  562. return func() []metric.Update {
  563. return ccs.scrapeServices(services)
  564. }
  565. }
  566. func (ccs *ClusterCacheScraper) scrapeServices(services []*clustercache.Service) []metric.Update {
  567. var scrapeResults []metric.Update
  568. for _, service := range services {
  569. serviceInfo := map[string]string{
  570. source.UIDLabel: string(service.UID),
  571. source.ServiceLabel: service.Name,
  572. source.NamespaceLabel: service.Namespace,
  573. source.ServiceTypeLabel: string(service.Type),
  574. }
  575. scrapeResults = append(scrapeResults, metric.Update{
  576. Name: metric.ServiceInfo,
  577. Labels: serviceInfo,
  578. Value: 0,
  579. AdditionalInfo: serviceInfo,
  580. })
  581. // service selector labels
  582. selectorNames, selectorValues := promutil.KubeLabelsToLabels(service.SpecSelector)
  583. serviceLabels := util.ToMap(selectorNames, selectorValues)
  584. scrapeResults = append(scrapeResults, metric.Update{
  585. Name: metric.ServiceSelectorLabels,
  586. Labels: serviceInfo,
  587. Value: 0,
  588. AdditionalInfo: serviceLabels,
  589. })
  590. }
  591. events.Dispatch(event.ScrapeEvent{
  592. ScraperName: event.KubernetesClusterScraperName,
  593. ScrapeType: event.ServiceScraperType,
  594. Targets: len(services),
  595. Errors: nil,
  596. })
  597. return scrapeResults
  598. }
  599. func (ccs *ClusterCacheScraper) GetScrapeStatefulSets(statefulSets []*clustercache.StatefulSet, namespaceIndex *SyncMap[string, types.UID]) ScrapeFunc {
  600. return func() []metric.Update {
  601. return ccs.scrapeStatefulSets(statefulSets, namespaceIndex)
  602. }
  603. }
  604. func (ccs *ClusterCacheScraper) scrapeStatefulSets(statefulSets []*clustercache.StatefulSet, namespaceIndex *SyncMap[string, types.UID]) []metric.Update {
  605. var scrapeResults []metric.Update
  606. for _, statefulSet := range statefulSets {
  607. nsUID, ok := namespaceIndex.Get(statefulSet.Namespace)
  608. if !ok {
  609. log.Debugf("statefulSet namespaceUID missing from index for namespace name '%s'", statefulSet.Namespace)
  610. }
  611. statefulSetInfo := map[string]string{
  612. source.UIDLabel: string(statefulSet.UID),
  613. source.NamespaceUIDLabel: string(nsUID),
  614. source.StatefulSetLabel: statefulSet.Name,
  615. }
  616. // statefulSet info
  617. scrapeResults = append(scrapeResults, metric.Update{
  618. Name: metric.StatefulSetInfo,
  619. Labels: statefulSetInfo,
  620. Value: 0,
  621. AdditionalInfo: statefulSetInfo,
  622. })
  623. // statefulSet labels
  624. labelNames, labelValues := promutil.KubeLabelsToLabels(statefulSet.Labels)
  625. statefulSetLabels := util.ToMap(labelNames, labelValues)
  626. scrapeResults = append(scrapeResults, metric.Update{
  627. Name: metric.StatefulSetLabels,
  628. Labels: statefulSetInfo,
  629. Value: 0,
  630. AdditionalInfo: statefulSetLabels,
  631. })
  632. // statefulSet annotations
  633. annotationNames, annotationValues := promutil.KubeAnnotationsToLabels(statefulSet.Annotations)
  634. statefulSetAnnotations := util.ToMap(annotationNames, annotationValues)
  635. scrapeResults = append(scrapeResults, metric.Update{
  636. Name: metric.StatefulSetAnnotations,
  637. Labels: statefulSetInfo,
  638. Value: 0,
  639. AdditionalInfo: statefulSetAnnotations,
  640. })
  641. // statefulSet match labels
  642. statefulSetInfo[source.NamespaceLabel] = statefulSet.Namespace
  643. matchLabelNames, matchLabelValues := promutil.KubeLabelsToLabels(statefulSet.SpecSelector.MatchLabels)
  644. statefulSetMatchLabels := util.ToMap(matchLabelNames, matchLabelValues)
  645. scrapeResults = append(scrapeResults, metric.Update{
  646. Name: metric.StatefulSetMatchLabels,
  647. Labels: statefulSetInfo,
  648. Value: 0,
  649. AdditionalInfo: statefulSetMatchLabels,
  650. })
  651. }
  652. events.Dispatch(event.ScrapeEvent{
  653. ScraperName: event.KubernetesClusterScraperName,
  654. ScrapeType: event.StatefulSetScraperType,
  655. Targets: len(statefulSets),
  656. Errors: nil,
  657. })
  658. return scrapeResults
  659. }
  660. func (ccs *ClusterCacheScraper) GetScrapeDaemonSets(daemonSets []*clustercache.DaemonSet, namespaceIndex *SyncMap[string, types.UID]) ScrapeFunc {
  661. return func() []metric.Update {
  662. return ccs.scrapeDaemonSets(daemonSets, namespaceIndex)
  663. }
  664. }
  665. func (ccs *ClusterCacheScraper) scrapeDaemonSets(daemonSets []*clustercache.DaemonSet, namespaceIndex *SyncMap[string, types.UID]) []metric.Update {
  666. var scrapeResults []metric.Update
  667. for _, daemonSet := range daemonSets {
  668. nsUID, ok := namespaceIndex.Get(daemonSet.Namespace)
  669. if !ok {
  670. log.Debugf("daemonSet namespaceUID missing from index for namespace name '%s'", daemonSet.Namespace)
  671. }
  672. daemonSetInfo := map[string]string{
  673. source.UIDLabel: string(daemonSet.UID),
  674. source.NamespaceUIDLabel: string(nsUID),
  675. source.DaemonSetLabel: daemonSet.Name,
  676. }
  677. // daemonSet info
  678. scrapeResults = append(scrapeResults, metric.Update{
  679. Name: metric.DaemonSetInfo,
  680. Labels: daemonSetInfo,
  681. Value: 0,
  682. AdditionalInfo: daemonSetInfo,
  683. })
  684. // daemonSet labels
  685. labelNames, labelValues := promutil.KubeLabelsToLabels(daemonSet.Labels)
  686. daemonSetLabels := util.ToMap(labelNames, labelValues)
  687. scrapeResults = append(scrapeResults, metric.Update{
  688. Name: metric.DaemonSetLabels,
  689. Labels: daemonSetInfo,
  690. Value: 0,
  691. AdditionalInfo: daemonSetLabels,
  692. })
  693. // daemonSet annotations
  694. annotationNames, annotationValues := promutil.KubeAnnotationsToLabels(daemonSet.Annotations)
  695. daemonSetAnnotations := util.ToMap(annotationNames, annotationValues)
  696. scrapeResults = append(scrapeResults, metric.Update{
  697. Name: metric.DaemonSetAnnotations,
  698. Labels: daemonSetInfo,
  699. Value: 0,
  700. AdditionalInfo: daemonSetAnnotations,
  701. })
  702. }
  703. events.Dispatch(event.ScrapeEvent{
  704. ScraperName: event.KubernetesClusterScraperName,
  705. ScrapeType: event.DaemonSetScraperType,
  706. Targets: len(daemonSets),
  707. Errors: nil,
  708. })
  709. return scrapeResults
  710. }
  711. func (ccs *ClusterCacheScraper) GetScrapeJobs(jobs []*clustercache.Job, namespaceIndex *SyncMap[string, types.UID]) ScrapeFunc {
  712. return func() []metric.Update {
  713. return ccs.scrapeJobs(jobs, namespaceIndex)
  714. }
  715. }
  716. func (ccs *ClusterCacheScraper) scrapeJobs(jobs []*clustercache.Job, namespaceIndex *SyncMap[string, types.UID]) []metric.Update {
  717. var scrapeResults []metric.Update
  718. for _, job := range jobs {
  719. nsUID, ok := namespaceIndex.Get(job.Namespace)
  720. if !ok {
  721. log.Debugf("job namespaceUID missing from index for namespace name '%s'", job.Namespace)
  722. }
  723. jobInfo := map[string]string{
  724. source.UIDLabel: string(job.UID),
  725. source.NamespaceUIDLabel: string(nsUID),
  726. source.JobLabel: job.Name,
  727. }
  728. // job info
  729. scrapeResults = append(scrapeResults, metric.Update{
  730. Name: metric.JobInfo,
  731. Labels: jobInfo,
  732. Value: 0,
  733. AdditionalInfo: jobInfo,
  734. })
  735. // job labels
  736. labelNames, labelValues := promutil.KubeLabelsToLabels(job.Labels)
  737. jobLabels := util.ToMap(labelNames, labelValues)
  738. scrapeResults = append(scrapeResults, metric.Update{
  739. Name: metric.JobLabels,
  740. Labels: jobInfo,
  741. Value: 0,
  742. AdditionalInfo: jobLabels,
  743. })
  744. // job annotations
  745. annotationNames, annotationValues := promutil.KubeAnnotationsToLabels(job.Annotations)
  746. jobAnnotations := util.ToMap(annotationNames, annotationValues)
  747. scrapeResults = append(scrapeResults, metric.Update{
  748. Name: metric.JobAnnotations,
  749. Labels: jobInfo,
  750. Value: 0,
  751. AdditionalInfo: jobAnnotations,
  752. })
  753. }
  754. events.Dispatch(event.ScrapeEvent{
  755. ScraperName: event.KubernetesClusterScraperName,
  756. ScrapeType: event.JobScraperType,
  757. Targets: len(jobs),
  758. Errors: nil,
  759. })
  760. return scrapeResults
  761. }
  762. func (ccs *ClusterCacheScraper) GetScrapeCronJobs(cronJobs []*clustercache.CronJob, namespaceIndex *SyncMap[string, types.UID]) ScrapeFunc {
  763. return func() []metric.Update {
  764. return ccs.scrapeCronJobs(cronJobs, namespaceIndex)
  765. }
  766. }
  767. func (ccs *ClusterCacheScraper) scrapeCronJobs(cronJobs []*clustercache.CronJob, namespaceIndex *SyncMap[string, types.UID]) []metric.Update {
  768. var scrapeResults []metric.Update
  769. for _, cronJob := range cronJobs {
  770. nsUID, ok := namespaceIndex.Get(cronJob.Namespace)
  771. if !ok {
  772. log.Debugf("cronjob namespaceUID missing from index for namespace name '%s'", cronJob.Namespace)
  773. }
  774. cronJobInfo := map[string]string{
  775. source.UIDLabel: string(cronJob.UID),
  776. source.NamespaceUIDLabel: string(nsUID),
  777. source.CronJobLabel: cronJob.Name,
  778. }
  779. // cronjob info
  780. scrapeResults = append(scrapeResults, metric.Update{
  781. Name: metric.CronJobInfo,
  782. Labels: cronJobInfo,
  783. Value: 0,
  784. AdditionalInfo: cronJobInfo,
  785. })
  786. // cronjob labels
  787. labelNames, labelValues := promutil.KubeLabelsToLabels(cronJob.Labels)
  788. cronJobLabels := util.ToMap(labelNames, labelValues)
  789. scrapeResults = append(scrapeResults, metric.Update{
  790. Name: metric.CronJobLabels,
  791. Labels: cronJobInfo,
  792. Value: 0,
  793. AdditionalInfo: cronJobLabels,
  794. })
  795. // cronjob annotations
  796. annotationNames, annotationValues := promutil.KubeAnnotationsToLabels(cronJob.Annotations)
  797. cronJobAnnotations := util.ToMap(annotationNames, annotationValues)
  798. scrapeResults = append(scrapeResults, metric.Update{
  799. Name: metric.CronJobAnnotations,
  800. Labels: cronJobInfo,
  801. Value: 0,
  802. AdditionalInfo: cronJobAnnotations,
  803. })
  804. }
  805. events.Dispatch(event.ScrapeEvent{
  806. ScraperName: event.KubernetesClusterScraperName,
  807. ScrapeType: event.CronJobScraperType,
  808. Targets: len(cronJobs),
  809. Errors: nil,
  810. })
  811. return scrapeResults
  812. }
  813. func (ccs *ClusterCacheScraper) GetScrapeReplicaSets(replicaSets []*clustercache.ReplicaSet, namespaceIndex *SyncMap[string, types.UID]) ScrapeFunc {
  814. return func() []metric.Update {
  815. return ccs.scrapeReplicaSets(replicaSets, namespaceIndex)
  816. }
  817. }
  818. func (ccs *ClusterCacheScraper) scrapeReplicaSets(replicaSets []*clustercache.ReplicaSet, namespaceIndex *SyncMap[string, types.UID]) []metric.Update {
  819. var scrapeResults []metric.Update
  820. for _, replicaSet := range replicaSets {
  821. nsUID, ok := namespaceIndex.Get(replicaSet.Namespace)
  822. if !ok {
  823. log.Debugf("replicaset namespaceUID missing from index for namespace name '%s'", replicaSet.Namespace)
  824. }
  825. replicaSetInfo := map[string]string{
  826. source.UIDLabel: string(replicaSet.UID),
  827. source.NamespaceUIDLabel: string(nsUID),
  828. source.ReplicaSetLabel: replicaSet.Name,
  829. }
  830. // replicaset info
  831. scrapeResults = append(scrapeResults, metric.Update{
  832. Name: metric.ReplicaSetInfo,
  833. Labels: replicaSetInfo,
  834. Value: 0,
  835. AdditionalInfo: replicaSetInfo,
  836. })
  837. // replicaset labels
  838. labelNames, labelValues := promutil.KubeLabelsToLabels(replicaSet.Labels)
  839. replicaSetLabels := util.ToMap(labelNames, labelValues)
  840. scrapeResults = append(scrapeResults, metric.Update{
  841. Name: metric.ReplicaSetLabels,
  842. Labels: replicaSetInfo,
  843. Value: 0,
  844. AdditionalInfo: replicaSetLabels,
  845. })
  846. // replicaset annotations
  847. annotationNames, annotationValues := promutil.KubeAnnotationsToLabels(replicaSet.Annotations)
  848. replicaSetAnnotations := util.ToMap(annotationNames, annotationValues)
  849. scrapeResults = append(scrapeResults, metric.Update{
  850. Name: metric.ReplicaSetAnnotations,
  851. Labels: replicaSetInfo,
  852. Value: 0,
  853. AdditionalInfo: replicaSetAnnotations,
  854. })
  855. // owner references for backward compatibility
  856. replicaSetOwnerInfo := map[string]string{
  857. source.ReplicaSetLabel: replicaSet.Name,
  858. source.NamespaceLabel: replicaSet.Namespace,
  859. source.UIDLabel: string(replicaSet.UID),
  860. }
  861. // this specific metric exports a special <none> value for name and kind
  862. // if there are no owners
  863. if len(replicaSet.OwnerReferences) == 0 {
  864. ownerInfo := maps.Clone(replicaSetOwnerInfo)
  865. ownerInfo[source.OwnerKindLabel] = source.NoneLabelValue
  866. ownerInfo[source.OwnerNameLabel] = source.NoneLabelValue
  867. scrapeResults = append(scrapeResults, metric.Update{
  868. Name: metric.KubeReplicasetOwner,
  869. Labels: ownerInfo,
  870. Value: 0,
  871. })
  872. } else {
  873. for _, owner := range replicaSet.OwnerReferences {
  874. controller := "false"
  875. if owner.Controller != nil && *owner.Controller {
  876. controller = "true"
  877. }
  878. ownerInfo := maps.Clone(replicaSetOwnerInfo)
  879. ownerInfo[source.OwnerKindLabel] = owner.Kind
  880. ownerInfo[source.OwnerNameLabel] = owner.Name
  881. ownerInfo[source.OwnerUIDLabel] = string(owner.UID)
  882. ownerInfo[source.ControllerLabel] = controller
  883. scrapeResults = append(scrapeResults, metric.Update{
  884. Name: metric.KubeReplicasetOwner,
  885. Labels: ownerInfo,
  886. Value: 0,
  887. })
  888. }
  889. }
  890. }
  891. events.Dispatch(event.ScrapeEvent{
  892. ScraperName: event.KubernetesClusterScraperName,
  893. ScrapeType: event.ReplicaSetScraperType,
  894. Targets: len(replicaSets),
  895. Errors: nil,
  896. })
  897. return scrapeResults
  898. }
  899. func (ccs *ClusterCacheScraper) GetScrapeResourceQuotas(resourceQuotas []*clustercache.ResourceQuota, namespaceIndex *SyncMap[string, types.UID]) ScrapeFunc {
  900. return func() []metric.Update {
  901. return ccs.scrapeResourceQuotas(resourceQuotas, namespaceIndex)
  902. }
  903. }
  904. func (ccs *ClusterCacheScraper) scrapeResourceQuotas(resourceQuotas []*clustercache.ResourceQuota, namespaceIndex *SyncMap[string, types.UID]) []metric.Update {
  905. var scrapeResults []metric.Update
  906. processResource := func(baseLabels map[string]string, name v1.ResourceName, quantity resource.Quantity, metricName string) metric.Update {
  907. resource, unit, value := toResourceUnitValue(name, quantity)
  908. labels := maps.Clone(baseLabels)
  909. labels[source.ResourceLabel] = resource
  910. labels[source.UnitLabel] = unit
  911. return metric.Update{
  912. Name: metricName,
  913. Labels: labels,
  914. Value: value,
  915. }
  916. }
  917. for _, resourceQuota := range resourceQuotas {
  918. nsUID, _ := namespaceIndex.Get(resourceQuota.Namespace)
  919. resourceQuotaInfo := map[string]string{
  920. source.UIDLabel: string(resourceQuota.UID),
  921. source.NamespaceUIDLabel: string(nsUID),
  922. source.ResourceQuotaLabel: resourceQuota.Name,
  923. }
  924. scrapeResults = append(scrapeResults, metric.Update{
  925. Name: metric.ResourceQuotaInfo,
  926. Labels: resourceQuotaInfo,
  927. AdditionalInfo: resourceQuotaInfo,
  928. Value: 0,
  929. })
  930. if resourceQuota.Spec.Hard != nil {
  931. // CPU/memory requests can also be aliased as "cpu" and "memory". For now, however, only scrape the complete names
  932. // https://kubernetes.io/docs/concepts/policy/resource-quotas/#compute-resource-quota
  933. if quantity, ok := resourceQuota.Spec.Hard[v1.ResourceRequestsCPU]; ok {
  934. scrapeResults = append(scrapeResults, processResource(resourceQuotaInfo, v1.ResourceCPU, quantity, metric.KubeResourceQuotaSpecResourceRequests))
  935. }
  936. if quantity, ok := resourceQuota.Spec.Hard[v1.ResourceRequestsMemory]; ok {
  937. scrapeResults = append(scrapeResults, processResource(resourceQuotaInfo, v1.ResourceMemory, quantity, metric.KubeResourceQuotaSpecResourceRequests))
  938. }
  939. if quantity, ok := resourceQuota.Spec.Hard[v1.ResourceLimitsCPU]; ok {
  940. scrapeResults = append(scrapeResults, processResource(resourceQuotaInfo, v1.ResourceCPU, quantity, metric.KubeResourceQuotaSpecResourceLimits))
  941. }
  942. if quantity, ok := resourceQuota.Spec.Hard[v1.ResourceLimitsMemory]; ok {
  943. scrapeResults = append(scrapeResults, processResource(resourceQuotaInfo, v1.ResourceMemory, quantity, metric.KubeResourceQuotaSpecResourceLimits))
  944. }
  945. }
  946. if resourceQuota.Status.Used != nil {
  947. if quantity, ok := resourceQuota.Status.Used[v1.ResourceRequestsCPU]; ok {
  948. scrapeResults = append(scrapeResults, processResource(resourceQuotaInfo, v1.ResourceCPU, quantity, metric.KubeResourceQuotaStatusUsedResourceRequests))
  949. }
  950. if quantity, ok := resourceQuota.Status.Used[v1.ResourceRequestsMemory]; ok {
  951. scrapeResults = append(scrapeResults, processResource(resourceQuotaInfo, v1.ResourceMemory, quantity, metric.KubeResourceQuotaStatusUsedResourceRequests))
  952. }
  953. if quantity, ok := resourceQuota.Status.Used[v1.ResourceLimitsCPU]; ok {
  954. scrapeResults = append(scrapeResults, processResource(resourceQuotaInfo, v1.ResourceCPU, quantity, metric.KubeResourceQuotaStatusUsedResourceLimits))
  955. }
  956. if quantity, ok := resourceQuota.Status.Used[v1.ResourceLimitsMemory]; ok {
  957. scrapeResults = append(scrapeResults, processResource(resourceQuotaInfo, v1.ResourceMemory, quantity, metric.KubeResourceQuotaStatusUsedResourceLimits))
  958. }
  959. }
  960. }
  961. events.Dispatch(event.ScrapeEvent{
  962. ScraperName: event.KubernetesClusterScraperName,
  963. ScrapeType: event.ResourceQuotaScraperType,
  964. Targets: len(resourceQuotas),
  965. Errors: nil,
  966. })
  967. return scrapeResults
  968. }
  969. // getPersistentVolumeClaimClass returns StorageClassName. If no storage class was
  970. // requested, it returns "".
  971. func getPersistentVolumeClaimClass(claim *clustercache.PersistentVolumeClaim) string {
  972. // Use beta annotation first
  973. if class, found := claim.Annotations[v1.BetaStorageClassAnnotation]; found {
  974. return class
  975. }
  976. if claim.Spec.StorageClassName != nil {
  977. return *claim.Spec.StorageClassName
  978. }
  979. // Special non-empty string to indicate absence of storage class.
  980. return ""
  981. }
  982. // toResourceUnitValue accepts a resource name and quantity and returns the sanitized resource, the unit, and the value in the units.
  983. // Returns an empty string for resource and unit if there was a failure.
  984. func toResourceUnitValue(resourceName v1.ResourceName, quantity resource.Quantity) (resource string, unit string, value float64) {
  985. resource = promutil.SanitizeLabelName(string(resourceName))
  986. switch resourceName {
  987. case v1.ResourceCPU:
  988. unit = "core"
  989. value = float64(quantity.MilliValue()) / 1000
  990. return
  991. case v1.ResourceStorage:
  992. fallthrough
  993. case v1.ResourceEphemeralStorage:
  994. fallthrough
  995. case v1.ResourceMemory:
  996. unit = "byte"
  997. value = float64(quantity.Value())
  998. return
  999. case v1.ResourcePods:
  1000. unit = "integer"
  1001. value = float64(quantity.Value())
  1002. return
  1003. default:
  1004. if isHugePageResourceName(resourceName) || isAttachableVolumeResourceName(resourceName) {
  1005. unit = "byte"
  1006. value = float64(quantity.Value())
  1007. return
  1008. }
  1009. if isExtendedResourceName(resourceName) {
  1010. unit = "integer"
  1011. value = float64(quantity.Value())
  1012. return
  1013. }
  1014. }
  1015. resource = ""
  1016. unit = ""
  1017. value = 0.0
  1018. return
  1019. }
  1020. // isHugePageResourceName checks for a huge page container resource name
  1021. func isHugePageResourceName(name v1.ResourceName) bool {
  1022. return strings.HasPrefix(string(name), v1.ResourceHugePagesPrefix)
  1023. }
  1024. // isAttachableVolumeResourceName checks for attached volume container resource name
  1025. func isAttachableVolumeResourceName(name v1.ResourceName) bool {
  1026. return strings.HasPrefix(string(name), v1.ResourceAttachableVolumesPrefix)
  1027. }
  1028. // isExtendedResourceName checks for extended container resource name
  1029. func isExtendedResourceName(name v1.ResourceName) bool {
  1030. if isNativeResource(name) || strings.HasPrefix(string(name), v1.DefaultResourceRequestsPrefix) {
  1031. return false
  1032. }
  1033. // Ensure it satisfies the rules in IsQualifiedName() after converted into quota resource name
  1034. nameForQuota := fmt.Sprintf("%s%s", v1.DefaultResourceRequestsPrefix, string(name))
  1035. if errs := validation.IsQualifiedName(nameForQuota); len(errs) != 0 {
  1036. return false
  1037. }
  1038. return true
  1039. }
  1040. // isNativeResource checks for a kubernetes.io/ prefixed resource name
  1041. func isNativeResource(name v1.ResourceName) bool {
  1042. return !strings.Contains(string(name), "/") || isPrefixedNativeResource(name)
  1043. }
  1044. func isPrefixedNativeResource(name v1.ResourceName) bool {
  1045. return strings.Contains(string(name), v1.ResourceDefaultNamespacePrefix)
  1046. }