metrics.go 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078
  1. package costmodel
  2. import (
  3. "math"
  4. "strconv"
  5. "strings"
  6. "sync"
  7. "time"
  8. "github.com/kubecost/cost-model/pkg/cloud"
  9. "github.com/kubecost/cost-model/pkg/clustercache"
  10. "github.com/kubecost/cost-model/pkg/env"
  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. promclient "github.com/prometheus/client_golang/api"
  15. "github.com/prometheus/client_golang/prometheus"
  16. dto "github.com/prometheus/client_model/go"
  17. v1 "k8s.io/api/core/v1"
  18. "k8s.io/client-go/kubernetes"
  19. "k8s.io/klog"
  20. )
  21. //--------------------------------------------------------------------------
  22. // StatefulsetCollector
  23. //--------------------------------------------------------------------------
  24. // StatefulsetCollector is a prometheus collector that generates StatefulsetMetrics
  25. type StatefulsetCollector struct {
  26. KubeClusterCache clustercache.ClusterCache
  27. }
  28. // Describe sends the super-set of all possible descriptors of metrics
  29. // collected by this Collector.
  30. func (sc StatefulsetCollector) Describe(ch chan<- *prometheus.Desc) {
  31. ch <- prometheus.NewDesc("statefulSet_match_labels", "statfulSet match labels", []string{}, nil)
  32. }
  33. // Collect is called by the Prometheus registry when collecting metrics.
  34. func (sc StatefulsetCollector) Collect(ch chan<- prometheus.Metric) {
  35. ds := sc.KubeClusterCache.GetAllStatefulSets()
  36. for _, statefulset := range ds {
  37. labels, values := prom.KubeLabelsToLabels(statefulset.Spec.Selector.MatchLabels)
  38. m := newStatefulsetMetric(statefulset.GetName(), statefulset.GetNamespace(), "statefulSet_match_labels", labels, values)
  39. ch <- m
  40. }
  41. }
  42. //--------------------------------------------------------------------------
  43. // StatefulsetMetric
  44. //--------------------------------------------------------------------------
  45. // StatefulsetMetric is a prometheus.Metric used to encode statefulset match labels
  46. type StatefulsetMetric struct {
  47. fqName string
  48. help string
  49. labelNames []string
  50. labelValues []string
  51. statefulsetName string
  52. namespace string
  53. }
  54. // Creates a new StatefulsetMetric, implementation of prometheus.Metric
  55. func newStatefulsetMetric(name, namespace, fqname string, labelNames []string, labelvalues []string) StatefulsetMetric {
  56. return StatefulsetMetric{
  57. fqName: fqname,
  58. labelNames: labelNames,
  59. labelValues: labelvalues,
  60. help: "statefulSet_match_labels StatefulSet Match Labels",
  61. statefulsetName: name,
  62. namespace: namespace,
  63. }
  64. }
  65. // Desc returns the descriptor for the Metric. This method idempotently
  66. // returns the same descriptor throughout the lifetime of the Metric.
  67. func (s StatefulsetMetric) Desc() *prometheus.Desc {
  68. l := prometheus.Labels{"statefulSet": s.statefulsetName, "namespace": s.namespace}
  69. return prometheus.NewDesc(s.fqName, s.help, s.labelNames, l)
  70. }
  71. // Write encodes the Metric into a "Metric" Protocol Buffer data
  72. // transmission object.
  73. func (s StatefulsetMetric) Write(m *dto.Metric) error {
  74. h := float64(1)
  75. m.Gauge = &dto.Gauge{
  76. Value: &h,
  77. }
  78. var labels []*dto.LabelPair
  79. for i := range s.labelNames {
  80. labels = append(labels, &dto.LabelPair{
  81. Name: &s.labelNames[i],
  82. Value: &s.labelValues[i],
  83. })
  84. }
  85. n := "namespace"
  86. labels = append(labels, &dto.LabelPair{
  87. Name: &n,
  88. Value: &s.namespace,
  89. })
  90. r := "statefulSet"
  91. labels = append(labels, &dto.LabelPair{
  92. Name: &r,
  93. Value: &s.statefulsetName,
  94. })
  95. m.Label = labels
  96. return nil
  97. }
  98. //--------------------------------------------------------------------------
  99. // DeploymentCollector
  100. //--------------------------------------------------------------------------
  101. // DeploymentCollector is a prometheus collector that generates DeploymentMetrics
  102. type DeploymentCollector struct {
  103. KubeClusterCache clustercache.ClusterCache
  104. }
  105. // Describe sends the super-set of all possible descriptors of metrics
  106. // collected by this Collector.
  107. func (sc DeploymentCollector) Describe(ch chan<- *prometheus.Desc) {
  108. ch <- prometheus.NewDesc("deployment_match_labels", "deployment match labels", []string{}, nil)
  109. }
  110. // Collect is called by the Prometheus registry when collecting metrics.
  111. func (sc DeploymentCollector) Collect(ch chan<- prometheus.Metric) {
  112. ds := sc.KubeClusterCache.GetAllDeployments()
  113. for _, deployment := range ds {
  114. labels, values := prom.KubeLabelsToLabels(deployment.Spec.Selector.MatchLabels)
  115. m := newDeploymentMetric(deployment.GetName(), deployment.GetNamespace(), "deployment_match_labels", labels, values)
  116. ch <- m
  117. }
  118. }
  119. //--------------------------------------------------------------------------
  120. // DeploymentMetric
  121. //--------------------------------------------------------------------------
  122. // DeploymentMetric is a prometheus.Metric used to encode deployment match labels
  123. type DeploymentMetric struct {
  124. fqName string
  125. help string
  126. labelNames []string
  127. labelValues []string
  128. deploymentName string
  129. namespace string
  130. }
  131. // Creates a new DeploymentMetric, implementation of prometheus.Metric
  132. func newDeploymentMetric(name, namespace, fqname string, labelNames []string, labelvalues []string) DeploymentMetric {
  133. return DeploymentMetric{
  134. fqName: fqname,
  135. labelNames: labelNames,
  136. labelValues: labelvalues,
  137. help: "deployment_match_labels Deployment Match Labels",
  138. deploymentName: name,
  139. namespace: namespace,
  140. }
  141. }
  142. // Desc returns the descriptor for the Metric. This method idempotently
  143. // returns the same descriptor throughout the lifetime of the Metric.
  144. func (s DeploymentMetric) Desc() *prometheus.Desc {
  145. l := prometheus.Labels{"deployment": s.deploymentName, "namespace": s.namespace}
  146. return prometheus.NewDesc(s.fqName, s.help, s.labelNames, l)
  147. }
  148. // Write encodes the Metric into a "Metric" Protocol Buffer data
  149. // transmission object.
  150. func (s DeploymentMetric) Write(m *dto.Metric) error {
  151. h := float64(1)
  152. m.Gauge = &dto.Gauge{
  153. Value: &h,
  154. }
  155. var labels []*dto.LabelPair
  156. for i := range s.labelNames {
  157. labels = append(labels, &dto.LabelPair{
  158. Name: &s.labelNames[i],
  159. Value: &s.labelValues[i],
  160. })
  161. }
  162. n := "namespace"
  163. labels = append(labels, &dto.LabelPair{
  164. Name: &n,
  165. Value: &s.namespace,
  166. })
  167. r := "deployment"
  168. labels = append(labels, &dto.LabelPair{
  169. Name: &r,
  170. Value: &s.deploymentName,
  171. })
  172. m.Label = labels
  173. return nil
  174. }
  175. //--------------------------------------------------------------------------
  176. // ServiceCollector
  177. //--------------------------------------------------------------------------
  178. // ServiceCollector is a prometheus collector that generates ServiceMetrics
  179. type ServiceCollector struct {
  180. KubeClusterCache clustercache.ClusterCache
  181. }
  182. // Describe sends the super-set of all possible descriptors of metrics
  183. // collected by this Collector.
  184. func (sc ServiceCollector) Describe(ch chan<- *prometheus.Desc) {
  185. ch <- prometheus.NewDesc("service_selector_labels", "service selector labels", []string{}, nil)
  186. }
  187. // Collect is called by the Prometheus registry when collecting metrics.
  188. func (sc ServiceCollector) Collect(ch chan<- prometheus.Metric) {
  189. svcs := sc.KubeClusterCache.GetAllServices()
  190. for _, svc := range svcs {
  191. labels, values := prom.KubeLabelsToLabels(svc.Spec.Selector)
  192. m := newServiceMetric(svc.GetName(), svc.GetNamespace(), "service_selector_labels", labels, values)
  193. ch <- m
  194. }
  195. }
  196. //--------------------------------------------------------------------------
  197. // ServiceMetric
  198. //--------------------------------------------------------------------------
  199. // ServiceMetric is a prometheus.Metric used to encode service selector labels
  200. type ServiceMetric struct {
  201. fqName string
  202. help string
  203. labelNames []string
  204. labelValues []string
  205. serviceName string
  206. namespace string
  207. }
  208. // Creates a new ServiceMetric, implementation of prometheus.Metric
  209. func newServiceMetric(name, namespace, fqname string, labelNames []string, labelvalues []string) ServiceMetric {
  210. return ServiceMetric{
  211. fqName: fqname,
  212. labelNames: labelNames,
  213. labelValues: labelvalues,
  214. help: "service_selector_labels Service Selector Labels",
  215. serviceName: name,
  216. namespace: namespace,
  217. }
  218. }
  219. // Desc returns the descriptor for the Metric. This method idempotently
  220. // returns the same descriptor throughout the lifetime of the Metric.
  221. func (s ServiceMetric) Desc() *prometheus.Desc {
  222. l := prometheus.Labels{"service": s.serviceName, "namespace": s.namespace}
  223. return prometheus.NewDesc(s.fqName, s.help, s.labelNames, l)
  224. }
  225. // Write encodes the Metric into a "Metric" Protocol Buffer data
  226. // transmission object.
  227. func (s ServiceMetric) Write(m *dto.Metric) error {
  228. h := float64(1)
  229. m.Gauge = &dto.Gauge{
  230. Value: &h,
  231. }
  232. var labels []*dto.LabelPair
  233. for i := range s.labelNames {
  234. labels = append(labels, &dto.LabelPair{
  235. Name: &s.labelNames[i],
  236. Value: &s.labelValues[i],
  237. })
  238. }
  239. n := "namespace"
  240. labels = append(labels, &dto.LabelPair{
  241. Name: &n,
  242. Value: &s.namespace,
  243. })
  244. r := "service"
  245. labels = append(labels, &dto.LabelPair{
  246. Name: &r,
  247. Value: &s.serviceName,
  248. })
  249. m.Label = labels
  250. return nil
  251. }
  252. //--------------------------------------------------------------------------
  253. // NamespaceAnnotationCollector
  254. //--------------------------------------------------------------------------
  255. // NamespaceAnnotationCollector is a prometheus collector that generates NamespaceAnnotationMetrics
  256. type NamespaceAnnotationCollector struct {
  257. KubeClusterCache clustercache.ClusterCache
  258. }
  259. // Describe sends the super-set of all possible descriptors of metrics
  260. // collected by this Collector.
  261. func (nsac NamespaceAnnotationCollector) Describe(ch chan<- *prometheus.Desc) {
  262. ch <- prometheus.NewDesc("kube_namespace_annotations", "namespace annotations", []string{}, nil)
  263. }
  264. // Collect is called by the Prometheus registry when collecting metrics.
  265. func (nsac NamespaceAnnotationCollector) Collect(ch chan<- prometheus.Metric) {
  266. namespaces := nsac.KubeClusterCache.GetAllNamespaces()
  267. for _, namespace := range namespaces {
  268. labels, values := prom.KubeAnnotationsToLabels(namespace.Annotations)
  269. m := newNamespaceAnnotationsMetric(namespace.GetName(), "kube_namespace_annotations", labels, values)
  270. ch <- m
  271. }
  272. }
  273. //--------------------------------------------------------------------------
  274. // NamespaceAnnotationsMetric
  275. //--------------------------------------------------------------------------
  276. // NamespaceAnnotationsMetric is a prometheus.Metric used to encode namespace annotations
  277. type NamespaceAnnotationsMetric struct {
  278. fqName string
  279. help string
  280. labelNames []string
  281. labelValues []string
  282. namespace string
  283. }
  284. // Creates a new NamespaceAnnotationsMetric, implementation of prometheus.Metric
  285. func newNamespaceAnnotationsMetric(namespace, fqname string, labelNames []string, labelValues []string) NamespaceAnnotationsMetric {
  286. return NamespaceAnnotationsMetric{
  287. namespace: namespace,
  288. fqName: fqname,
  289. labelNames: labelNames,
  290. labelValues: labelValues,
  291. help: "kube_namespace_annotations Namespace Annotations",
  292. }
  293. }
  294. // Desc returns the descriptor for the Metric. This method idempotently
  295. // returns the same descriptor throughout the lifetime of the Metric.
  296. func (nam NamespaceAnnotationsMetric) Desc() *prometheus.Desc {
  297. l := prometheus.Labels{"namespace": nam.namespace}
  298. return prometheus.NewDesc(nam.fqName, nam.help, nam.labelNames, l)
  299. }
  300. // Write encodes the Metric into a "Metric" Protocol Buffer data
  301. // transmission object.
  302. func (nam NamespaceAnnotationsMetric) Write(m *dto.Metric) error {
  303. h := float64(1)
  304. m.Gauge = &dto.Gauge{
  305. Value: &h,
  306. }
  307. var labels []*dto.LabelPair
  308. for i := range nam.labelNames {
  309. labels = append(labels, &dto.LabelPair{
  310. Name: &nam.labelNames[i],
  311. Value: &nam.labelValues[i],
  312. })
  313. }
  314. n := "namespace"
  315. labels = append(labels, &dto.LabelPair{
  316. Name: &n,
  317. Value: &nam.namespace,
  318. })
  319. m.Label = labels
  320. return nil
  321. }
  322. //--------------------------------------------------------------------------
  323. // PodAnnotationCollector
  324. //--------------------------------------------------------------------------
  325. // PodAnnotationCollector is a prometheus collector that generates PodAnnotationMetrics
  326. type PodAnnotationCollector struct {
  327. KubeClusterCache clustercache.ClusterCache
  328. }
  329. // Describe sends the super-set of all possible descriptors of metrics
  330. // collected by this Collector.
  331. func (pac PodAnnotationCollector) Describe(ch chan<- *prometheus.Desc) {
  332. ch <- prometheus.NewDesc("kube_pod_annotations", "pod annotations", []string{}, nil)
  333. }
  334. // Collect is called by the Prometheus registry when collecting metrics.
  335. func (pac PodAnnotationCollector) Collect(ch chan<- prometheus.Metric) {
  336. pods := pac.KubeClusterCache.GetAllPods()
  337. for _, pod := range pods {
  338. labels, values := prom.KubeAnnotationsToLabels(pod.Annotations)
  339. m := newPodAnnotationMetric(pod.GetNamespace(), pod.GetName(), "kube_pod_annotations", labels, values)
  340. ch <- m
  341. }
  342. }
  343. //--------------------------------------------------------------------------
  344. // PodAnnotationsMetric
  345. //--------------------------------------------------------------------------
  346. // PodAnnotationsMetric is a prometheus.Metric used to encode namespace annotations
  347. type PodAnnotationsMetric struct {
  348. name string
  349. fqName string
  350. help string
  351. labelNames []string
  352. labelValues []string
  353. namespace string
  354. }
  355. // Creates a new PodAnnotationsMetric, implementation of prometheus.Metric
  356. func newPodAnnotationMetric(namespace, name, fqname string, labelNames []string, labelValues []string) PodAnnotationsMetric {
  357. return PodAnnotationsMetric{
  358. namespace: namespace,
  359. name: name,
  360. fqName: fqname,
  361. labelNames: labelNames,
  362. labelValues: labelValues,
  363. help: "kube_pod_annotations Pod Annotations",
  364. }
  365. }
  366. // Desc returns the descriptor for the Metric. This method idempotently
  367. // returns the same descriptor throughout the lifetime of the Metric.
  368. func (pam PodAnnotationsMetric) Desc() *prometheus.Desc {
  369. l := prometheus.Labels{"namespace": pam.namespace, "pod": pam.name}
  370. return prometheus.NewDesc(pam.fqName, pam.help, pam.labelNames, l)
  371. }
  372. // Write encodes the Metric into a "Metric" Protocol Buffer data
  373. // transmission object.
  374. func (pam PodAnnotationsMetric) Write(m *dto.Metric) error {
  375. h := float64(1)
  376. m.Gauge = &dto.Gauge{
  377. Value: &h,
  378. }
  379. var labels []*dto.LabelPair
  380. for i := range pam.labelNames {
  381. labels = append(labels, &dto.LabelPair{
  382. Name: &pam.labelNames[i],
  383. Value: &pam.labelValues[i],
  384. })
  385. }
  386. n := "namespace"
  387. labels = append(labels, &dto.LabelPair{
  388. Name: &n,
  389. Value: &pam.namespace,
  390. })
  391. r := "pod"
  392. labels = append(labels, &dto.LabelPair{
  393. Name: &r,
  394. Value: &pam.name,
  395. })
  396. m.Label = labels
  397. return nil
  398. }
  399. //--------------------------------------------------------------------------
  400. // ClusterInfoCollector
  401. //--------------------------------------------------------------------------
  402. // ClusterInfoCollector is a prometheus collector that generates ClusterInfoMetrics
  403. type ClusterInfoCollector struct {
  404. Cloud cloud.Provider
  405. KubeClientSet kubernetes.Interface
  406. }
  407. // Describe sends the super-set of all possible descriptors of metrics
  408. // collected by this Collector.
  409. func (cic ClusterInfoCollector) Describe(ch chan<- *prometheus.Desc) {
  410. ch <- prometheus.NewDesc("kubecost_cluster_info", "Kubecost Cluster Info", []string{}, nil)
  411. }
  412. // Collect is called by the Prometheus registry when collecting metrics.
  413. func (cic ClusterInfoCollector) Collect(ch chan<- prometheus.Metric) {
  414. clusterInfo := GetClusterInfo(cic.KubeClientSet, cic.Cloud)
  415. labels := prom.MapToLabels(clusterInfo)
  416. m := newClusterInfoMetric("kubecost_cluster_info", labels)
  417. ch <- m
  418. }
  419. //--------------------------------------------------------------------------
  420. // ClusterInfoMetric
  421. //--------------------------------------------------------------------------
  422. // ClusterInfoMetric is a prometheus.Metric used to encode the local cluster info
  423. type ClusterInfoMetric struct {
  424. fqName string
  425. help string
  426. labels map[string]string
  427. }
  428. // Creates a new ClusterInfoMetric, implementation of prometheus.Metric
  429. func newClusterInfoMetric(fqName string, labels map[string]string) ClusterInfoMetric {
  430. return ClusterInfoMetric{
  431. fqName: fqName,
  432. labels: labels,
  433. help: "kubecost_cluster_info ClusterInfo",
  434. }
  435. }
  436. // Desc returns the descriptor for the Metric. This method idempotently
  437. // returns the same descriptor throughout the lifetime of the Metric.
  438. func (cim ClusterInfoMetric) Desc() *prometheus.Desc {
  439. l := prometheus.Labels{}
  440. return prometheus.NewDesc(cim.fqName, cim.help, prom.LabelNamesFrom(cim.labels), l)
  441. }
  442. // Write encodes the Metric into a "Metric" Protocol Buffer data
  443. // transmission object.
  444. func (cim ClusterInfoMetric) Write(m *dto.Metric) error {
  445. h := float64(1)
  446. m.Gauge = &dto.Gauge{
  447. Value: &h,
  448. }
  449. var labels []*dto.LabelPair
  450. for k, v := range cim.labels {
  451. labels = append(labels, &dto.LabelPair{
  452. Name: toStringPtr(k),
  453. Value: toStringPtr(v),
  454. })
  455. }
  456. m.Label = labels
  457. return nil
  458. }
  459. // toStringPtr is used to create a new string pointer from iteration vars
  460. func toStringPtr(s string) *string {
  461. return &s
  462. }
  463. //--------------------------------------------------------------------------
  464. // Cost Model Metrics Initialization
  465. //--------------------------------------------------------------------------
  466. // Only allow the metrics to be instantiated and registered once
  467. var metricsInit sync.Once
  468. var (
  469. cpuGv *prometheus.GaugeVec
  470. ramGv *prometheus.GaugeVec
  471. gpuGv *prometheus.GaugeVec
  472. pvGv *prometheus.GaugeVec
  473. spotGv *prometheus.GaugeVec
  474. totalGv *prometheus.GaugeVec
  475. ramAllocGv *prometheus.GaugeVec
  476. cpuAllocGv *prometheus.GaugeVec
  477. gpuAllocGv *prometheus.GaugeVec
  478. pvAllocGv *prometheus.GaugeVec
  479. networkZoneEgressCostG prometheus.Gauge
  480. networkRegionEgressCostG prometheus.Gauge
  481. networkInternetEgressCostG prometheus.Gauge
  482. clusterManagementCostGv *prometheus.GaugeVec
  483. lbCostGv *prometheus.GaugeVec
  484. )
  485. // initCostModelMetrics uses a sync.Once to ensure that these metrics are only created once
  486. func initCostModelMetrics(clusterCache clustercache.ClusterCache, provider cloud.Provider) {
  487. metricsInit.Do(func() {
  488. cpuGv = prometheus.NewGaugeVec(prometheus.GaugeOpts{
  489. Name: "node_cpu_hourly_cost",
  490. Help: "node_cpu_hourly_cost hourly cost for each cpu on this node",
  491. }, []string{"instance", "node", "instance_type", "region", "provider_id"})
  492. ramGv = prometheus.NewGaugeVec(prometheus.GaugeOpts{
  493. Name: "node_ram_hourly_cost",
  494. Help: "node_ram_hourly_cost hourly cost for each gb of ram on this node",
  495. }, []string{"instance", "node", "instance_type", "region", "provider_id"})
  496. gpuGv = prometheus.NewGaugeVec(prometheus.GaugeOpts{
  497. Name: "node_gpu_hourly_cost",
  498. Help: "node_gpu_hourly_cost hourly cost for each gpu on this node",
  499. }, []string{"instance", "node", "instance_type", "region", "provider_id"})
  500. pvGv = prometheus.NewGaugeVec(prometheus.GaugeOpts{
  501. Name: "pv_hourly_cost",
  502. Help: "pv_hourly_cost Cost per GB per hour on a persistent disk",
  503. }, []string{"volumename", "persistentvolume", "provider_id"})
  504. spotGv = prometheus.NewGaugeVec(prometheus.GaugeOpts{
  505. Name: "kubecost_node_is_spot",
  506. Help: "kubecost_node_is_spot Cloud provider info about node preemptibility",
  507. }, []string{"instance", "node", "instance_type", "region", "provider_id"})
  508. totalGv = prometheus.NewGaugeVec(prometheus.GaugeOpts{
  509. Name: "node_total_hourly_cost",
  510. Help: "node_total_hourly_cost Total node cost per hour",
  511. }, []string{"instance", "node", "instance_type", "region", "provider_id"})
  512. ramAllocGv = prometheus.NewGaugeVec(prometheus.GaugeOpts{
  513. Name: "container_memory_allocation_bytes",
  514. Help: "container_memory_allocation_bytes Bytes of RAM used",
  515. }, []string{"namespace", "pod", "container", "instance", "node"})
  516. cpuAllocGv = prometheus.NewGaugeVec(prometheus.GaugeOpts{
  517. Name: "container_cpu_allocation",
  518. Help: "container_cpu_allocation Percent of a single CPU used in a minute",
  519. }, []string{"namespace", "pod", "container", "instance", "node"})
  520. gpuAllocGv = prometheus.NewGaugeVec(prometheus.GaugeOpts{
  521. Name: "container_gpu_allocation",
  522. Help: "container_gpu_allocation GPU used",
  523. }, []string{"namespace", "pod", "container", "instance", "node"})
  524. pvAllocGv = prometheus.NewGaugeVec(prometheus.GaugeOpts{
  525. Name: "pod_pvc_allocation",
  526. Help: "pod_pvc_allocation Bytes used by a PVC attached to a pod",
  527. }, []string{"namespace", "pod", "persistentvolumeclaim", "persistentvolume"})
  528. networkZoneEgressCostG = prometheus.NewGauge(prometheus.GaugeOpts{
  529. Name: "kubecost_network_zone_egress_cost",
  530. Help: "kubecost_network_zone_egress_cost Total cost per GB egress across zones",
  531. })
  532. networkRegionEgressCostG = prometheus.NewGauge(prometheus.GaugeOpts{
  533. Name: "kubecost_network_region_egress_cost",
  534. Help: "kubecost_network_region_egress_cost Total cost per GB egress across regions",
  535. })
  536. networkInternetEgressCostG = prometheus.NewGauge(prometheus.GaugeOpts{
  537. Name: "kubecost_network_internet_egress_cost",
  538. Help: "kubecost_network_internet_egress_cost Total cost per GB of internet egress.",
  539. })
  540. clusterManagementCostGv = prometheus.NewGaugeVec(prometheus.GaugeOpts{
  541. Name: "kubecost_cluster_management_cost",
  542. Help: "kubecost_cluster_management_cost Hourly cost paid as a cluster management fee.",
  543. }, []string{"provisioner_name"})
  544. lbCostGv = prometheus.NewGaugeVec(prometheus.GaugeOpts{ // no differentiation between ELB and ALB right now
  545. Name: "kubecost_load_balancer_cost",
  546. Help: "kubecost_load_balancer_cost Hourly cost of load balancer",
  547. }, []string{"ingress_ip", "namespace", "service_name"}) // assumes one ingress IP per load balancer
  548. // Register cost-model metrics for emission
  549. prometheus.MustRegister(cpuGv, ramGv, gpuGv, totalGv, pvGv, spotGv)
  550. prometheus.MustRegister(ramAllocGv, cpuAllocGv, gpuAllocGv, pvAllocGv)
  551. prometheus.MustRegister(networkZoneEgressCostG, networkRegionEgressCostG, networkInternetEgressCostG)
  552. prometheus.MustRegister(clusterManagementCostGv, lbCostGv)
  553. // General Metric Collectors
  554. prometheus.MustRegister(ServiceCollector{
  555. KubeClusterCache: clusterCache,
  556. })
  557. prometheus.MustRegister(DeploymentCollector{
  558. KubeClusterCache: clusterCache,
  559. })
  560. prometheus.MustRegister(StatefulsetCollector{
  561. KubeClusterCache: clusterCache,
  562. })
  563. prometheus.MustRegister(ClusterInfoCollector{
  564. KubeClientSet: clusterCache.GetClient(),
  565. Cloud: provider,
  566. })
  567. if env.IsEmitNamespaceAnnotationsMetric() {
  568. prometheus.MustRegister(NamespaceAnnotationCollector{
  569. KubeClusterCache: clusterCache,
  570. })
  571. }
  572. if env.IsEmitPodAnnotationsMetric() {
  573. prometheus.MustRegister(PodAnnotationCollector{
  574. KubeClusterCache: clusterCache,
  575. })
  576. }
  577. })
  578. }
  579. //--------------------------------------------------------------------------
  580. // CostModelMetricsEmitter
  581. //--------------------------------------------------------------------------
  582. // CostModelMetricsEmitter emits all cost-model specific metrics calculated by
  583. // the CostModel.ComputeCostData() method.
  584. type CostModelMetricsEmitter struct {
  585. PrometheusClient promclient.Client
  586. KubeClusterCache clustercache.ClusterCache
  587. CloudProvider cloud.Provider
  588. Model *CostModel
  589. // Metrics
  590. CPUPriceRecorder *prometheus.GaugeVec
  591. RAMPriceRecorder *prometheus.GaugeVec
  592. PersistentVolumePriceRecorder *prometheus.GaugeVec
  593. GPUPriceRecorder *prometheus.GaugeVec
  594. PVAllocationRecorder *prometheus.GaugeVec
  595. NodeSpotRecorder *prometheus.GaugeVec
  596. NodeTotalPriceRecorder *prometheus.GaugeVec
  597. RAMAllocationRecorder *prometheus.GaugeVec
  598. CPUAllocationRecorder *prometheus.GaugeVec
  599. GPUAllocationRecorder *prometheus.GaugeVec
  600. ClusterManagementCostRecorder *prometheus.GaugeVec
  601. LBCostRecorder *prometheus.GaugeVec
  602. NetworkZoneEgressRecorder prometheus.Gauge
  603. NetworkRegionEgressRecorder prometheus.Gauge
  604. NetworkInternetEgressRecorder prometheus.Gauge
  605. // Flow Control
  606. recordingLock *sync.Mutex
  607. recordingStopping bool
  608. recordingStop chan bool
  609. }
  610. // NewCostModelMetricsEmitter creates a new cost-model metrics emitter. Use Start() to begin metric emission.
  611. func NewCostModelMetricsEmitter(promClient promclient.Client, clusterCache clustercache.ClusterCache, provider cloud.Provider, model *CostModel) *CostModelMetricsEmitter {
  612. // init will only actually execute once to register the custom gauges
  613. initCostModelMetrics(clusterCache, provider)
  614. return &CostModelMetricsEmitter{
  615. PrometheusClient: promClient,
  616. KubeClusterCache: clusterCache,
  617. CloudProvider: provider,
  618. Model: model,
  619. CPUPriceRecorder: cpuGv,
  620. RAMPriceRecorder: ramGv,
  621. GPUPriceRecorder: gpuGv,
  622. PersistentVolumePriceRecorder: pvGv,
  623. NodeSpotRecorder: spotGv,
  624. NodeTotalPriceRecorder: totalGv,
  625. RAMAllocationRecorder: ramAllocGv,
  626. CPUAllocationRecorder: cpuAllocGv,
  627. GPUAllocationRecorder: gpuAllocGv,
  628. PVAllocationRecorder: pvAllocGv,
  629. NetworkZoneEgressRecorder: networkZoneEgressCostG,
  630. NetworkRegionEgressRecorder: networkRegionEgressCostG,
  631. NetworkInternetEgressRecorder: networkInternetEgressCostG,
  632. ClusterManagementCostRecorder: clusterManagementCostGv,
  633. LBCostRecorder: lbCostGv,
  634. recordingLock: new(sync.Mutex),
  635. recordingStopping: false,
  636. recordingStop: nil,
  637. }
  638. }
  639. // Checks to see if there is a metric recording stop channel. If it exists, a new
  640. // channel is not created and false is returned. If it doesn't exist, a new channel
  641. // is created and true is returned.
  642. func (cmme *CostModelMetricsEmitter) checkOrCreateRecordingChan() bool {
  643. cmme.recordingLock.Lock()
  644. defer cmme.recordingLock.Unlock()
  645. if cmme.recordingStop != nil {
  646. return false
  647. }
  648. cmme.recordingStop = make(chan bool, 1)
  649. return true
  650. }
  651. // IsRunning returns true if metric recording is running.
  652. func (cmme *CostModelMetricsEmitter) IsRunning() bool {
  653. cmme.recordingLock.Lock()
  654. defer cmme.recordingLock.Unlock()
  655. return cmme.recordingStop != nil
  656. }
  657. // StartCostModelMetricRecording starts the go routine that emits metrics used to determine
  658. // cluster costs.
  659. func (cmme *CostModelMetricsEmitter) Start() bool {
  660. // Check to see if we're already recording
  661. // This function will create the stop recording channel and return true
  662. // if it doesn't exist.
  663. if !cmme.checkOrCreateRecordingChan() {
  664. log.Errorf("Attempted to start cost model metric recording when it's already running.")
  665. return false
  666. }
  667. go func() {
  668. defer errors.HandlePanic()
  669. containerSeen := make(map[string]bool)
  670. nodeSeen := make(map[string]bool)
  671. loadBalancerSeen := make(map[string]bool)
  672. pvSeen := make(map[string]bool)
  673. pvcSeen := make(map[string]bool)
  674. getKeyFromLabelStrings := func(labels ...string) string {
  675. return strings.Join(labels, ",")
  676. }
  677. getLabelStringsFromKey := func(key string) []string {
  678. return strings.Split(key, ",")
  679. }
  680. var defaultRegion string = ""
  681. nodeList := cmme.KubeClusterCache.GetAllNodes()
  682. if len(nodeList) > 0 {
  683. defaultRegion = nodeList[0].Labels[v1.LabelZoneRegion]
  684. }
  685. for {
  686. klog.V(4).Info("Recording prices...")
  687. podlist := cmme.KubeClusterCache.GetAllPods()
  688. podStatus := make(map[string]v1.PodPhase)
  689. for _, pod := range podlist {
  690. podStatus[pod.Name] = pod.Status.Phase
  691. }
  692. cfg, _ := cmme.CloudProvider.GetConfig()
  693. provisioner, clusterManagementCost, err := cmme.CloudProvider.ClusterManagementPricing()
  694. if err != nil {
  695. klog.V(1).Infof("Error getting cluster management cost %s", err.Error())
  696. }
  697. cmme.ClusterManagementCostRecorder.WithLabelValues(provisioner).Set(clusterManagementCost)
  698. // Record network pricing at global scope
  699. networkCosts, err := cmme.CloudProvider.NetworkPricing()
  700. if err != nil {
  701. klog.V(4).Infof("Failed to retrieve network costs: %s", err.Error())
  702. } else {
  703. cmme.NetworkZoneEgressRecorder.Set(networkCosts.ZoneNetworkEgressCost)
  704. cmme.NetworkRegionEgressRecorder.Set(networkCosts.RegionNetworkEgressCost)
  705. cmme.NetworkInternetEgressRecorder.Set(networkCosts.InternetNetworkEgressCost)
  706. }
  707. // TODO: Pass PrometheusClient and CloudProvider into CostModel on instantiation so this isn't so awkward
  708. data, err := cmme.Model.ComputeCostData(cmme.PrometheusClient, cmme.CloudProvider, "2m", "", "")
  709. if err != nil {
  710. // For an error collection, we'll just log the length of the errors (ComputeCostData already logs the
  711. // actual errors)
  712. if prom.IsErrorCollection(err) {
  713. if ec, ok := err.(prom.QueryErrorCollection); ok {
  714. log.Errorf("Error in price recording: %d errors occurred", len(ec.Errors()))
  715. }
  716. } else {
  717. log.Errorf("Error in price recording: " + err.Error())
  718. }
  719. // zero the for loop so the time.Sleep will still work
  720. data = map[string]*CostData{}
  721. }
  722. // TODO: Pass CloudProvider into CostModel on instantiation so this isn't so awkward
  723. nodes, err := cmme.Model.GetNodeCost(cmme.CloudProvider)
  724. for nodeName, node := range nodes {
  725. // Emit costs, guarding against NaN inputs for custom pricing.
  726. cpuCost, _ := strconv.ParseFloat(node.VCPUCost, 64)
  727. if math.IsNaN(cpuCost) || math.IsInf(cpuCost, 0) {
  728. cpuCost, _ = strconv.ParseFloat(cfg.CPU, 64)
  729. if math.IsNaN(cpuCost) || math.IsInf(cpuCost, 0) {
  730. cpuCost = 0
  731. }
  732. }
  733. cpu, _ := strconv.ParseFloat(node.VCPU, 64)
  734. if math.IsNaN(cpu) || math.IsInf(cpu, 0) {
  735. cpu = 1 // Assume 1 CPU
  736. }
  737. ramCost, _ := strconv.ParseFloat(node.RAMCost, 64)
  738. if math.IsNaN(ramCost) || math.IsInf(ramCost, 0) {
  739. ramCost, _ = strconv.ParseFloat(cfg.RAM, 64)
  740. if math.IsNaN(ramCost) || math.IsInf(ramCost, 0) {
  741. ramCost = 0
  742. }
  743. }
  744. ram, _ := strconv.ParseFloat(node.RAMBytes, 64)
  745. if math.IsNaN(ram) || math.IsInf(ram, 0) {
  746. ram = 0
  747. }
  748. gpu, _ := strconv.ParseFloat(node.GPU, 64)
  749. if math.IsNaN(gpu) || math.IsInf(gpu, 0) {
  750. gpu = 0
  751. }
  752. gpuCost, _ := strconv.ParseFloat(node.GPUCost, 64)
  753. if math.IsNaN(gpuCost) || math.IsInf(gpuCost, 0) {
  754. gpuCost, _ = strconv.ParseFloat(cfg.GPU, 64)
  755. if math.IsNaN(gpuCost) || math.IsInf(gpuCost, 0) {
  756. gpuCost = 0
  757. }
  758. }
  759. nodeType := node.InstanceType
  760. nodeRegion := node.Region
  761. totalCost := cpu*cpuCost + ramCost*(ram/1024/1024/1024) + gpu*gpuCost
  762. cmme.CPUPriceRecorder.WithLabelValues(nodeName, nodeName, nodeType, nodeRegion, node.ProviderID).Set(cpuCost)
  763. cmme.RAMPriceRecorder.WithLabelValues(nodeName, nodeName, nodeType, nodeRegion, node.ProviderID).Set(ramCost)
  764. cmme.GPUPriceRecorder.WithLabelValues(nodeName, nodeName, nodeType, nodeRegion, node.ProviderID).Set(gpuCost)
  765. cmme.NodeTotalPriceRecorder.WithLabelValues(nodeName, nodeName, nodeType, nodeRegion, node.ProviderID).Set(totalCost)
  766. if node.IsSpot() {
  767. cmme.NodeSpotRecorder.WithLabelValues(nodeName, nodeName, nodeType, nodeRegion, node.ProviderID).Set(1.0)
  768. } else {
  769. cmme.NodeSpotRecorder.WithLabelValues(nodeName, nodeName, nodeType, nodeRegion, node.ProviderID).Set(0.0)
  770. }
  771. labelKey := getKeyFromLabelStrings(nodeName, nodeName, nodeType, nodeRegion, node.ProviderID)
  772. nodeSeen[labelKey] = true
  773. }
  774. // TODO: Pass CloudProvider into CostModel on instantiation so this isn't so awkward
  775. loadBalancers, err := cmme.Model.GetLBCost(cmme.CloudProvider)
  776. for lbKey, lb := range loadBalancers {
  777. // TODO: parse (if necessary) and calculate cost associated with loadBalancer based on dynamic cloud prices fetched into each lb struct on GetLBCost() call
  778. keyParts := getLabelStringsFromKey(lbKey)
  779. namespace := keyParts[0]
  780. serviceName := keyParts[1]
  781. ingressIP := ""
  782. if len(lb.IngressIPAddresses) > 0 {
  783. ingressIP = lb.IngressIPAddresses[0] // assumes one ingress IP per load balancer
  784. }
  785. cmme.LBCostRecorder.WithLabelValues(ingressIP, namespace, serviceName).Set(lb.Cost)
  786. labelKey := getKeyFromLabelStrings(namespace, serviceName)
  787. loadBalancerSeen[labelKey] = true
  788. }
  789. for _, costs := range data {
  790. nodeName := costs.NodeName
  791. namespace := costs.Namespace
  792. podName := costs.PodName
  793. containerName := costs.Name
  794. if costs.PVCData != nil {
  795. for _, pvc := range costs.PVCData {
  796. if pvc.Volume != nil {
  797. timesClaimed := pvc.TimesClaimed
  798. if timesClaimed == 0 {
  799. timesClaimed = 1 // unallocated PVs are unclaimed but have a full allocation
  800. }
  801. cmme.PVAllocationRecorder.WithLabelValues(namespace, podName, pvc.Claim, pvc.VolumeName).Set(pvc.Values[0].Value / float64(timesClaimed))
  802. labelKey := getKeyFromLabelStrings(namespace, podName, pvc.Claim, pvc.VolumeName)
  803. pvcSeen[labelKey] = true
  804. }
  805. }
  806. }
  807. if len(costs.RAMAllocation) > 0 {
  808. cmme.RAMAllocationRecorder.WithLabelValues(namespace, podName, containerName, nodeName, nodeName).Set(costs.RAMAllocation[0].Value)
  809. }
  810. if len(costs.CPUAllocation) > 0 {
  811. cmme.CPUAllocationRecorder.WithLabelValues(namespace, podName, containerName, nodeName, nodeName).Set(costs.CPUAllocation[0].Value)
  812. }
  813. if len(costs.GPUReq) > 0 {
  814. // allocation here is set to the request because shared GPU usage not yet supported.
  815. cmme.GPUAllocationRecorder.WithLabelValues(namespace, podName, containerName, nodeName, nodeName).Set(costs.GPUReq[0].Value)
  816. }
  817. labelKey := getKeyFromLabelStrings(namespace, podName, containerName, nodeName, nodeName)
  818. if podStatus[podName] == v1.PodRunning { // Only report data for current pods
  819. containerSeen[labelKey] = true
  820. } else {
  821. containerSeen[labelKey] = false
  822. }
  823. storageClasses := cmme.KubeClusterCache.GetAllStorageClasses()
  824. storageClassMap := make(map[string]map[string]string)
  825. for _, storageClass := range storageClasses {
  826. params := storageClass.Parameters
  827. storageClassMap[storageClass.ObjectMeta.Name] = params
  828. if storageClass.GetAnnotations()["storageclass.kubernetes.io/is-default-class"] == "true" || storageClass.GetAnnotations()["storageclass.beta.kubernetes.io/is-default-class"] == "true" {
  829. storageClassMap["default"] = params
  830. storageClassMap[""] = params
  831. }
  832. }
  833. pvs := cmme.KubeClusterCache.GetAllPersistentVolumes()
  834. for _, pv := range pvs {
  835. parameters, ok := storageClassMap[pv.Spec.StorageClassName]
  836. if !ok {
  837. klog.V(4).Infof("Unable to find parameters for storage class \"%s\". Does pv \"%s\" have a storageClassName?", pv.Spec.StorageClassName, pv.Name)
  838. }
  839. var region string
  840. if r, ok := pv.Labels[v1.LabelZoneRegion]; ok {
  841. region = r
  842. } else {
  843. region = defaultRegion
  844. }
  845. cacPv := &cloud.PV{
  846. Class: pv.Spec.StorageClassName,
  847. Region: region,
  848. Parameters: parameters,
  849. }
  850. // TODO: GetPVCost should be a method in CostModel?
  851. GetPVCost(cacPv, pv, cmme.CloudProvider, region)
  852. c, _ := strconv.ParseFloat(cacPv.Cost, 64)
  853. cmme.PersistentVolumePriceRecorder.WithLabelValues(pv.Name, pv.Name, cacPv.ProviderID).Set(c)
  854. labelKey := getKeyFromLabelStrings(pv.Name, pv.Name)
  855. pvSeen[labelKey] = true
  856. }
  857. }
  858. for labelString, seen := range nodeSeen {
  859. if !seen {
  860. klog.V(4).Infof("Removing %s from nodes", labelString)
  861. labels := getLabelStringsFromKey(labelString)
  862. ok := cmme.NodeTotalPriceRecorder.DeleteLabelValues(labels...)
  863. if ok {
  864. klog.V(4).Infof("removed %s from totalprice", labelString)
  865. } else {
  866. klog.Infof("FAILURE TO REMOVE %s from totalprice", labelString)
  867. }
  868. ok = cmme.NodeSpotRecorder.DeleteLabelValues(labels...)
  869. if ok {
  870. klog.V(4).Infof("removed %s from spot records", labelString)
  871. } else {
  872. klog.Infof("FAILURE TO REMOVE %s from spot records", labelString)
  873. }
  874. ok = cmme.CPUPriceRecorder.DeleteLabelValues(labels...)
  875. if ok {
  876. klog.V(4).Infof("removed %s from cpuprice", labelString)
  877. } else {
  878. klog.Infof("FAILURE TO REMOVE %s from cpuprice", labelString)
  879. }
  880. ok = cmme.GPUPriceRecorder.DeleteLabelValues(labels...)
  881. if ok {
  882. klog.V(4).Infof("removed %s from gpuprice", labelString)
  883. } else {
  884. klog.Infof("FAILURE TO REMOVE %s from gpuprice", labelString)
  885. }
  886. ok = cmme.RAMPriceRecorder.DeleteLabelValues(labels...)
  887. if ok {
  888. klog.V(4).Infof("removed %s from ramprice", labelString)
  889. } else {
  890. klog.Infof("FAILURE TO REMOVE %s from ramprice", labelString)
  891. }
  892. delete(nodeSeen, labelString)
  893. } else {
  894. nodeSeen[labelString] = false
  895. }
  896. }
  897. for labelString, seen := range loadBalancerSeen {
  898. if !seen {
  899. labels := getLabelStringsFromKey(labelString)
  900. cmme.LBCostRecorder.DeleteLabelValues(labels...)
  901. } else {
  902. loadBalancerSeen[labelString] = false
  903. }
  904. }
  905. for labelString, seen := range containerSeen {
  906. if !seen {
  907. labels := getLabelStringsFromKey(labelString)
  908. cmme.RAMAllocationRecorder.DeleteLabelValues(labels...)
  909. cmme.CPUAllocationRecorder.DeleteLabelValues(labels...)
  910. cmme.GPUAllocationRecorder.DeleteLabelValues(labels...)
  911. delete(containerSeen, labelString)
  912. } else {
  913. containerSeen[labelString] = false
  914. }
  915. }
  916. for labelString, seen := range pvSeen {
  917. if !seen {
  918. labels := getLabelStringsFromKey(labelString)
  919. cmme.PersistentVolumePriceRecorder.DeleteLabelValues(labels...)
  920. delete(pvSeen, labelString)
  921. } else {
  922. pvSeen[labelString] = false
  923. }
  924. }
  925. for labelString, seen := range pvcSeen {
  926. if !seen {
  927. labels := getLabelStringsFromKey(labelString)
  928. cmme.PVAllocationRecorder.DeleteLabelValues(labels...)
  929. delete(pvcSeen, labelString)
  930. } else {
  931. pvcSeen[labelString] = false
  932. }
  933. }
  934. select {
  935. case <-time.After(time.Minute):
  936. case <-cmme.recordingStop:
  937. cmme.recordingLock.Lock()
  938. cmme.recordingStopping = false
  939. cmme.recordingStop = nil
  940. cmme.recordingLock.Unlock()
  941. return
  942. }
  943. }
  944. }()
  945. return true
  946. }
  947. // Stop halts the metrics emission loop after the current emission is completed
  948. // or if the emission is paused.
  949. func (cmme *CostModelMetricsEmitter) Stop() {
  950. cmme.recordingLock.Lock()
  951. defer cmme.recordingLock.Unlock()
  952. if !cmme.recordingStopping && cmme.recordingStop != nil {
  953. cmme.recordingStopping = true
  954. close(cmme.recordingStop)
  955. }
  956. }