| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- package synthetic
- import (
- "maps"
- "math"
- "time"
- "github.com/opencost/opencost/core/pkg/log"
- "github.com/opencost/opencost/core/pkg/source"
- "github.com/opencost/opencost/modules/collector-source/pkg/metric"
- )
- // ContainerMemoryAllocationMetric is the grouping unit for memory usage and request
- type ContainerMemoryAllocationMetric struct {
- requestMetric *metric.Update
- usageMetric *metric.Update
- }
- // Synthesize returns a new ContainerMemoryAllocationBytes metric update with the max(request, usage)
- func (cmam *ContainerMemoryAllocationMetric) Synthesize() metric.Update {
- if cmam.requestMetric != nil && cmam.usageMetric != nil {
- req := cmam.requestMetric.Value
- if math.IsNaN(req) {
- log.Debugf("NaN value found during memory allocation synthesis for requests.")
- req = 0.0
- }
- used := cmam.usageMetric.Value
- if math.IsNaN(used) {
- log.Debugf("NaN value found during memory allocation synthesis for used.")
- used = 0.0
- }
- // TODO: validate and merge labels if they both have keys?
- labels := maps.Clone(cmam.usageMetric.Labels)
- return metric.Update{
- Name: metric.ContainerMemoryAllocationBytes,
- Labels: labels,
- Value: max(req, used),
- }
- } else if cmam.requestMetric != nil {
- req := cmam.requestMetric.Value
- if math.IsNaN(req) {
- log.Debugf("NaN value found during memory allocation synthesis for requests.")
- req = 0.0
- }
- // drop the "extra" labels
- labels := maps.Clone(cmam.requestMetric.Labels)
- delete(labels, source.ResourceLabel)
- delete(labels, source.UnitLabel)
- return metric.Update{
- Name: metric.ContainerMemoryAllocationBytes,
- Labels: labels,
- Value: req,
- }
- }
- // not possible for both request and usage to be nil, so we can assume only used is
- // valid here
- used := cmam.usageMetric.Value
- if math.IsNaN(used) {
- log.Debugf("NaN value found during memory allocation synthesis for used.")
- used = 0.0
- }
- labels := maps.Clone(cmam.usageMetric.Labels)
- return metric.Update{
- Name: metric.ContainerMemoryAllocationBytes,
- Labels: labels,
- Value: used,
- }
- }
- // ContainerMemoryAllocationSynthesizer is a MetricSynthesizer that leverages pod uid and container name grouping
- // to match relevant request and usage metrics to build the memory allocation data.
- type ContainerMemoryAllocationSynthesizer struct {
- byPod map[string]map[string]*ContainerMemoryAllocationMetric
- }
- // NewContainerMemoryAllocationSynthesizer creates a new ContainerMemoryAllocationSynthesizer which synthesizes
- // metric updates for ContainerMemoryAllocationBytes from ram requests and ram usage metrics.
- func NewContainerMemoryAllocationSynthesizer() *ContainerMemoryAllocationSynthesizer {
- return &ContainerMemoryAllocationSynthesizer{
- byPod: make(map[string]map[string]*ContainerMemoryAllocationMetric),
- }
- }
- // Process accepts metric updates and only records updates relevant to memory allocation.
- func (cmas *ContainerMemoryAllocationSynthesizer) Process(t time.Time, update *metric.Update) {
- switch update.Name {
- case metric.KubePodContainerResourceRequests:
- cmas.addRequestsMetric(update)
- case metric.ContainerMemoryWorkingSetBytes:
- cmas.addUsageMetric(update)
- }
- }
- // Synthesize generates all new memory allocation metrics
- func (cmas *ContainerMemoryAllocationSynthesizer) Synthesize() []metric.Update {
- var updates []metric.Update
- for _, pod := range cmas.byPod {
- for _, synthesizer := range pod {
- updates = append(updates, synthesizer.Synthesize())
- }
- }
- return updates
- }
- // Clear drops the current metric mapping and creates a new map ready to process next metrics collection.
- func (cmas *ContainerMemoryAllocationSynthesizer) Clear() {
- cmas.byPod = make(map[string]map[string]*ContainerMemoryAllocationMetric)
- }
- func (cmas *ContainerMemoryAllocationSynthesizer) addRequestsMetric(update *metric.Update) {
- if !cmas.isValidRequests(update.Labels) {
- return
- }
- podUID := update.Labels[source.UIDLabel]
- container := update.Labels[source.ContainerLabel]
- if _, ok := cmas.byPod[podUID]; !ok {
- cmas.byPod[podUID] = make(map[string]*ContainerMemoryAllocationMetric)
- }
- if _, ok := cmas.byPod[podUID][container]; !ok {
- cmas.byPod[podUID][container] = &ContainerMemoryAllocationMetric{
- requestMetric: update,
- }
- } else {
- cmas.byPod[podUID][container].requestMetric = update
- }
- }
- func (cmas *ContainerMemoryAllocationSynthesizer) addUsageMetric(update *metric.Update) {
- if !cmas.isValidUsage(update.Labels) {
- return
- }
- podUID := update.Labels[source.UIDLabel]
- container := update.Labels[source.ContainerLabel]
- if _, ok := cmas.byPod[podUID]; !ok {
- cmas.byPod[podUID] = make(map[string]*ContainerMemoryAllocationMetric)
- }
- if _, ok := cmas.byPod[podUID][container]; !ok {
- cmas.byPod[podUID][container] = &ContainerMemoryAllocationMetric{
- usageMetric: update,
- }
- } else {
- cmas.byPod[podUID][container].usageMetric = update
- }
- }
- func (cmas *ContainerMemoryAllocationSynthesizer) isValidRequests(labels map[string]string) bool {
- return labels[source.ResourceLabel] == "memory" &&
- labels[source.UnitLabel] == "byte" &&
- labels[source.ContainerLabel] != "POD" &&
- labels[source.ContainerLabel] != "" &&
- labels[source.NodeLabel] != "" &&
- labels[source.UIDLabel] != ""
- }
- func (cmas *ContainerMemoryAllocationSynthesizer) isValidUsage(labels map[string]string) bool {
- return labels[source.ContainerLabel] != "POD" &&
- labels[source.ContainerLabel] != "" &&
- labels[source.UIDLabel] != ""
- }
|