statsummary.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. package scrape
  2. import (
  3. "github.com/opencost/opencost/core/pkg/log"
  4. "github.com/opencost/opencost/core/pkg/nodestats"
  5. "github.com/opencost/opencost/core/pkg/source"
  6. "github.com/opencost/opencost/modules/collector-source/pkg/metric"
  7. stats "k8s.io/kubelet/pkg/apis/stats/v1alpha1"
  8. )
  9. // Stat Summary Metrics
  10. const (
  11. NodeCPUSecondsTotal = "node_cpu_seconds_total"
  12. NodeFSCapacityBytes = "node_fs_capacity_bytes" // replaces container_fs_limit_bytes
  13. ContainerNetworkReceiveBytesTotal = "container_network_receive_bytes_total"
  14. ContainerNetworkTransmitBytesTotal = "container_network_transmit_bytes_total"
  15. ContainerCPUUsageSecondsTotal = "container_cpu_usage_seconds_total"
  16. ContainerMemoryWorkingSetBytes = "container_memory_working_set_bytes"
  17. ContainerFSUsageBytes = "container_fs_usage_bytes"
  18. KubeletVolumeStatsUsedBytes = "kubelet_volume_stats_used_bytes"
  19. )
  20. type StatSummaryScraper struct {
  21. client nodestats.StatSummaryClient
  22. }
  23. func newStatSummaryScraper(client nodestats.StatSummaryClient) Scraper {
  24. return &StatSummaryScraper{
  25. client: client,
  26. }
  27. }
  28. func (s *StatSummaryScraper) Scrape() []metric.Update {
  29. var scrapeResults []metric.Update
  30. nodeStats, err := s.client.GetNodeData()
  31. if err != nil {
  32. log.Errorf("error retrieving node stat data: %s", err.Error())
  33. return scrapeResults
  34. }
  35. // track if a pvc has already been seen when updating KubeletVolumeStatsUsedBytes
  36. seenPVC := map[stats.PVCReference]struct{}{}
  37. for _, stat := range nodeStats {
  38. nodeName := stat.Node.NodeName
  39. if stat.Node.CPU != nil && stat.Node.CPU.UsageCoreNanoSeconds != nil {
  40. scrapeResults = append(scrapeResults, metric.Update{
  41. Name: NodeCPUSecondsTotal,
  42. Labels: map[string]string{
  43. source.KubernetesNodeLabel: nodeName,
  44. source.ModeLabel: "", // TODO
  45. },
  46. Value: float64(*stat.Node.CPU.UsageCoreNanoSeconds) * 1e-9,
  47. })
  48. }
  49. if stat.Node.Fs != nil && stat.Node.Fs.CapacityBytes != nil {
  50. scrapeResults = append(scrapeResults, metric.Update{
  51. Name: NodeFSCapacityBytes,
  52. Labels: map[string]string{
  53. source.InstanceLabel: nodeName,
  54. source.DeviceLabel: "local", // This value has to be populated but isn't important here
  55. },
  56. Value: float64(*stat.Node.Fs.CapacityBytes),
  57. })
  58. }
  59. for _, pod := range stat.Pods {
  60. podName := pod.PodRef.Name
  61. namespace := pod.PodRef.Namespace
  62. podUID := pod.PodRef.UID
  63. if pod.Network != nil {
  64. networkLabels := map[string]string{
  65. source.UIDLabel: podUID,
  66. source.PodLabel: podName,
  67. source.NamespaceLabel: namespace,
  68. }
  69. // The network may contain a list of stats or itself be a single stat, if the list is not present
  70. // scrape the object itself
  71. if pod.Network.Interfaces != nil {
  72. for _, networkStat := range pod.Network.Interfaces {
  73. scrapeNetworkStats(&scrapeResults, networkLabels, networkStat)
  74. }
  75. } else {
  76. scrapeNetworkStats(&scrapeResults, networkLabels, pod.Network.InterfaceStats)
  77. }
  78. }
  79. for _, volumeStats := range pod.VolumeStats {
  80. if volumeStats.PVCRef == nil || volumeStats.UsedBytes == nil {
  81. continue
  82. }
  83. if _, ok := seenPVC[*volumeStats.PVCRef]; ok {
  84. continue
  85. }
  86. scrapeResults = append(scrapeResults, metric.Update{
  87. Name: KubeletVolumeStatsUsedBytes,
  88. Labels: map[string]string{
  89. source.PVCLabel: volumeStats.PVCRef.Name,
  90. source.NamespaceLabel: volumeStats.PVCRef.Namespace,
  91. },
  92. Value: float64(*volumeStats.UsedBytes),
  93. })
  94. seenPVC[*volumeStats.PVCRef] = struct{}{}
  95. }
  96. for _, container := range pod.Containers {
  97. if container.CPU != nil && container.CPU.UsageCoreNanoSeconds != nil {
  98. scrapeResults = append(scrapeResults, metric.Update{
  99. Name: ContainerCPUUsageSecondsTotal,
  100. Labels: map[string]string{
  101. source.ContainerLabel: container.Name,
  102. source.PodLabel: podName,
  103. source.NamespaceLabel: namespace,
  104. source.NodeLabel: nodeName,
  105. source.InstanceLabel: nodeName,
  106. },
  107. Value: float64(*container.CPU.UsageCoreNanoSeconds) * 1e-9,
  108. })
  109. }
  110. if container.Memory != nil && container.Memory.WorkingSetBytes != nil {
  111. scrapeResults = append(scrapeResults, metric.Update{
  112. Name: ContainerMemoryWorkingSetBytes,
  113. Labels: map[string]string{
  114. source.ContainerLabel: container.Name,
  115. source.PodLabel: podName,
  116. source.NamespaceLabel: namespace,
  117. source.NodeLabel: nodeName,
  118. source.InstanceLabel: nodeName,
  119. },
  120. Value: float64(*container.Memory.WorkingSetBytes),
  121. })
  122. }
  123. if container.Rootfs != nil && container.Rootfs.UsedBytes != nil {
  124. scrapeResults = append(scrapeResults, metric.Update{
  125. Name: ContainerFSUsageBytes,
  126. Labels: map[string]string{
  127. source.InstanceLabel: nodeName,
  128. source.DeviceLabel: "local",
  129. },
  130. Value: float64(*container.Rootfs.UsedBytes),
  131. })
  132. }
  133. }
  134. }
  135. }
  136. return scrapeResults
  137. }
  138. func scrapeNetworkStats(scrapeResults *[]metric.Update, labels map[string]string, networkStats stats.InterfaceStats) {
  139. // Skip stats for cni0 which tracks internal cluster traffic
  140. if networkStats.Name == "cni0" {
  141. return
  142. }
  143. if networkStats.RxBytes != nil {
  144. *scrapeResults = append(*scrapeResults, metric.Update{
  145. Name: ContainerNetworkReceiveBytesTotal,
  146. Labels: labels,
  147. Value: float64(*networkStats.RxBytes),
  148. })
  149. }
  150. if networkStats.TxBytes != nil {
  151. *scrapeResults = append(*scrapeResults, metric.Update{
  152. Name: ContainerNetworkTransmitBytesTotal,
  153. Labels: labels,
  154. Value: float64(*networkStats.TxBytes),
  155. })
  156. }
  157. }