Răsfoiți Sursa

add more NaN sanitization for allocation, summary allocs, and assets

Signed-off-by: saweber <saweber@gmail.com>
saweber 2 ani în urmă
părinte
comite
9d577df156

+ 61 - 0
pkg/kubecost/allocation.go

@@ -223,6 +223,8 @@ func (pv PVAllocations) Clone() PVAllocations {
 // Add adds contents of that to the calling PVAllocations
 func (pv PVAllocations) Add(that PVAllocations) PVAllocations {
 	apv := pv.Clone()
+	apv.SanitizeNaN()
+	that.SanitizeNaN()
 	if that != nil {
 		if apv == nil {
 			apv = PVAllocations{}
@@ -439,6 +441,8 @@ func ComputePercentages(toInsert *ProportionalAssetResourceCost) {
 }
 
 func (parcs ProportionalAssetResourceCosts) Add(that ProportionalAssetResourceCosts) {
+	parcs.SanitizeNaN()
+	that.SanitizeNaN()
 	for _, parc := range that {
 		// if name field is empty, we know this is a cluster level PARC aggregation
 		insertByName := true
@@ -562,6 +566,8 @@ func (scbs SharedCostBreakdowns) Insert(scb SharedCostBreakdown) {
 }
 
 func (scbs SharedCostBreakdowns) Add(that SharedCostBreakdowns) {
+	scbs.SanitizeNaN()
+	that.SanitizeNaN()
 	for _, scb := range that {
 		scbs.Insert(scb)
 	}
@@ -783,6 +789,8 @@ func (a *Allocation) TotalCost() float64 {
 		return 0.0
 	}
 
+	a.SanitizeNaN()
+
 	return a.CPUTotalCost() + a.GPUTotalCost() + a.RAMTotalCost() + a.PVTotalCost() + a.NetworkTotalCost() + a.LBTotalCost() + a.SharedTotalCost() + a.ExternalCost
 }
 
@@ -792,6 +800,8 @@ func (a *Allocation) CPUTotalCost() float64 {
 		return 0.0
 	}
 
+	a.SanitizeNaN()
+
 	return a.CPUCost + a.CPUCostAdjustment
 }
 
@@ -801,6 +811,8 @@ func (a *Allocation) GPUTotalCost() float64 {
 		return 0.0
 	}
 
+	a.SanitizeNaN()
+
 	return a.GPUCost + a.GPUCostAdjustment
 }
 
@@ -810,6 +822,8 @@ func (a *Allocation) RAMTotalCost() float64 {
 		return 0.0
 	}
 
+	a.SanitizeNaN()
+
 	return a.RAMCost + a.RAMCostAdjustment
 }
 
@@ -819,6 +833,8 @@ func (a *Allocation) PVTotalCost() float64 {
 		return 0.0
 	}
 
+	a.SanitizeNaN()
+
 	return a.PVCost() + a.PVCostAdjustment
 }
 
@@ -828,6 +844,8 @@ func (a *Allocation) NetworkTotalCost() float64 {
 		return 0.0
 	}
 
+	a.SanitizeNaN()
+
 	return a.NetworkCost + a.NetworkCostAdjustment
 }
 
@@ -843,6 +861,7 @@ func (a *Allocation) LoadBalancerTotalCost() float64 {
 		return 0.0
 	}
 
+	a.SanitizeNaN()
 	return a.LoadBalancerCost + a.LoadBalancerCostAdjustment
 }
 
@@ -852,6 +871,8 @@ func (a *Allocation) SharedTotalCost() float64 {
 		return 0.0
 	}
 
+	a.SanitizeNaN()
+
 	return a.SharedCost
 }
 
@@ -861,6 +882,8 @@ func (a *Allocation) PVCost() float64 {
 		return 0.0
 	}
 
