2
0

statsummary.go 5.6 KB

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