ramallocation.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. package synthetic
  2. import (
  3. "maps"
  4. "math"
  5. "time"
  6. "github.com/opencost/opencost/core/pkg/log"
  7. "github.com/opencost/opencost/core/pkg/source"
  8. "github.com/opencost/opencost/modules/collector-source/pkg/metric"
  9. )
  10. // ContainerMemoryAllocationMetric is the grouping unit for memory usage and request
  11. type ContainerMemoryAllocationMetric struct {
  12. requestMetric *metric.Update
  13. usageMetric *metric.Update
  14. }
  15. // Synthesize returns a new ContainerMemoryAllocationBytes metric update with the max(request, usage)
  16. func (cmam *ContainerMemoryAllocationMetric) Synthesize() metric.Update {
  17. if cmam.requestMetric != nil && cmam.usageMetric != nil {
  18. req := cmam.requestMetric.Value
  19. if math.IsNaN(req) {
  20. log.Debugf("NaN value found during memory allocation synthesis for requests.")
  21. req = 0.0
  22. }
  23. used := cmam.usageMetric.Value
  24. if math.IsNaN(used) {
  25. log.Debugf("NaN value found during memory allocation synthesis for used.")
  26. used = 0.0
  27. }
  28. // TODO: validate and merge labels if they both have keys?
  29. labels := maps.Clone(cmam.usageMetric.Labels)
  30. return metric.Update{
  31. Name: metric.ContainerMemoryAllocationBytes,
  32. Labels: labels,
  33. Value: max(req, used),
  34. }
  35. } else if cmam.requestMetric != nil {
  36. req := cmam.requestMetric.Value
  37. if math.IsNaN(req) {
  38. log.Debugf("NaN value found during memory allocation synthesis for requests.")
  39. req = 0.0
  40. }
  41. // drop the "extra" labels
  42. labels := maps.Clone(cmam.requestMetric.Labels)
  43. delete(labels, source.ResourceLabel)
  44. delete(labels, source.UnitLabel)
  45. return metric.Update{
  46. Name: metric.ContainerMemoryAllocationBytes,
  47. Labels: labels,
  48. Value: req,
  49. }
  50. }
  51. // not possible for both request and usage to be nil, so we can assume only used is
  52. // valid here
  53. used := cmam.usageMetric.Value
  54. if math.IsNaN(used) {
  55. log.Debugf("NaN value found during memory allocation synthesis for used.")
  56. used = 0.0
  57. }
  58. labels := maps.Clone(cmam.usageMetric.Labels)
  59. return metric.Update{
  60. Name: metric.ContainerMemoryAllocationBytes,
  61. Labels: labels,
  62. Value: used,
  63. }
  64. }
  65. // ContainerMemoryAllocationSynthesizer is a MetricSynthesizer that leverages pod uid and container name grouping
  66. // to match relevant request and usage metrics to build the memory allocation data.
  67. type ContainerMemoryAllocationSynthesizer struct {
  68. byPod map[string]map[string]*ContainerMemoryAllocationMetric
  69. }
  70. // NewContainerMemoryAllocationSynthesizer creates a new ContainerMemoryAllocationSynthesizer which synthesizes
  71. // metric updates for ContainerMemoryAllocationBytes from ram requests and ram usage metrics.
  72. func NewContainerMemoryAllocationSynthesizer() *ContainerMemoryAllocationSynthesizer {
  73. return &ContainerMemoryAllocationSynthesizer{
  74. byPod: make(map[string]map[string]*ContainerMemoryAllocationMetric),
  75. }
  76. }
  77. // Process accepts metric updates and only records updates relevant to memory allocation.
  78. func (cmas *ContainerMemoryAllocationSynthesizer) Process(t time.Time, update *metric.Update) {
  79. switch update.Name {
  80. case metric.KubePodContainerResourceRequests:
  81. cmas.addRequestsMetric(update)
  82. case metric.ContainerMemoryWorkingSetBytes:
  83. cmas.addUsageMetric(update)
  84. }
  85. }
  86. // Synthesize generates all new memory allocation metrics
  87. func (cmas *ContainerMemoryAllocationSynthesizer) Synthesize() []metric.Update {
  88. var updates []metric.Update
  89. for _, pod := range cmas.byPod {
  90. for _, synthesizer := range pod {
  91. updates = append(updates, synthesizer.Synthesize())
  92. }
  93. }
  94. return updates
  95. }
  96. // Clear drops the current metric mapping and creates a new map ready to process next metrics collection.
  97. func (cmas *ContainerMemoryAllocationSynthesizer) Clear() {
  98. cmas.byPod = make(map[string]map[string]*ContainerMemoryAllocationMetric)
  99. }
  100. func (cmas *ContainerMemoryAllocationSynthesizer) addRequestsMetric(update *metric.Update) {
  101. if !cmas.isValidRequests(update.Labels) {
  102. return
  103. }
  104. podUID := update.Labels[source.UIDLabel]
  105. container := update.Labels[source.ContainerLabel]
  106. if _, ok := cmas.byPod[podUID]; !ok {
  107. cmas.byPod[podUID] = make(map[string]*ContainerMemoryAllocationMetric)
  108. }
  109. if _, ok := cmas.byPod[podUID][container]; !ok {
  110. cmas.byPod[podUID][container] = &ContainerMemoryAllocationMetric{
  111. requestMetric: update,
  112. }
  113. } else {
  114. cmas.byPod[podUID][container].requestMetric = update
  115. }
  116. }
  117. func (cmas *ContainerMemoryAllocationSynthesizer) addUsageMetric(update *metric.Update) {
  118. if !cmas.isValidUsage(update.Labels) {
  119. return
  120. }
  121. podUID := update.Labels[source.UIDLabel]
  122. container := update.Labels[source.ContainerLabel]
  123. if _, ok := cmas.byPod[podUID]; !ok {
  124. cmas.byPod[podUID] = make(map[string]*ContainerMemoryAllocationMetric)
  125. }
  126. if _, ok := cmas.byPod[podUID][container]; !ok {
  127. cmas.byPod[podUID][container] = &ContainerMemoryAllocationMetric{
  128. usageMetric: update,
  129. }
  130. } else {
  131. cmas.byPod[podUID][container].usageMetric = update
  132. }
  133. }
  134. func (cmas *ContainerMemoryAllocationSynthesizer) isValidRequests(labels map[string]string) bool {
  135. return labels[source.ResourceLabel] == "memory" &&
  136. labels[source.UnitLabel] == "byte" &&
  137. labels[source.ContainerLabel] != "POD" &&
  138. labels[source.ContainerLabel] != "" &&
  139. labels[source.NodeLabel] != "" &&
  140. labels[source.UIDLabel] != ""
  141. }
  142. func (cmas *ContainerMemoryAllocationSynthesizer) isValidUsage(labels map[string]string) bool {
  143. return labels[source.ContainerLabel] != "POD" &&
  144. labels[source.ContainerLabel] != "" &&
  145. labels[source.UIDLabel] != ""
  146. }