+	a.SanitizeNaN()
+
 	cost := 0.0
 	for _, pv := range a.PVs {
 		cost += pv.Cost
@@ -874,6 +897,8 @@ func (a *Allocation) PVByteHours() float64 {
 		return 0.0
 	}
 
+	a.SanitizeNaN()
+
 	byteHours := 0.0
 	for _, pv := range a.PVs {
 		byteHours += pv.ByteHours
@@ -889,6 +914,8 @@ func (a *Allocation) CPUEfficiency() float64 {
 		return 0.0
 	}
 
+	a.SanitizeNaN()
+
 	if a.CPUCoreRequestAverage > 0 {
 		return a.CPUCoreUsageAverage / a.CPUCoreRequestAverage
 	}
@@ -908,6 +935,8 @@ func (a *Allocation) RAMEfficiency() float64 {
 		return 0.0
 	}
 
+	a.SanitizeNaN()
+
 	if a.RAMBytesRequestAverage > 0 {
 		return a.RAMBytesUsageAverage / a.RAMBytesRequestAverage
 	}
@@ -926,6 +955,8 @@ func (a *Allocation) TotalEfficiency() float64 {
 		return 0.0
 	}
 
+	a.SanitizeNaN()
+
 	if a.RAMTotalCost()+a.CPUTotalCost() > 0 {
 		ramCostEff := a.RAMEfficiency() * a.RAMTotalCost()
 		cpuCostEff := a.CPUEfficiency() * a.CPUTotalCost()
@@ -937,6 +968,12 @@ func (a *Allocation) TotalEfficiency() float64 {
 
 // CPUCores converts the Allocation's CPUCoreHours into average CPUCores
 func (a *Allocation) CPUCores() float64 {
+	if a == nil {
+		return 0.0
+	}
+
+	a.SanitizeNaN()
+
 	if a.Minutes() <= 0.0 {
 		return 0.0
 	}
@@ -945,6 +982,12 @@ func (a *Allocation) CPUCores() float64 {
 
 // RAMBytes converts the Allocation's RAMByteHours into average RAMBytes
 func (a *Allocation) RAMBytes() float64 {
+	if a == nil {
+		return 0.0
+	}
+
+	a.SanitizeNaN()
+
 	if a.Minutes() <= 0.0 {
 		return 0.0
 	}
@@ -953,6 +996,12 @@ func (a *Allocation) RAMBytes() float64 {
 
 // GPUs converts the Allocation's GPUHours into average GPUs
 func (a *Allocation) GPUs() float64 {
+	if a == nil {
+		return 0.0
+	}
+
+	a.SanitizeNaN()
+
 	if a.Minutes() <= 0.0 {
 		return 0.0
 	}
@@ -961,6 +1010,12 @@ func (a *Allocation) GPUs() float64 {
 
 // PVBytes converts the Allocation's PVByteHours into average PVBytes
 func (a *Allocation) PVBytes() float64 {
+	if a == nil {
+		return 0.0
+	}
+
+	a.SanitizeNaN()
+
 	if a.Minutes() <= 0.0 {
 		return 0.0
 	}
@@ -1080,6 +1135,9 @@ func (a *Allocation) add(that *Allocation) {
 		return
 	}
 
+	a.SanitizeNaN()
+	that.SanitizeNaN()
+
 	// Generate keys for each allocation to allow for special logic to set the controller
 	// in the case of keys matching but controllers not matching.
 	aggByForKey := []string{"cluster", "node", "namespace", "pod", "container"}
@@ -1217,6 +1275,8 @@ func (a *Allocation) add(that *Allocation) {
 func (thisLbAllocs LbAllocations) Add(thatLbAllocs LbAllocations) LbAllocations {
 	// loop through both sets of LB allocations, building a new LBAllocations that has the summed set
 	mergedLbAllocs := thisLbAllocs.Clone()
+	mergedLbAllocs.SanitizeNaN()
+	thatLbAllocs.SanitizeNaN()
 	if thatLbAllocs != nil {
 		if mergedLbAllocs == nil {
 			mergedLbAllocs = LbAllocations{}
@@ -1439,6 +1499,7 @@ func (as *AllocationSet) AggregateBy(aggregateBy []string, options *AllocationAg
 	// them to their respective sets, removing them from the set of allocations
 	// to aggregate.
 	for _, alloc := range as.Allocations {
+		alloc.SanitizeNaN()
 
 		alloc.Properties.AggregatedMetadata = options.IncludeAggregatedMetadata
 		// build a parallel set of allocations to only be used

+ 60 - 0
pkg/kubecost/asset.go

@@ -540,6 +540,7 @@ func (a *Any) SetLabels(labels AssetLabels) {
 
 // Adjustment returns the Asset's cost adjustment
 func (a *Any) GetAdjustment() float64 {
+	a.SanitizeNaN()
 	return a.Adjustment
 }
 
@@ -550,6 +551,7 @@ func (a *Any) SetAdjustment(adj float64) {
 
 // TotalCost returns the Asset's TotalCost
 func (a *Any) TotalCost() float64 {
+	a.SanitizeNaN()
 	return a.Cost + a.Adjustment
 }
 
@@ -602,6 +604,9 @@ func (a *Any) SetStartEnd(start, end time.Time) {
 func (a *Any) Add(that Asset) Asset {
 	this := a.Clone().(*Any)
 
+	this.SanitizeNaN()
+	that.SanitizeNaN()
+
 	props := a.Properties.Merge(that.GetProperties())
 	labels := a.Labels.Merge(that.GetLabels())
 
@@ -744,6 +749,7 @@ func (ca *Cloud) SetLabels(labels AssetLabels) {
 
 // Adjustment returns the Asset's adjustment value
 func (ca *Cloud) GetAdjustment() float64 {
+	ca.SanitizeNaN()
 	return ca.Adjustment
 }
 
@@ -754,6 +760,7 @@ func (ca *Cloud) SetAdjustment(adj float64) {
 
 // TotalCost returns the Asset's total cost
 func (ca *Cloud) TotalCost() float64 {
+	ca.SanitizeNaN()
 	return ca.Cost + ca.Adjustment + ca.Credit
 }
 
@@ -811,6 +818,9 @@ func (ca *Cloud) Add(a Asset) Asset {
 		return this
 	}
 
+	ca.SanitizeNaN()
+	a.SanitizeNaN()
+
 	props := ca.Properties.Merge(a.GetProperties())
 	labels := ca.Labels.Merge(a.GetLabels())
 
@@ -840,6 +850,9 @@ func (ca *Cloud) add(that *Cloud) {
 		return
 	}
 
+	ca.SanitizeNaN()
+	that.SanitizeNaN()
+
 	props := ca.Properties.Merge(that.Properties)
 	labels := ca.Labels.Merge(that.Labels)
 
@@ -987,6 +1000,7 @@ func (cm *ClusterManagement) SetLabels(props AssetLabels) {
 
 // Adjustment does not apply to ClusterManagement
 func (cm *ClusterManagement) GetAdjustment() float64 {
+	cm.SanitizeNaN()
 	return cm.Adjustment
 }
 
@@ -997,6 +1011,7 @@ func (cm *ClusterManagement) SetAdjustment(adj float64) {
 
 // TotalCost returns the Asset's total cost
 func (cm *ClusterManagement) TotalCost() float64 {
+	cm.SanitizeNaN()
 	return cm.Cost + cm.Adjustment
 }
 
@@ -1044,6 +1059,9 @@ func (cm *ClusterManagement) Add(a Asset) Asset {
 		return this
 	}
 
+	cm.SanitizeNaN()
+	a.SanitizeNaN()
+
 	props := cm.Properties.Merge(a.GetProperties())
 	labels := cm.Labels.Merge(a.GetLabels())
 
@@ -1073,6 +1091,9 @@ func (cm *ClusterManagement) add(that *ClusterManagement) {
 		return
 	}
 
+	cm.SanitizeNaN()
+	that.SanitizeNaN()
+
 	props := cm.Properties.Merge(that.Properties)
 	labels := cm.Labels.Merge(that.Labels)
 	window := cm.Window.Expand(that.Window)
@@ -1210,6 +1231,7 @@ func (d *Disk) SetLabels(labels AssetLabels) {
 
 // Adjustment returns the Asset's cost adjustment
 func (d *Disk) GetAdjustment() float64 {
+	d.SanitizeNaN()
 	return d.Adjustment
 }
 
@@ -1220,6 +1242,7 @@ func (d *Disk) SetAdjustment(adj float64) {
 
 // TotalCost returns the Asset's total cost
 func (d *Disk) TotalCost() float64 {
+	d.SanitizeNaN()
 	return d.Cost + d.Adjustment
 }
 
@@ -1289,6 +1312,9 @@ func (d *Disk) Add(a Asset) Asset {
 		return this
 	}
 
+	d.SanitizeNaN()
+	a.SanitizeNaN()
+
 	props := d.Properties.Merge(a.GetProperties())
 	labels := d.Labels.Merge(a.GetLabels())
 
@@ -1318,6 +1344,9 @@ func (d *Disk) add(that *Disk) {
 		return
 	}
 
+	d.SanitizeNaN()
+	that.SanitizeNaN()
+
 	props := d.Properties.Merge(that.Properties)
 	labels := d.Labels.Merge(that.Labels)
 	d.SetProperties(props)
@@ -1666,6 +1695,7 @@ func (n *Network) SetLabels(labels AssetLabels) {
 
 // Adjustment returns the Asset's cost adjustment
 func (n *Network) GetAdjustment() float64 {
+	n.SanitizeNaN()
 	return n.Adjustment
 }
 
@@ -1676,6 +1706,7 @@ func (n *Network) SetAdjustment(adj float64) {
 
 // TotalCost returns the Asset's total cost
 func (n *Network) TotalCost() float64 {
+	n.SanitizeNaN()
 	return n.Cost + n.Adjustment
 }
 
@@ -1745,6 +1776,9 @@ func (n *Network) Add(a Asset) Asset {
 		return this
 	}
 
+	n.SanitizeNaN()
+	a.SanitizeNaN()
+
 	props := n.Properties.Merge(a.GetProperties())
 	labels := n.Labels.Merge(a.GetLabels())
 
@@ -1774,6 +1808,9 @@ func (n *Network) add(that *Network) {
 		return
 	}
 
+	n.SanitizeNaN()
+	that.SanitizeNaN()
+
 	props := n.Properties.Merge(that.Properties)
 	labels := n.Labels.Merge(that.Labels)
 	n.SetProperties(props)
@@ -1960,6 +1997,7 @@ func (n *Node) SetLabels(labels AssetLabels) {
 
 // Adjustment returns the Asset's cost adjustment
 func (n *Node) GetAdjustment() float64 {
+	n.SanitizeNaN()
 	return n.Adjustment
 }
 
@@ -1970,6 +2008,7 @@ func (n *Node) SetAdjustment(adj float64) {
 
 // TotalCost returns the Asset's total cost
 func (n *Node) TotalCost() float64 {
+	n.SanitizeNaN()
 	return ((n.CPUCost + n.RAMCost) * (1.0 - n.Discount)) + n.GPUCost + n.Adjustment
 }
 
@@ -2039,6 +2078,9 @@ func (n *Node) Add(a Asset) Asset {
 		return this
 	}
 
+	n.SanitizeNaN()
+	a.SanitizeNaN()
+
 	props := n.Properties.Merge(a.GetProperties())
 	labels := n.Labels.Merge(a.GetLabels())
 
@@ -2068,6 +2110,9 @@ func (n *Node) add(that *Node) {
 		return
 	}
 
+	n.SanitizeNaN()
+	that.SanitizeNaN()
+
 	props := n.Properties.Merge(that.Properties)
 	labels := n.Labels.Merge(that.Labels)
 	n.SetProperties(props)
@@ -2412,6 +2457,7 @@ func (lb *LoadBalancer) SetLabels(labels AssetLabels) {
 
 // Adjustment returns the Asset's cost adjustment
 func (lb *LoadBalancer) GetAdjustment() float64 {
+	lb.SanitizeNaN()
 	return lb.Adjustment
 }
 
@@ -2422,6 +2468,7 @@ func (lb *LoadBalancer) SetAdjustment(adj float64) {
 
 // TotalCost returns the total cost of the Asset
 func (lb *LoadBalancer) TotalCost() float64 {
+	lb.SanitizeNaN()
 	return lb.Cost + lb.Adjustment
 }
 
@@ -2479,6 +2526,9 @@ func (lb *LoadBalancer) Add(a Asset) Asset {
 		return this
 	}
 
+	lb.SanitizeNaN()
+	a.SanitizeNaN()
+
 	props := lb.GetProperties().Merge(a.GetProperties())
 	labels := lb.Labels.Merge(a.GetLabels())
 
@@ -2508,6 +2558,9 @@ func (lb *LoadBalancer) add(that *LoadBalancer) {
 		return
 	}
 
+	lb.SanitizeNaN()
+	that.SanitizeNaN()
+
 	props := lb.Properties.Merge(that.GetProperties())
 	labels := lb.Labels.Merge(that.GetLabels())
 	lb.SetProperties(props)
@@ -2667,6 +2720,7 @@ func (sa *SharedAsset) SetAdjustment(float64) {
 
 // TotalCost returns the Asset's total cost
 func (sa *SharedAsset) TotalCost() float64 {
+	sa.SanitizeNaN()
 	return sa.Cost
 }
 
@@ -2714,6 +2768,9 @@ func (sa *SharedAsset) Add(a Asset) Asset {
 		return this
 	}
 
+	sa.SanitizeNaN()
+	a.SanitizeNaN()
+
 	props := sa.Properties.Merge(a.GetProperties())
 	labels := sa.Labels.Merge(a.GetLabels())
 
@@ -2743,6 +2800,9 @@ func (sa *SharedAsset) add(that *SharedAsset) {
 		return
 	}
 
+	sa.SanitizeNaN()
+	that.SanitizeNaN()
+
 	props := sa.Properties.Merge(that.GetProperties())
 	labels := sa.Labels.Merge(that.GetLabels())
 	sa.SetProperties(props)

+ 52 - 0
pkg/kubecost/summaryallocation.go

@@ -3,6 +3,7 @@ package kubecost
 import (
 	"errors"
 	"fmt"
+	"math"
 	"strings"
 	"sync"
 	"time"
@@ -98,6 +99,9 @@ func (sa *SummaryAllocation) Add(that *SummaryAllocation) error {
 	// expensive.
 	sa.Properties = nil
 
+	sa.sanitizeNaN()
+	that.sanitizeNaN()
+
 	// Sum non-cumulative fields by turning them into cumulative, adding them,
 	// and then converting them back into averages after minutes have been
 	// combined (just below).
@@ -177,6 +181,8 @@ func (sa *SummaryAllocation) CPUEfficiency() float64 {
 		return 0.0
 	}
 
+	sa.sanitizeNaN()
+
 	if sa.CPUCoreRequestAverage > 0 {
 		return sa.CPUCoreUsageAverage / sa.CPUCoreRequestAverage
 	}
@@ -328,6 +334,8 @@ func (sa *SummaryAllocation) RAMEfficiency() float64 {
 		return 0.0
 	}
 
+	sa.sanitizeNaN()
+
 	if sa.RAMBytesRequestAverage > 0 {
 		return sa.RAMBytesUsageAverage / sa.RAMBytesRequestAverage
 	}
@@ -345,6 +353,8 @@ func (sa *SummaryAllocation) TotalCost() float64 {
 		return 0.0
 	}
 
+	sa.sanitizeNaN()
+
 	return sa.CPUCost + sa.GPUCost + sa.RAMCost + sa.PVCost + sa.NetworkCost + sa.LoadBalancerCost + sa.SharedCost + sa.ExternalCost
 }
 
@@ -355,6 +365,8 @@ func (sa *SummaryAllocation) TotalEfficiency() float64 {
 		return 0.0
 	}
 
+	sa.sanitizeNaN()
+
 	if sa.RAMCost+sa.CPUCost > 0 {
 		ramCostEff := sa.RAMEfficiency() * sa.RAMCost
 		cpuCostEff := sa.CPUEfficiency() * sa.CPUCost
@@ -364,6 +376,45 @@ func (sa *SummaryAllocation) TotalEfficiency() float64 {
 	return 0.0
 }
 
+func (sa *SummaryAllocation) sanitizeNaN() {
+	if math.IsNaN(sa.CPUCoreRequestAverage) {
+		sa.CPUCoreRequestAverage = 0
+	}
+	if math.IsNaN(sa.CPUCoreUsageAverage) {
+		sa.CPUCoreUsageAverage = 0
+	}
+	if math.IsNaN(sa.CPUCost) {
+		sa.CPUCost = 0
+	}
+	if math.IsNaN(sa.GPUCost) {
+		sa.GPUCost = 0
+	}
+	if math.IsNaN(sa.NetworkCost) {
+		sa.NetworkCost = 0
+	}
+	if math.IsNaN(sa.LoadBalancerCost) {
+		sa.LoadBalancerCost = 0
+	}
+	if math.IsNaN(sa.PVCost) {
+		sa.PVCost = 0
+	}
+	if math.IsNaN(sa.RAMBytesRequestAverage) {
+		sa.RAMBytesRequestAverage = 0
+	}
+	if math.IsNaN(sa.RAMBytesUsageAverage) {
+		sa.RAMBytesUsageAverage = 0
+	}
+	if math.IsNaN(sa.RAMCost) {
+		sa.RAMCost = 0
+	}
+	if math.IsNaN(sa.SharedCost) {
+		sa.SharedCost = 0
+	}
+	if math.IsNaN(sa.ExternalCost) {
+		sa.ExternalCost = 0
+	}
+}
+
 // SummaryAllocationSet stores a set of SummaryAllocations, each with a unique
 // name, that share a window. An AllocationSet is mutable, so treat it like a
 // threadsafe map.
@@ -655,6 +706,7 @@ func (sas *SummaryAllocationSet) AggregateBy(aggregateBy []string, options *Allo
 
 	// 1 & 2. Identify set membership and aggregate aforementioned totals.
 	for _, sa := range sas.SummaryAllocations {
+		sa.sanitizeNaN()
 		if sa.Share {
 			var key string
 			if options.IdleByNode {

+ 69 - 0
pkg/kubecost/summaryallocation_test.go

@@ -1,6 +1,8 @@
 package kubecost
 
 import (
+	"math"
+	"reflect"
 	"testing"
 	"time"
 
@@ -211,6 +213,73 @@ func TestSummaryAllocation_Add(t *testing.T) {
 	})
 }
 
+func TestSummaryAllocation_Add_SanitizeNaNs(t *testing.T) {
+	window, _ := ParseWindowUTC("yesterday")
+
+	var sa, nansa *SummaryAllocation
+	var err error
+
+	start := *window.Start()
+
+	end := *window.End()
+
+	sa = &SummaryAllocation{
+		Name: "cluster1/namespace1/pod1/container1",
+		Properties: &AllocationProperties{
+			Cluster:   "cluster1",
+			Namespace: "namespace1",
+			Pod:       "pod1",
+			Container: "container1",
+		},
+		Start:                  start,
+		End:                    end,
+		CPUCoreRequestAverage:  0.5,
+		CPUCoreUsageAverage:    0.1,
+		CPUCost:                0.2,
+		GPUCost:                1.0,
+		NetworkCost:            0.1,
+		LoadBalancerCost:       0.6,
+		PVCost:                 0.005,
+		RAMBytesRequestAverage: 50.0 * 1024.0 * 1024.0,
+		RAMBytesUsageAverage:   10.0 * 1024.0 * 1024.0,
+		RAMCost:                0.05,
+		SharedCost:             1.0,
+		ExternalCost:           1.0,
+	}
+
+	nansa = &SummaryAllocation{
+		Name: "cluster1/namespace1/pod2/container2",
+		Properties: &AllocationProperties{
+			Cluster:   "cluster1",
+			Namespace: "namespace1",
+			Pod:       "pod2",
+			Container: "container2",
+		},
+		Start:                  start,
+		End:                    end,
+		CPUCoreRequestAverage:  math.NaN(),
+		CPUCoreUsageAverage:    math.NaN(),
+		CPUCost:                math.NaN(),
+		GPUCost:                math.NaN(),
+		NetworkCost:            math.NaN(),
+		LoadBalancerCost:       math.NaN(),
+		PVCost:                 math.NaN(),
+		RAMBytesRequestAverage: math.NaN(),
+		RAMBytesUsageAverage:   math.NaN(),
+		RAMCost:                math.NaN(),
+		SharedCost:             math.NaN(),
+		ExternalCost:           math.NaN(),
+	}
+
+	err = nansa.Add(sa)
+	if err != nil {
+		t.Fatalf("TestSummaryAllocation_Add_SanitizeNaNs: unexpected error: %s", err)
+	}
+
+	v := reflect.ValueOf(*nansa)
+	checkAllFloat64sForNaN(t, v, "TestSummaryAllocation_Add_SanitizeNaNs")
+}
+
 func TestSummaryAllocationSet_RAMEfficiency(t *testing.T) {
 	// Generating 6 sample summary allocations for testing
 	var sa1, sa2, sa3, sa4, sa5, sa6, idlesa *SummaryAllocation