metrics.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727
  1. package costmodel
  2. import (
  3. "math"
  4. "regexp"
  5. "sort"
  6. "strconv"
  7. "strings"
  8. "sync"
  9. "time"
  10. costAnalyzerCloud "github.com/kubecost/cost-model/pkg/cloud"
  11. "github.com/kubecost/cost-model/pkg/errors"
  12. "github.com/kubecost/cost-model/pkg/log"
  13. "github.com/kubecost/cost-model/pkg/prom"
  14. "github.com/prometheus/client_golang/prometheus"
  15. dto "github.com/prometheus/client_model/go"
  16. v1 "k8s.io/api/core/v1"
  17. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  18. "k8s.io/client-go/kubernetes"
  19. "k8s.io/klog"
  20. )
  21. var (
  22. invalidLabelCharRE = regexp.MustCompile(`[^a-zA-Z0-9_]`)
  23. )
  24. //--------------------------------------------------------------------------
  25. // StatefulsetCollector
  26. //--------------------------------------------------------------------------
  27. // StatefulsetCollector is a prometheus collector that generates StatefulsetMetrics
  28. type StatefulsetCollector struct {
  29. KubeClientSet kubernetes.Interface
  30. }
  31. // Describe sends the super-set of all possible descriptors of metrics
  32. // collected by this Collector.
  33. func (sc StatefulsetCollector) Describe(ch chan<- *prometheus.Desc) {
  34. ch <- prometheus.NewDesc("statefulSet_match_labels", "statfulSet match labels", []string{}, nil)
  35. }
  36. // Collect is called by the Prometheus registry when collecting metrics.
  37. func (sc StatefulsetCollector) Collect(ch chan<- prometheus.Metric) {
  38. ds, _ := sc.KubeClientSet.AppsV1().StatefulSets("").List(metav1.ListOptions{})
  39. for _, statefulset := range ds.Items {
  40. labels, values := kubeLabelsToPrometheusLabels(statefulset.Spec.Selector.MatchLabels)
  41. m := newStatefulsetMetric(statefulset.GetName(), statefulset.GetNamespace(), "statefulSet_match_labels", labels, values)
  42. ch <- m
  43. }
  44. }
  45. //--------------------------------------------------------------------------
  46. // StatefulsetMetric
  47. //--------------------------------------------------------------------------
  48. // StatefulsetMetric is a prometheus.Metric used to encode statefulset match labels
  49. type StatefulsetMetric struct {
  50. fqName string
  51. help string
  52. labelNames []string
  53. labelValues []string
  54. statefulsetName string
  55. namespace string
  56. }
  57. // Creates a new StatefulsetMetric, implementation of prometheus.Metric
  58. func newStatefulsetMetric(name, namespace, fqname string, labelNames []string, labelvalues []string) StatefulsetMetric {
  59. return StatefulsetMetric{
  60. fqName: fqname,
  61. labelNames: labelNames,
  62. labelValues: labelvalues,
  63. help: "statefulSet_match_labels StatefulSet Match Labels",
  64. statefulsetName: name,
  65. namespace: namespace,
  66. }
  67. }
  68. // Desc returns the descriptor for the Metric. This method idempotently
  69. // returns the same descriptor throughout the lifetime of the Metric.
  70. func (s StatefulsetMetric) Desc() *prometheus.Desc {
  71. l := prometheus.Labels{"statefulSet": s.statefulsetName, "namespace": s.namespace}
  72. return prometheus.NewDesc(s.fqName, s.help, s.labelNames, l)
  73. }
  74. // Write encodes the Metric into a "Metric" Protocol Buffer data
  75. // transmission object.
  76. func (s StatefulsetMetric) Write(m *dto.Metric) error {
  77. h := float64(1)
  78. m.Gauge = &dto.Gauge{
  79. Value: &h,
  80. }
  81. var labels []*dto.LabelPair
  82. for i := range s.labelNames {
  83. labels = append(labels, &dto.LabelPair{
  84. Name: &s.labelNames[i],
  85. Value: &s.labelValues[i],
  86. })
  87. }
  88. n := "namespace"
  89. labels = append(labels, &dto.LabelPair{
  90. Name: &n,
  91. Value: &s.namespace,
  92. })
  93. r := "statefulSet"
  94. labels = append(labels, &dto.LabelPair{
  95. Name: &r,
  96. Value: &s.statefulsetName,
  97. })
  98. m.Label = labels
  99. return nil
  100. }
  101. //--------------------------------------------------------------------------
  102. // DeploymentCollector
  103. //--------------------------------------------------------------------------
  104. // DeploymentCollector is a prometheus collector that generates DeploymentMetrics
  105. type DeploymentCollector struct {
  106. KubeClientSet kubernetes.Interface
  107. }
  108. // Describe sends the super-set of all possible descriptors of metrics
  109. // collected by this Collector.
  110. func (sc DeploymentCollector) Describe(ch chan<- *prometheus.Desc) {
  111. ch <- prometheus.NewDesc("deployment_match_labels", "deployment match labels", []string{}, nil)
  112. }
  113. // Collect is called by the Prometheus registry when collecting metrics.
  114. func (sc DeploymentCollector) Collect(ch chan<- prometheus.Metric) {
  115. ds, _ := sc.KubeClientSet.AppsV1().Deployments("").List(metav1.ListOptions{})
  116. for _, deployment := range ds.Items {
  117. labels, values := kubeLabelsToPrometheusLabels(deployment.Spec.Selector.MatchLabels)
  118. m := newDeploymentMetric(deployment.GetName(), deployment.GetNamespace(), "deployment_match_labels", labels, values)
  119. ch <- m
  120. }
  121. }
  122. //--------------------------------------------------------------------------
  123. // DeploymentMetric
  124. //--------------------------------------------------------------------------
  125. // DeploymentMetric is a prometheus.Metric used to encode deployment match labels
  126. type DeploymentMetric struct {
  127. fqName string
  128. help string
  129. labelNames []string
  130. labelValues []string
  131. deploymentName string
  132. namespace string
  133. }
  134. // Creates a new DeploymentMetric, implementation of prometheus.Metric
  135. func newDeploymentMetric(name, namespace, fqname string, labelNames []string, labelvalues []string) DeploymentMetric {
  136. return DeploymentMetric{
  137. fqName: fqname,
  138. labelNames: labelNames,
  139. labelValues: labelvalues,
  140. help: "deployment_match_labels Deployment Match Labels",
  141. deploymentName: name,
  142. namespace: namespace,
  143. }
  144. }
  145. // Desc returns the descriptor for the Metric. This method idempotently
  146. // returns the same descriptor throughout the lifetime of the Metric.
  147. func (s DeploymentMetric) Desc() *prometheus.Desc {
  148. l := prometheus.Labels{"deployment": s.deploymentName, "namespace": s.namespace}
  149. return prometheus.NewDesc(s.fqName, s.help, s.labelNames, l)
  150. }
  151. // Write encodes the Metric into a "Metric" Protocol Buffer data
  152. // transmission object.
  153. func (s DeploymentMetric) Write(m *dto.Metric) error {
  154. h := float64(1)
  155. m.Gauge = &dto.Gauge{
  156. Value: &h,
  157. }
  158. var labels []*dto.LabelPair
  159. for i := range s.labelNames {
  160. labels = append(labels, &dto.LabelPair{
  161. Name: &s.labelNames[i],
  162. Value: &s.labelValues[i],
  163. })
  164. }
  165. n := "namespace"
  166. labels = append(labels, &dto.LabelPair{
  167. Name: &n,
  168. Value: &s.namespace,
  169. })
  170. r := "deployment"
  171. labels = append(labels, &dto.LabelPair{
  172. Name: &r,
  173. Value: &s.deploymentName,
  174. })
  175. m.Label = labels
  176. return nil
  177. }
  178. //--------------------------------------------------------------------------
  179. // ServiceCollector
  180. //--------------------------------------------------------------------------
  181. // ServiceCollector is a prometheus collector that generates ServiceMetrics
  182. type ServiceCollector struct {
  183. KubeClientSet kubernetes.Interface
  184. }
  185. // Describe sends the super-set of all possible descriptors of metrics
  186. // collected by this Collector.
  187. func (sc ServiceCollector) Describe(ch chan<- *prometheus.Desc) {
  188. ch <- prometheus.NewDesc("service_selector_labels", "service selector labels", []string{}, nil)
  189. }
  190. // Collect is called by the Prometheus registry when collecting metrics.
  191. func (sc ServiceCollector) Collect(ch chan<- prometheus.Metric) {
  192. svcs, _ := sc.KubeClientSet.CoreV1().Services("").List(metav1.ListOptions{})
  193. for _, svc := range svcs.Items {
  194. labels, values := kubeLabelsToPrometheusLabels(svc.Spec.Selector)
  195. m := newServiceMetric(svc.GetName(), svc.GetNamespace(), "service_selector_labels", labels, values)
  196. ch <- m
  197. }
  198. }
  199. //--------------------------------------------------------------------------
  200. // ServiceMetric
  201. //--------------------------------------------------------------------------
  202. // ServiceMetric is a prometheus.Metric used to encode service selector labels
  203. type ServiceMetric struct {
  204. fqName string
  205. help string
  206. labelNames []string
  207. labelValues []string
  208. serviceName string
  209. namespace string
  210. }
  211. // Creates a new ServiceMetric, implementation of prometheus.Metric
  212. func newServiceMetric(name, namespace, fqname string, labelNames []string, labelvalues []string) ServiceMetric {
  213. return ServiceMetric{
  214. fqName: fqname,
  215. labelNames: labelNames,
  216. labelValues: labelvalues,
  217. help: "service_selector_labels Service Selector Labels",
  218. serviceName: name,
  219. namespace: namespace,
  220. }
  221. }
  222. // Desc returns the descriptor for the Metric. This method idempotently
  223. // returns the same descriptor throughout the lifetime of the Metric.
  224. func (s ServiceMetric) Desc() *prometheus.Desc {
  225. l := prometheus.Labels{"service": s.serviceName, "namespace": s.namespace}
  226. return prometheus.NewDesc(s.fqName, s.help, s.labelNames, l)
  227. }
  228. // Write encodes the Metric into a "Metric" Protocol Buffer data
  229. // transmission object.
  230. func (s ServiceMetric) Write(m *dto.Metric) error {
  231. h := float64(1)
  232. m.Gauge = &dto.Gauge{
  233. Value: &h,
  234. }
  235. var labels []*dto.LabelPair
  236. for i := range s.labelNames {
  237. labels = append(labels, &dto.LabelPair{
  238. Name: &s.labelNames[i],
  239. Value: &s.labelValues[i],
  240. })
  241. }
  242. n := "namespace"
  243. labels = append(labels, &dto.LabelPair{
  244. Name: &n,
  245. Value: &s.namespace,
  246. })
  247. r := "service"
  248. labels = append(labels, &dto.LabelPair{
  249. Name: &r,
  250. Value: &s.serviceName,
  251. })
  252. m.Label = labels
  253. return nil
  254. }
  255. //--------------------------------------------------------------------------
  256. // ClusterInfoCollector
  257. //--------------------------------------------------------------------------
  258. // ClusterInfoCollector is a prometheus collector that generates ClusterInfoMetrics
  259. type ClusterInfoCollector struct {
  260. Cloud costAnalyzerCloud.Provider
  261. KubeClientSet kubernetes.Interface
  262. }
  263. // Describe sends the super-set of all possible descriptors of metrics
  264. // collected by this Collector.
  265. func (cic ClusterInfoCollector) Describe(ch chan<- *prometheus.Desc) {
  266. ch <- prometheus.NewDesc("kubecost_cluster_info", "Kubecost Cluster Info", []string{}, nil)
  267. }
  268. // Collect is called by the Prometheus registry when collecting metrics.
  269. func (cic ClusterInfoCollector) Collect(ch chan<- prometheus.Metric) {
  270. clusterInfo := GetClusterInfo(cic.KubeClientSet, cic.Cloud)
  271. labels := prom.MapToLabels(clusterInfo)
  272. m := newClusterInfoMetric("kubecost_cluster_info", labels)
  273. ch <- m
  274. }
  275. //--------------------------------------------------------------------------
  276. // ClusterInfoMetric
  277. //--------------------------------------------------------------------------
  278. // ClusterInfoMetric is a prometheus.Metric used to encode the local cluster info
  279. type ClusterInfoMetric struct {
  280. fqName string
  281. help string
  282. labels map[string]string
  283. }
  284. // Creates a new ClusterInfoMetric, implementation of prometheus.Metric
  285. func newClusterInfoMetric(fqName string, labels map[string]string) ClusterInfoMetric {
  286. return ClusterInfoMetric{
  287. fqName: fqName,
  288. labels: labels,
  289. help: "kubecost_cluster_info ClusterInfo",
  290. }
  291. }
  292. // Desc returns the descriptor for the Metric. This method idempotently
  293. // returns the same descriptor throughout the lifetime of the Metric.
  294. func (cim ClusterInfoMetric) Desc() *prometheus.Desc {
  295. l := prometheus.Labels{}
  296. return prometheus.NewDesc(cim.fqName, cim.help, prom.LabelNamesFrom(cim.labels), l)
  297. }
  298. // Write encodes the Metric into a "Metric" Protocol Buffer data
  299. // transmission object.
  300. func (cim ClusterInfoMetric) Write(m *dto.Metric) error {
  301. h := float64(1)
  302. m.Gauge = &dto.Gauge{
  303. Value: &h,
  304. }
  305. var labels []*dto.LabelPair
  306. for k, v := range cim.labels {
  307. labels = append(labels, &dto.LabelPair{
  308. Name: toStringPtr(k),
  309. Value: toStringPtr(v),
  310. })
  311. }
  312. m.Label = labels
  313. return nil
  314. }
  315. // toStringPtr is used to create a new string pointer from iteration vars
  316. func toStringPtr(s string) *string {
  317. return &s
  318. }
  319. //--------------------------------------------------------------------------
  320. // Package Functions
  321. //--------------------------------------------------------------------------
  322. var (
  323. recordingLock sync.Mutex
  324. recordingStopping bool
  325. recordingStop chan bool
  326. )
  327. // Checks to see if there is a metric recording stop channel. If it exists, a new
  328. // channel is not created and false is returned. If it doesn't exist, a new channel
  329. // is created and true is returned.
  330. func checkOrCreateRecordingChan() bool {
  331. recordingLock.Lock()
  332. defer recordingLock.Unlock()
  333. if recordingStop != nil {
  334. return false
  335. }
  336. recordingStop = make(chan bool, 1)
  337. return true
  338. }
  339. // IsCostModelMetricRecordingRunning returns true if metric recording is still running.
  340. func IsCostModelMetricRecordingRunning() bool {
  341. recordingLock.Lock()
  342. defer recordingLock.Unlock()
  343. return recordingStop != nil
  344. }
  345. // StartCostModelMetricRecording starts the go routine that emits metrics used to determine
  346. // cluster costs.
  347. func StartCostModelMetricRecording(a *Accesses) bool {
  348. // Check to see if we're already recording
  349. // This function will create the stop recording channel and return true
  350. // if it doesn't exist.
  351. if !checkOrCreateRecordingChan() {
  352. log.Errorf("Attempted to start cost model metric recording when it's already running.")
  353. return false
  354. }
  355. go func() {
  356. defer errors.HandlePanic()
  357. containerSeen := make(map[string]bool)
  358. nodeSeen := make(map[string]bool)
  359. loadBalancerSeen := make(map[string]bool)
  360. pvSeen := make(map[string]bool)
  361. pvcSeen := make(map[string]bool)
  362. getKeyFromLabelStrings := func(labels ...string) string {
  363. return strings.Join(labels, ",")
  364. }
  365. getLabelStringsFromKey := func(key string) []string {
  366. return strings.Split(key, ",")
  367. }
  368. var defaultRegion string = ""
  369. nodeList := a.Model.Cache.GetAllNodes()
  370. if len(nodeList) > 0 {
  371. defaultRegion = nodeList[0].Labels[v1.LabelZoneRegion]
  372. }
  373. for {
  374. klog.V(4).Info("Recording prices...")
  375. podlist := a.Model.Cache.GetAllPods()
  376. podStatus := make(map[string]v1.PodPhase)
  377. for _, pod := range podlist {
  378. podStatus[pod.Name] = pod.Status.Phase
  379. }
  380. cfg, _ := a.Cloud.GetConfig()
  381. provisioner, clusterManagementCost, err := a.Cloud.ClusterManagementPricing()
  382. if err != nil {
  383. klog.V(1).Infof("Error getting cluster management cost %s", err.Error())
  384. }
  385. a.ClusterManagementCostRecorder.WithLabelValues(provisioner).Set(clusterManagementCost)
  386. // Record network pricing at global scope
  387. networkCosts, err := a.Cloud.NetworkPricing()
  388. if err != nil {
  389. klog.V(4).Infof("Failed to retrieve network costs: %s", err.Error())
  390. } else {
  391. a.NetworkZoneEgressRecorder.Set(networkCosts.ZoneNetworkEgressCost)
  392. a.NetworkRegionEgressRecorder.Set(networkCosts.RegionNetworkEgressCost)
  393. a.NetworkInternetEgressRecorder.Set(networkCosts.InternetNetworkEgressCost)
  394. }
  395. data, err := a.Model.ComputeCostData(a.PrometheusClient, a.KubeClientSet, a.Cloud, "2m", "", "")
  396. if err != nil {
  397. klog.V(1).Info("Error in price recording: " + err.Error())
  398. // zero the for loop so the time.Sleep will still work
  399. data = map[string]*CostData{}
  400. }
  401. nodes, err := a.Model.GetNodeCost(a.Cloud)
  402. for nodeName, node := range nodes {
  403. // Emit costs, guarding against NaN inputs for custom pricing.
  404. cpuCost, _ := strconv.ParseFloat(node.VCPUCost, 64)
  405. if math.IsNaN(cpuCost) || math.IsInf(cpuCost, 0) {
  406. cpuCost, _ = strconv.ParseFloat(cfg.CPU, 64)
  407. if math.IsNaN(cpuCost) || math.IsInf(cpuCost, 0) {
  408. cpuCost = 0
  409. }
  410. }
  411. cpu, _ := strconv.ParseFloat(node.VCPU, 64)
  412. if math.IsNaN(cpu) || math.IsInf(cpu, 0) {
  413. cpu = 1 // Assume 1 CPU
  414. }
  415. ramCost, _ := strconv.ParseFloat(node.RAMCost, 64)
  416. if math.IsNaN(ramCost) || math.IsInf(ramCost, 0) {
  417. ramCost, _ = strconv.ParseFloat(cfg.RAM, 64)
  418. if math.IsNaN(ramCost) || math.IsInf(ramCost, 0) {
  419. ramCost = 0
  420. }
  421. }
  422. ram, _ := strconv.ParseFloat(node.RAMBytes, 64)
  423. if math.IsNaN(ram) || math.IsInf(ram, 0) {
  424. ram = 0
  425. }
  426. gpu, _ := strconv.ParseFloat(node.GPU, 64)
  427. if math.IsNaN(gpu) || math.IsInf(gpu, 0) {
  428. gpu = 0
  429. }
  430. gpuCost, _ := strconv.ParseFloat(node.GPUCost, 64)
  431. if math.IsNaN(gpuCost) || math.IsInf(gpuCost, 0) {
  432. gpuCost, _ = strconv.ParseFloat(cfg.GPU, 64)
  433. if math.IsNaN(gpuCost) || math.IsInf(gpuCost, 0) {
  434. gpuCost = 0
  435. }
  436. }
  437. nodeType := node.InstanceType
  438. nodeRegion := node.Region
  439. totalCost := cpu*cpuCost + ramCost*(ram/1024/1024/1024) + gpu*gpuCost
  440. a.CPUPriceRecorder.WithLabelValues(nodeName, nodeName, nodeType, nodeRegion, node.ProviderID).Set(cpuCost)
  441. a.RAMPriceRecorder.WithLabelValues(nodeName, nodeName, nodeType, nodeRegion, node.ProviderID).Set(ramCost)
  442. a.GPUPriceRecorder.WithLabelValues(nodeName, nodeName, nodeType, nodeRegion, node.ProviderID).Set(gpuCost)
  443. a.NodeTotalPriceRecorder.WithLabelValues(nodeName, nodeName, nodeType, nodeRegion, node.ProviderID).Set(totalCost)
  444. if node.IsSpot() {
  445. a.NodeSpotRecorder.WithLabelValues(nodeName, nodeName, nodeType, nodeRegion, node.ProviderID).Set(1.0)
  446. } else {
  447. a.NodeSpotRecorder.WithLabelValues(nodeName, nodeName, nodeType, nodeRegion, node.ProviderID).Set(0.0)
  448. }
  449. labelKey := getKeyFromLabelStrings(nodeName, nodeName, nodeType, nodeRegion, node.ProviderID)
  450. nodeSeen[labelKey] = true
  451. }
  452. loadBalancers, err := a.Model.GetLBCost(a.Cloud)
  453. for lbKey, lb := range loadBalancers {
  454. // TODO: parse (if necessary) and calculate cost associated with loadBalancer based on dynamic cloud prices fetched into each lb struct on GetLBCost() call
  455. keyParts := getLabelStringsFromKey(lbKey)
  456. namespace := keyParts[0]
  457. serviceName := keyParts[1]
  458. ingressIP := ""
  459. if len(lb.IngressIPAddresses) > 0 {
  460. ingressIP = lb.IngressIPAddresses[0] // assumes one ingress IP per load balancer
  461. }
  462. a.LBCostRecorder.WithLabelValues(ingressIP, namespace, serviceName).Set(lb.Cost)
  463. labelKey := getKeyFromLabelStrings(namespace, serviceName)
  464. loadBalancerSeen[labelKey] = true
  465. }
  466. for _, costs := range data {
  467. nodeName := costs.NodeName
  468. namespace := costs.Namespace
  469. podName := costs.PodName
  470. containerName := costs.Name
  471. if costs.PVCData != nil {
  472. for _, pvc := range costs.PVCData {
  473. if pvc.Volume != nil {
  474. timesClaimed := pvc.TimesClaimed
  475. if timesClaimed == 0 {
  476. timesClaimed = 1 // unallocated PVs are unclaimed but have a full allocation
  477. }
  478. a.PVAllocationRecorder.WithLabelValues(namespace, podName, pvc.Claim, pvc.VolumeName).Set(pvc.Values[0].Value / float64(timesClaimed))
  479. labelKey := getKeyFromLabelStrings(namespace, podName, pvc.Claim, pvc.VolumeName)
  480. pvcSeen[labelKey] = true
  481. }
  482. }
  483. }
  484. if len(costs.RAMAllocation) > 0 {
  485. a.RAMAllocationRecorder.WithLabelValues(namespace, podName, containerName, nodeName, nodeName).Set(costs.RAMAllocation[0].Value)
  486. }
  487. if len(costs.CPUAllocation) > 0 {
  488. a.CPUAllocationRecorder.WithLabelValues(namespace, podName, containerName, nodeName, nodeName).Set(costs.CPUAllocation[0].Value)
  489. }
  490. if len(costs.GPUReq) > 0 {
  491. // allocation here is set to the request because shared GPU usage not yet supported.
  492. a.GPUAllocationRecorder.WithLabelValues(namespace, podName, containerName, nodeName, nodeName).Set(costs.GPUReq[0].Value)
  493. }
  494. labelKey := getKeyFromLabelStrings(namespace, podName, containerName, nodeName, nodeName)
  495. if podStatus[podName] == v1.PodRunning { // Only report data for current pods
  496. containerSeen[labelKey] = true
  497. } else {
  498. containerSeen[labelKey] = false
  499. }
  500. storageClasses := a.Model.Cache.GetAllStorageClasses()
  501. storageClassMap := make(map[string]map[string]string)
  502. for _, storageClass := range storageClasses {
  503. params := storageClass.Parameters
  504. storageClassMap[storageClass.ObjectMeta.Name] = params
  505. if storageClass.GetAnnotations()["storageclass.kubernetes.io/is-default-class"] == "true" || storageClass.GetAnnotations()["storageclass.beta.kubernetes.io/is-default-class"] == "true" {
  506. storageClassMap["default"] = params
  507. storageClassMap[""] = params
  508. }
  509. }
  510. pvs := a.Model.Cache.GetAllPersistentVolumes()
  511. for _, pv := range pvs {
  512. parameters, ok := storageClassMap[pv.Spec.StorageClassName]
  513. if !ok {
  514. klog.V(4).Infof("Unable to find parameters for storage class \"%s\". Does pv \"%s\" have a storageClassName?", pv.Spec.StorageClassName, pv.Name)
  515. }
  516. var region string
  517. if r, ok := pv.Labels[v1.LabelZoneRegion]; ok {
  518. region = r
  519. } else {
  520. region = defaultRegion
  521. }
  522. cacPv := &costAnalyzerCloud.PV{
  523. Class: pv.Spec.StorageClassName,
  524. Region: region,
  525. Parameters: parameters,
  526. }
  527. GetPVCost(cacPv, pv, a.Cloud, region)
  528. c, _ := strconv.ParseFloat(cacPv.Cost, 64)
  529. a.PersistentVolumePriceRecorder.WithLabelValues(pv.Name, pv.Name, cacPv.ProviderID).Set(c)
  530. labelKey := getKeyFromLabelStrings(pv.Name, pv.Name)
  531. pvSeen[labelKey] = true
  532. }
  533. }
  534. for labelString, seen := range nodeSeen {
  535. if !seen {
  536. klog.V(4).Infof("Removing %s from nodes", labelString)
  537. labels := getLabelStringsFromKey(labelString)
  538. ok := a.NodeTotalPriceRecorder.DeleteLabelValues(labels...)
  539. if ok {
  540. klog.V(4).Infof("removed %s from totalprice", labelString)
  541. } else {
  542. klog.Infof("FAILURE TO REMOVE %s from totalprice", labelString)
  543. }
  544. ok = a.NodeSpotRecorder.DeleteLabelValues(labels...)
  545. if ok {
  546. klog.V(4).Infof("removed %s from spot records", labelString)
  547. } else {
  548. klog.Infof("FAILURE TO REMOVE %s from spot records", labelString)
  549. }
  550. ok = a.CPUPriceRecorder.DeleteLabelValues(labels...)
  551. if ok {
  552. klog.V(4).Infof("removed %s from cpuprice", labelString)
  553. } else {
  554. klog.Infof("FAILURE TO REMOVE %s from cpuprice", labelString)
  555. }
  556. ok = a.GPUPriceRecorder.DeleteLabelValues(labels...)
  557. if ok {
  558. klog.V(4).Infof("removed %s from gpuprice", labelString)
  559. } else {
  560. klog.Infof("FAILURE TO REMOVE %s from gpuprice", labelString)
  561. }
  562. ok = a.RAMPriceRecorder.DeleteLabelValues(labels...)
  563. if ok {
  564. klog.V(4).Infof("removed %s from ramprice", labelString)
  565. } else {
  566. klog.Infof("FAILURE TO REMOVE %s from ramprice", labelString)
  567. }
  568. delete(nodeSeen, labelString)
  569. } else {
  570. nodeSeen[labelString] = false
  571. }
  572. }
  573. for labelString, seen := range loadBalancerSeen {
  574. if !seen {
  575. labels := getLabelStringsFromKey(labelString)
  576. a.LBCostRecorder.DeleteLabelValues(labels...)
  577. } else {
  578. loadBalancerSeen[labelString] = false
  579. }
  580. }
  581. for labelString, seen := range containerSeen {
  582. if !seen {
  583. labels := getLabelStringsFromKey(labelString)
  584. a.RAMAllocationRecorder.DeleteLabelValues(labels...)
  585. a.CPUAllocationRecorder.DeleteLabelValues(labels...)
  586. a.GPUAllocationRecorder.DeleteLabelValues(labels...)
  587. delete(containerSeen, labelString)
  588. } else {
  589. containerSeen[labelString] = false
  590. }
  591. }
  592. for labelString, seen := range pvSeen {
  593. if !seen {
  594. labels := getLabelStringsFromKey(labelString)
  595. a.PersistentVolumePriceRecorder.DeleteLabelValues(labels...)
  596. delete(pvSeen, labelString)
  597. } else {
  598. pvSeen[labelString] = false
  599. }
  600. }
  601. for labelString, seen := range pvcSeen {
  602. if !seen {
  603. labels := getLabelStringsFromKey(labelString)
  604. a.PVAllocationRecorder.DeleteLabelValues(labels...)
  605. delete(pvcSeen, labelString)
  606. } else {
  607. pvcSeen[labelString] = false
  608. }
  609. }
  610. select {
  611. case <-time.After(time.Minute):
  612. case <-recordingStop:
  613. recordingLock.Lock()
  614. recordingStopping = false
  615. recordingStop = nil
  616. recordingLock.Unlock()
  617. return
  618. }
  619. }
  620. }()
  621. return true
  622. }
  623. // StopCostModelMetricRecording halts the metrics emission loop after the current emission is completed
  624. // or if the emission is paused.
  625. func StopCostModelMetricRecording() {
  626. recordingLock.Lock()
  627. defer recordingLock.Unlock()
  628. if !recordingStopping && recordingStop != nil {
  629. recordingStopping = true
  630. close(recordingStop)
  631. }
  632. }
  633. // Converts kubernetes labels into prometheus labels.
  634. func kubeLabelsToPrometheusLabels(labels map[string]string) ([]string, []string) {
  635. labelKeys := make([]string, 0, len(labels))
  636. for k := range labels {
  637. labelKeys = append(labelKeys, k)
  638. }
  639. sort.Strings(labelKeys)
  640. labelValues := make([]string, 0, len(labels))
  641. for i, k := range labelKeys {
  642. labelKeys[i] = "label_" + SanitizeLabelName(k)
  643. labelValues = append(labelValues, labels[k])
  644. }
  645. return labelKeys, labelValues
  646. }
  647. // Replaces all illegal prometheus label characters with _
  648. func SanitizeLabelName(s string) string {
  649. return invalidLabelCharRE.ReplaceAllString(s, "_")
  650. }