Pārlūkot izejas kodu

Merge branch 'develop' into sean/pv-alloc-recon

# Conflicts:
#	pkg/kubecost/allocation.go
#	pkg/kubecost/allocation_test.go
Sean Holcomb 5 gadi atpakaļ
vecāks
revīzija
9caa4118d6

+ 1 - 1
README.md

@@ -74,7 +74,7 @@ Resources are allocated based on the time-weighted maximum of resource Requests
 
 #### How do I set my AWS Spot bids for accurate allocation?
 
-Modify [spotCPU](https://github.com/kubecost/cost-model/blob/master/cloud/default.json#L5) and  [spotRAM](https://github.com/kubecost/cost-model/blob/master/cloud/default.json#L7) in default.json to the price of your bid. Allocation will use these bid prices, but it does not take into account what you are actually charged by AWS. Alternatively, you can provide an AWS key to allow access to the Spot data feed. This will provide accurate Spot prices. 
+Modify [spotCPU](https://github.com/kubecost/cost-model/blob/master/configs/default.json#L5) and  [spotRAM](https://github.com/kubecost/cost-model/blob/master/configs/default.json#L7) in default.json to the price of your bid. Allocation will use these bid prices, but it does not take into account what you are actually charged by AWS. Alternatively, you can provide an AWS key to allow access to the Spot data feed. This will provide accurate Spot prices. 
 
 #### Do I need a GCP billing API key?
 

+ 2 - 2
pkg/costmodel/costmodel.go

@@ -1057,7 +1057,7 @@ func (cm *CostModel) GetNodeCost(cp costAnalyzerCloud.Provider) (map[string]*cos
 
 			cpuToRAMRatio := defaultCPU / defaultRAM
 			if math.IsNaN(cpuToRAMRatio) {
-				klog.V(1).Infof("[Warning] cpuToRAMRatio[defaultCPU: %f / defaultRam: %f] is NaN. Setting to 0.", defaultCPU, defaultRAM)
+				klog.V(1).Infof("[Warning] cpuToRAMRatio[defaultCPU: %f / defaultRAM: %f] is NaN. Setting to 0.", defaultCPU, defaultRAM)
 				cpuToRAMRatio = 0
 			}
 
@@ -1137,7 +1137,7 @@ func (cm *CostModel) GetNodeCost(cp costAnalyzerCloud.Provider) (map[string]*cos
 
 			cpuToRAMRatio := defaultCPU / defaultRAM
 			if math.IsNaN(cpuToRAMRatio) {
-				klog.V(1).Infof("[Warning] cpuToRAMRatio[defaultCPU: %f / defaultRam: %f] is NaN. Setting to 0.", defaultCPU, defaultRAM)
+				klog.V(1).Infof("[Warning] cpuToRAMRatio[defaultCPU: %f / defaultRAM: %f] is NaN. Setting to 0.", defaultCPU, defaultRAM)
 				cpuToRAMRatio = 0
 			}
 

+ 46 - 37
pkg/kubecost/allocation.go

@@ -59,20 +59,20 @@ type Allocation struct {
 	CPUCoreRequestAverage  float64               `json:"cpuCoreRequestAverage"`
 	CPUCoreUsageAverage    float64               `json:"cpuCoreUsageAverage"`
 	CPUCost                float64               `json:"cpuCost"`
-	CPUAdjustment          float64               `json:"cpuAdjustment"`
+	CPUCostAdjustment      float64               `json:"cpuCostAdjustment"`
 	GPUHours               float64               `json:"gpuHours"`
 	GPUCost                float64               `json:"gpuCost"`
-	GPUAdjustment          float64               `json:"gpuAdjustment"`
+	GPUCostAdjustment      float64               `json:"gpuCostAdjustment"`
 	NetworkCost            float64               `json:"networkCost"`
 	LoadBalancerCost       float64               `json:"loadBalancerCost"`
 	PVByteHours            float64               `json:"pvByteHours"`
 	PVCost                 float64               `json:"pvCost"`
-	PVAdjustment           float64               `json:"pvAdjustment"`
+	PVCostAdjustment       float64               `json:"pvAdjustment"`
 	RAMByteHours           float64               `json:"ramByteHours"`
 	RAMBytesRequestAverage float64               `json:"ramByteRequestAverage"`
 	RAMBytesUsageAverage   float64               `json:"ramByteUsageAverage"`
 	RAMCost                float64               `json:"ramCost"`
-	RAMAdjustment          float64               `json:"ramAdjustment"`
+	RAMCostAdjustment      float64               `json:"ramCostAdjustment"`
 	SharedCost             float64               `json:"sharedCost"`
 	ExternalCost           float64               `json:"externalCost"`
 	// RawAllocationOnly is a pointer so if it is not present it will be
@@ -147,20 +147,20 @@ func (a *Allocation) Clone() *Allocation {
 		CPUCoreRequestAverage:  a.CPUCoreRequestAverage,
 		CPUCoreUsageAverage:    a.CPUCoreUsageAverage,
 		CPUCost:                a.CPUCost,
-		CPUAdjustment:          a.CPUAdjustment,
+		CPUCostAdjustment:      a.CPUCostAdjustment,
 		GPUHours:               a.GPUHours,
 		GPUCost:                a.GPUCost,
-		GPUAdjustment:          a.GPUAdjustment,
+		GPUCostAdjustment:      a.GPUCostAdjustment,
 		NetworkCost:            a.NetworkCost,
 		LoadBalancerCost:       a.LoadBalancerCost,
 		PVByteHours:            a.PVByteHours,
 		PVCost:                 a.PVCost,
-		PVAdjustment:           a.PVAdjustment,
+		PVCostAdjustment:       a.PVCostAdjustment,
 		RAMByteHours:           a.RAMByteHours,
 		RAMBytesRequestAverage: a.RAMBytesRequestAverage,
 		RAMBytesUsageAverage:   a.RAMBytesUsageAverage,
 		RAMCost:                a.RAMCost,
-		RAMAdjustment:          a.RAMAdjustment,
+		RAMCostAdjustment:      a.RAMCostAdjustment,
 		SharedCost:             a.SharedCost,
 		ExternalCost:           a.ExternalCost,
 		RawAllocationOnly:      a.RawAllocationOnly.Clone(),
@@ -209,7 +209,7 @@ func (a *Allocation) Equal(that *Allocation) bool {
 	if !util.IsApproximately(a.CPUCost, that.CPUCost) {
 		return false
 	}
-	if !util.IsApproximately(a.CPUAdjustment, that.CPUAdjustment) {
+	if !util.IsApproximately(a.CPUCostAdjustment, that.CPUCostAdjustment) {
 		return false
 	}
 	if !util.IsApproximately(a.GPUHours, that.GPUHours) {
@@ -218,7 +218,7 @@ func (a *Allocation) Equal(that *Allocation) bool {
 	if !util.IsApproximately(a.GPUCost, that.GPUCost) {
 		return false
 	}
-	if !util.IsApproximately(a.GPUAdjustment, that.GPUAdjustment) {
+	if !util.IsApproximately(a.GPUCostAdjustment, that.GPUCostAdjustment) {
 		return false
 	}
 	if !util.IsApproximately(a.NetworkCost, that.NetworkCost) {
@@ -233,7 +233,7 @@ func (a *Allocation) Equal(that *Allocation) bool {
 	if !util.IsApproximately(a.PVCost, that.PVCost) {
 		return false
 	}
-	if !util.IsApproximately(a.PVAdjustment, that.PVAdjustment) {
+	if !util.IsApproximately(a.PVCostAdjustment, that.PVCostAdjustment) {
 		return false
 	}
 	if !util.IsApproximately(a.RAMByteHours, that.RAMByteHours) {
@@ -242,7 +242,7 @@ func (a *Allocation) Equal(that *Allocation) bool {
 	if !util.IsApproximately(a.RAMCost, that.RAMCost) {
 		return false
 	}
-	if !util.IsApproximately(a.RAMAdjustment, that.RAMAdjustment) {
+	if !util.IsApproximately(a.RAMCostAdjustment, that.RAMCostAdjustment) {
 		return false
 	}
 	if !util.IsApproximately(a.SharedCost, that.SharedCost) {
@@ -277,19 +277,19 @@ func (a *Allocation) TotalCost() float64 {
 }
 
 func (a *Allocation) CPUTotalCost() float64 {
-	return a.CPUCost + a.CPUAdjustment
+	return a.CPUCost + a.CPUCostAdjustment
 }
 
 func (a *Allocation) GPUTotalCost() float64 {
-	return a.GPUCost + a.GPUAdjustment
+	return a.GPUCost + a.GPUCostAdjustment
 }
 
 func (a *Allocation) RAMTotalCost() float64 {
-	return a.RAMCost + a.RAMAdjustment
+	return a.RAMCost + a.RAMCostAdjustment
 }
 
 func (a *Allocation) PVTotalCost() float64 {
-	return a.PVCost + a.PVAdjustment
+	return a.PVCost + a.PVCostAdjustment
 }
 
 // CPUEfficiency is the ratio of usage to request. If there is no request and
@@ -350,6 +350,14 @@ func (a *Allocation) RAMBytes() float64 {
 	return a.RAMByteHours / (a.Minutes() / 60.0)
 }
 
+// GPUs converts the Allocation's GPUHours into average GPUs
+func (a *Allocation) GPUs() float64 {
+	if a.Minutes() <= 0.0 {
+		return 0.0
+	}
+	return a.GPUHours / (a.Minutes() / 60.0)
+}
+
 // PVBytes converts the Allocation's PVByteHours into average PVBytes
 func (a *Allocation) PVBytes() float64 {
 	if a.Minutes() <= 0.0 {
@@ -372,23 +380,24 @@ func (a *Allocation) MarshalJSON() ([]byte, error) {
 	jsonEncodeFloat64(buffer, "cpuCoreUsageAverage", a.CPUCoreUsageAverage, ",")
 	jsonEncodeFloat64(buffer, "cpuCoreHours", a.CPUCoreHours, ",")
 	jsonEncodeFloat64(buffer, "cpuCost", a.CPUCost, ",")
-	jsonEncodeFloat64(buffer, "cpuAdjustment", a.CPUAdjustment, ",")
+	jsonEncodeFloat64(buffer, "cpuCostAdjustment", a.CPUCostAdjustment, ",")
 	jsonEncodeFloat64(buffer, "cpuEfficiency", a.CPUEfficiency(), ",")
+	jsonEncodeFloat64(buffer, "gpuCount", a.GPUs(), ",")
 	jsonEncodeFloat64(buffer, "gpuHours", a.GPUHours, ",")
 	jsonEncodeFloat64(buffer, "gpuCost", a.GPUCost, ",")
-	jsonEncodeFloat64(buffer, "gpuAdjustment", a.GPUAdjustment, ",")
+	jsonEncodeFloat64(buffer, "gpuCostAdjustment", a.GPUCostAdjustment, ",")
 	jsonEncodeFloat64(buffer, "networkCost", a.NetworkCost, ",")
 	jsonEncodeFloat64(buffer, "loadBalancerCost", a.LoadBalancerCost, ",")
 	jsonEncodeFloat64(buffer, "pvBytes", a.PVBytes(), ",")
 	jsonEncodeFloat64(buffer, "pvByteHours", a.PVByteHours, ",")
 	jsonEncodeFloat64(buffer, "pvCost", a.PVCost, ",")
-	jsonEncodeFloat64(buffer, "pvAdjustment", a.PVAdjustment, ",")
+	jsonEncodeFloat64(buffer, "pvAdjustment", a.PVCostAdjustment, ",")
 	jsonEncodeFloat64(buffer, "ramBytes", a.RAMBytes(), ",")
 	jsonEncodeFloat64(buffer, "ramByteRequestAverage", a.RAMBytesRequestAverage, ",")
 	jsonEncodeFloat64(buffer, "ramByteUsageAverage", a.RAMBytesUsageAverage, ",")
 	jsonEncodeFloat64(buffer, "ramByteHours", a.RAMByteHours, ",")
 	jsonEncodeFloat64(buffer, "ramCost", a.RAMCost, ",")
-	jsonEncodeFloat64(buffer, "ramAdjustment", a.RAMAdjustment, ",")
+	jsonEncodeFloat64(buffer, "ramCostAdjustment", a.RAMCostAdjustment, ",")
 	jsonEncodeFloat64(buffer, "ramEfficiency", a.RAMEfficiency(), ",")
 	jsonEncodeFloat64(buffer, "sharedCost", a.SharedCost, ",")
 	jsonEncodeFloat64(buffer, "externalCost", a.ExternalCost, ",")
@@ -519,10 +528,10 @@ func (a *Allocation) add(that *Allocation) {
 	a.ExternalCost += that.ExternalCost
 
 	// Sum all cumulative adjustment fields
-	a.CPUAdjustment += that.CPUAdjustment
-	a.RAMAdjustment += that.RAMAdjustment
-	a.GPUAdjustment += that.GPUAdjustment
-	a.PVAdjustment += that.PVAdjustment
+	a.CPUCostAdjustment += that.CPUCostAdjustment
+	a.RAMCostAdjustment += that.RAMCostAdjustment
+	a.GPUCostAdjustment += that.GPUCostAdjustment
+	a.PVCostAdjustment += that.PVCostAdjustment
 
 	// Any data that is in a "raw allocation only" is not valid in any
 	// sort of cumulative Allocation (like one that is added).
@@ -1505,9 +1514,9 @@ func (as *AllocationSet) ComputeIdleAllocations(assetSet *AssetSet) (map[string]
 	return idleAllocs, nil
 }
 
-// ReconcileAllocations calculate the exact cost of Allocation by resource(cpu, ram, gpu etc) based on Asset(s) on which
+// Reconcile calculate the exact cost of Allocation by resource(cpu, ram, gpu etc) based on Asset(s) on which
 // the Allocation depends.
-func (as *AllocationSet) ReconcileAllocations(assetSet *AssetSet) error {
+func (as *AllocationSet) Reconcile(assetSet *AssetSet) error {
 	if as == nil {
 		return fmt.Errorf("cannot reconcile allocation for nil AllocationSet")
 	}
@@ -1521,11 +1530,11 @@ func (as *AllocationSet) ReconcileAllocations(assetSet *AssetSet) error {
 	}
 
 	// Build map of Assets with type Node by their ProviderId so that they can be matched to Allocations to determine
-	// proper CPU GPU and Ram prices
+	// proper CPU GPU and RAM prices
 	nodeByProviderID := map[string]*Node{}
 	diskByName := map[string]*Disk{}
 	assetSet.Each(func(key string, a Asset) {
-		if node, ok := a.(*Node); ok {
+		if node, ok := a.(*Node); ok && node.properties.ProviderID != "" {
 			nodeByProviderID[node.properties.ProviderID] = node
 		}
 		if disk, ok := a.(*Disk); ok {
@@ -1590,8 +1599,8 @@ func (a *Allocation) reconcileNodes(nodeByProviderID map[string]*Node) {
 		log.Warningf("Missing Ram Byte Hours for node Provider ID: %s", providerId)
 	}
 	gpuUsageProportion := 0.0
-	if node.GPUCount != 0 && node.Minutes() != 0 {
-		gpuUsageProportion = a.GPUHours / (node.GPUCount * node.Minutes() / 60)
+	if node.GPUHours != 0 {
+		gpuUsageProportion = a.GPUHours / node.GPUHours
 	}
 	// No log for GPU because not all nodes have GPU
 
@@ -1600,9 +1609,9 @@ func (a *Allocation) reconcileNodes(nodeByProviderID map[string]*Node) {
 	allocRAMCost := ramUsageProportion * ramCost
 	allocGPUCost := gpuUsageProportion * gpuCost
 
-	a.CPUAdjustment = allocCPUCost - a.CPUCost
-	a.RAMAdjustment = allocRAMCost - a.RAMCost
-	a.GPUAdjustment = allocGPUCost - a.GPUCost
+	a.CPUCostAdjustment = allocCPUCost - a.CPUCost
+	a.RAMCostAdjustment = allocRAMCost - a.RAMCost
+	a.GPUCostAdjustment = allocGPUCost - a.GPUCost
 }
 
 func (a *Allocation) reconcileDisks(diskByName map[string]*Disk) {
@@ -1612,14 +1621,13 @@ func (a *Allocation) reconcileDisks(diskByName map[string]*Disk) {
 		return
 	}
 	// Set PV Adjustment for allocation to 0 for idempotency
-	a.PVAdjustment = 0.0
+	a.PVCostAdjustment = 0.0
 	for pvName, pvUsage := range pvBreakDown {
 		disk, ok := diskByName[pvName]
 		if !ok {
 			// Failed to find disk in assets
 			continue
 		}
-
 		// Check the proportion of disk that is being used by
 		pvUsageProportion := 0.0
 		if disk.ByteHours != 0 {
@@ -1631,9 +1639,10 @@ func (a *Allocation) reconcileDisks(diskByName map[string]*Disk) {
 		// take proportion of disk adjusted cost
 		allocPVCost := pvUsageProportion * disk.TotalCost()
 
-		// PVAdjustment is cumulative as there can be many PVs for each Allocation
-		a.PVAdjustment += allocPVCost - pvUsage.Cost
+		// PVCostAdjustment is cumulative as there can be many PVs for each Allocation
+		a.PVCostAdjustment += allocPVCost - pvUsage.Cost
 	}
+
 }
 
 // Delete removes the allocation with the given name from the set

+ 111 - 111
pkg/kubecost/allocation_test.go

@@ -112,18 +112,18 @@ func TestAllocation_Add(t *testing.T) {
 		CPUCoreRequestAverage:  2.0,
 		CPUCoreUsageAverage:    1.0,
 		CPUCost:                2.0 * hrs1 * cpuPrice,
-		CPUAdjustment:          3.0,
+		CPUCostAdjustment:      3.0,
 		GPUHours:               1.0 * hrs1,
 		GPUCost:                1.0 * hrs1 * gpuPrice,
-		GPUAdjustment:          2.0,
+		GPUCostAdjustment:      2.0,
 		PVByteHours:            100.0 * gib * hrs1,
 		PVCost:                 100.0 * hrs1 * pvPrice,
-		PVAdjustment:           4.0,
+		PVCostAdjustment:       4.0,
 		RAMByteHours:           8.0 * gib * hrs1,
 		RAMBytesRequestAverage: 8.0 * gib,
 		RAMBytesUsageAverage:   4.0 * gib,
 		RAMCost:                8.0 * hrs1 * ramPrice,
-		RAMAdjustment:          1.0,
+		RAMCostAdjustment:      1.0,
 		SharedCost:             2.00,
 		ExternalCost:           1.00,
 		RawAllocationOnly:      &RawAllocationOnlyData{},
@@ -177,20 +177,20 @@ func TestAllocation_Add(t *testing.T) {
 	if !util.IsApproximately(a1.CPUCost+a2.CPUCost, act.CPUCost) {
 		t.Fatalf("Allocation.Add: expected %f; actual %f", a1.CPUCost+a2.CPUCost, act.CPUCost)
 	}
-	if !util.IsApproximately(a1.CPUAdjustment+a2.CPUAdjustment, act.CPUAdjustment) {
-		t.Fatalf("Allocation.Add: expected %f; actual %f", a1.CPUAdjustment+a2.CPUAdjustment, act.CPUAdjustment)
+	if !util.IsApproximately(a1.CPUCostAdjustment+a2.CPUCostAdjustment, act.CPUCostAdjustment) {
+		t.Fatalf("Allocation.Add: expected %f; actual %f", a1.CPUCostAdjustment+a2.CPUCostAdjustment, act.CPUCostAdjustment)
 	}
 	if !util.IsApproximately(a1.GPUCost+a2.GPUCost, act.GPUCost) {
 		t.Fatalf("Allocation.Add: expected %f; actual %f", a1.GPUCost+a2.GPUCost, act.GPUCost)
 	}
-	if !util.IsApproximately(a1.GPUAdjustment+a2.GPUAdjustment, act.GPUAdjustment) {
-		t.Fatalf("Allocation.Add: expected %f; actual %f", a1.GPUAdjustment+a2.GPUAdjustment, act.GPUAdjustment)
+	if !util.IsApproximately(a1.GPUCostAdjustment+a2.GPUCostAdjustment, act.GPUCostAdjustment) {
+		t.Fatalf("Allocation.Add: expected %f; actual %f", a1.GPUCostAdjustment+a2.GPUCostAdjustment, act.GPUCostAdjustment)
 	}
 	if !util.IsApproximately(a1.RAMCost+a2.RAMCost, act.RAMCost) {
 		t.Fatalf("Allocation.Add: expected %f; actual %f", a1.RAMCost+a2.RAMCost, act.RAMCost)
 	}
-	if !util.IsApproximately(a1.RAMAdjustment+a2.RAMAdjustment, act.RAMAdjustment) {
-		t.Fatalf("Allocation.Add: expected %f; actual %f", a1.RAMAdjustment+a2.RAMAdjustment, act.RAMAdjustment)
+	if !util.IsApproximately(a1.RAMCostAdjustment+a2.RAMCostAdjustment, act.RAMCostAdjustment) {
+		t.Fatalf("Allocation.Add: expected %f; actual %f", a1.RAMCostAdjustment+a2.RAMCostAdjustment, act.RAMCostAdjustment)
 	}
 	if !util.IsApproximately(a1.PVCost+a2.PVCost, act.PVCost) {
 		t.Fatalf("Allocation.Add: expected %f; actual %f", a1.PVCost+a2.PVCost, act.PVCost)
@@ -282,18 +282,18 @@ func TestAllocation_Share(t *testing.T) {
 		CPUCoreRequestAverage:  2.0,
 		CPUCoreUsageAverage:    1.0,
 		CPUCost:                2.0 * hrs1 * cpuPrice,
-		CPUAdjustment:          3.0,
+		CPUCostAdjustment:      3.0,
 		GPUHours:               1.0 * hrs1,
 		GPUCost:                1.0 * hrs1 * gpuPrice,
-		GPUAdjustment:          2.0,
+		GPUCostAdjustment:      2.0,
 		PVByteHours:            100.0 * gib * hrs1,
 		PVCost:                 100.0 * hrs1 * pvPrice,
-		PVAdjustment:           4.0,
+		PVCostAdjustment:       4.0,
 		RAMByteHours:           8.0 * gib * hrs1,
 		RAMBytesRequestAverage: 8.0 * gib,
 		RAMBytesUsageAverage:   4.0 * gib,
 		RAMCost:                8.0 * hrs1 * ramPrice,
-		RAMAdjustment:          1.0,
+		RAMCostAdjustment:      1.0,
 		SharedCost:             2.00,
 		ExternalCost:           1.00,
 	}
@@ -442,20 +442,20 @@ func TestAllocation_MarshalJSON(t *testing.T) {
 		CPUCoreRequestAverage:  2.0,
 		CPUCoreUsageAverage:    1.0,
 		CPUCost:                2.0 * hrs * cpuPrice,
-		CPUAdjustment:          3.0,
+		CPUCostAdjustment:      3.0,
 		GPUHours:               1.0 * hrs,
 		GPUCost:                1.0 * hrs * gpuPrice,
-		GPUAdjustment:          2.0,
+		GPUCostAdjustment:      2.0,
 		NetworkCost:            0.05,
 		LoadBalancerCost:       0.02,
 		PVByteHours:            100.0 * gib * hrs,
 		PVCost:                 100.0 * hrs * pvPrice,
-		PVAdjustment:           4.0,
+		PVCostAdjustment:       4.0,
 		RAMByteHours:           8.0 * gib * hrs,
 		RAMBytesRequestAverage: 8.0 * gib,
 		RAMBytesUsageAverage:   4.0 * gib,
 		RAMCost:                8.0 * hrs * ramPrice,
-		RAMAdjustment:          1.0,
+		RAMCostAdjustment:      1.0,
 		SharedCost:             2.00,
 		ExternalCost:           1.00,
 		RawAllocationOnly:      &RawAllocationOnlyData{},
@@ -761,7 +761,7 @@ func generateAssetSets(start, end time.Time) []*AssetSet {
 	cluster1Nodes.adjustment = -10.00
 	cluster1Nodes.CPUCoreHours = 8
 	cluster1Nodes.RAMByteHours = 6
-	cluster1Nodes.GPUCount = 1
+	cluster1Nodes.GPUHours = 24
 
 	cluster2Node1 := NewNode("node1", "cluster2", "node1", start, end, NewWindow(&start, &end))
 	cluster2Node1.CPUCost = 20.0
@@ -769,7 +769,7 @@ func generateAssetSets(start, end time.Time) []*AssetSet {
 	cluster2Node1.GPUCost = 0.0
 	cluster2Node1.CPUCoreHours = 4
 	cluster2Node1.RAMByteHours = 3
-	cluster2Node1.GPUCount = 0
+	cluster2Node1.GPUHours = 0
 
 	cluster2Node2 := NewNode("node2", "cluster2", "node2", start, end, NewWindow(&start, &end))
 	cluster2Node2.CPUCost = 20.0
@@ -777,7 +777,7 @@ func generateAssetSets(start, end time.Time) []*AssetSet {
 	cluster2Node2.GPUCost = 0.0
 	cluster2Node2.CPUCoreHours = 3
 	cluster2Node2.RAMByteHours = 2
-	cluster2Node2.GPUCount = 0
+	cluster2Node2.GPUHours = 0
 
 	cluster2Node3 := NewNode("node3", "cluster2", "node3", start, end, NewWindow(&start, &end))
 	cluster2Node3.CPUCost = 10.0
@@ -785,7 +785,7 @@ func generateAssetSets(start, end time.Time) []*AssetSet {
 	cluster2Node3.GPUCost = 10.0
 	cluster2Node3.CPUCoreHours = 2
 	cluster2Node3.RAMByteHours = 2
-	cluster2Node3.GPUCount = 1
+	cluster2Node3.GPUHours = 24
 
 	cluster2Disk1 := NewDisk("disk1", "cluster2", "disk1", start, end, NewWindow(&start, &end))
 	cluster2Disk1.Cost = 5.0
@@ -834,7 +834,7 @@ func generateAssetSets(start, end time.Time) []*AssetSet {
 	cluster1Nodes.adjustment = 90.00
 	cluster1Nodes.CPUCoreHours = 8
 	cluster1Nodes.RAMByteHours = 6
-	cluster1Nodes.GPUCount = 1
+	cluster1Nodes.GPUHours = 24
 
 	cluster2Node1 = NewNode("node1", "cluster2", "node1", start, end, NewWindow(&start, &end))
 	cluster2Node1.CPUCost = 20.0
@@ -842,7 +842,7 @@ func generateAssetSets(start, end time.Time) []*AssetSet {
 	cluster2Node1.GPUCost = 0.0
 	cluster2Node1.CPUCoreHours = 4
 	cluster2Node1.RAMByteHours = 3
-	cluster2Node1.GPUCount = 0
+	cluster2Node1.GPUHours = 0
 
 	cluster2Node2 = NewNode("node2", "cluster2", "node2", start, end, NewWindow(&start, &end))
 	cluster2Node2.CPUCost = 20.0
@@ -850,7 +850,7 @@ func generateAssetSets(start, end time.Time) []*AssetSet {
 	cluster2Node2.GPUCost = 0.0
 	cluster2Node2.CPUCoreHours = 3
 	cluster2Node2.RAMByteHours = 2
-	cluster2Node2.GPUCount = 0
+	cluster2Node2.GPUHours = 0
 
 	cluster2Node3 = NewNode("node3", "cluster2", "node3", start, end, NewWindow(&start, &end))
 	cluster2Node3.CPUCost = 10.0
@@ -858,7 +858,7 @@ func generateAssetSets(start, end time.Time) []*AssetSet {
 	cluster2Node3.GPUCost = 10.0
 	cluster2Node3.CPUCoreHours = 2
 	cluster2Node3.RAMByteHours = 2
-	cluster2Node3.GPUCount = 1
+	cluster2Node3.GPUHours = 24
 
 	cluster2Disk1 = NewDisk("disk1", "cluster2", "disk1", start, end, NewWindow(&start, &end))
 	cluster2Disk1.Cost = 5.0
@@ -1792,9 +1792,9 @@ func TestAllocationSet_ReconcileAllocations(t *testing.T) {
 				// RAM	|    44	    |	  6	     |     11 	  |	  1
 				// GPU	|    11	    |	 24      |     1 	  |	  1
 				"cluster1/namespace1/pod1/container1": {
-					CPUAdjustment: 5.25,
-					RAMAdjustment: -4.333333,
-					GPUAdjustment: -0.583333,
+					CPUCostAdjustment: 5.25,
+					RAMCostAdjustment: -4.333333,
+					GPUCostAdjustment: -0.583333,
 				},
 				// ADJUSTMENT_RATE: 0.90909090909
 				// Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
@@ -1802,29 +1802,29 @@ func TestAllocationSet_ReconcileAllocations(t *testing.T) {
 				// RAM	|    44	    |	  6	     |     1 	  |	  1
 				// GPU	|    11	    |	 24      |     1 	  |	  1
 				"cluster1/namespace1/pod-abc/container2": {
-					CPUAdjustment: 5.25,
-					RAMAdjustment: 5.666667,
-					GPUAdjustment: -0.583333,
+					CPUCostAdjustment: 5.25,
+					RAMCostAdjustment: 5.666667,
+					GPUCostAdjustment: -0.583333,
 				},
 				"cluster1/namespace1/pod-def/container3": {
-					CPUAdjustment: 5.25,
-					RAMAdjustment: 5.666667,
-					GPUAdjustment: -0.583333,
+					CPUCostAdjustment: 5.25,
+					RAMCostAdjustment: 5.666667,
+					GPUCostAdjustment: -0.583333,
 				},
 				"cluster1/namespace2/pod-ghi/container4": {
-					CPUAdjustment: 5.25,
-					RAMAdjustment: 5.666667,
-					GPUAdjustment: -0.583333,
+					CPUCostAdjustment: 5.25,
+					RAMCostAdjustment: 5.666667,
+					GPUCostAdjustment: -0.583333,
 				},
 				"cluster1/namespace2/pod-ghi/container5": {
-					CPUAdjustment: 5.25,
-					RAMAdjustment: 5.666667,
-					GPUAdjustment: -0.583333,
+					CPUCostAdjustment: 5.25,
+					RAMCostAdjustment: 5.666667,
+					GPUCostAdjustment: -0.583333,
 				},
 				"cluster1/namespace2/pod-jkl/container6": {
-					CPUAdjustment: 5.25,
-					RAMAdjustment: 5.666667,
-					GPUAdjustment: -0.583333,
+					CPUCostAdjustment: 5.25,
+					RAMCostAdjustment: 5.666667,
+					GPUCostAdjustment: -0.583333,
 				},
 				// ADJUSTMENT_RATE: 1.0
 				// Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
@@ -1832,16 +1832,16 @@ func TestAllocationSet_ReconcileAllocations(t *testing.T) {
 				// RAM	|    15	    |	  3	     |     1 	  |	  1
 				// GPU	|    0	    |	  0      |     1 	  |	  1
 				"cluster2/namespace2/pod-mno/container4": {
-					CPUAdjustment: 4.0,
-					RAMAdjustment: 4.0,
-					GPUAdjustment: -1.0,
-					PVAdjustment:  2.0,
+					CPUCostAdjustment: 4.0,
+					RAMCostAdjustment: 4.0,
+					GPUCostAdjustment: -1.0,
+					PVCostAdjustment:  2.0,
 				},
 				"cluster2/namespace2/pod-mno/container5": {
-					CPUAdjustment: 4.0,
-					RAMAdjustment: 4.0,
-					GPUAdjustment: -1.0,
-					PVAdjustment:  2.0,
+					CPUCostAdjustment: 4.0,
+					RAMCostAdjustment: 4.0,
+					GPUCostAdjustment: -1.0,
+					PVCostAdjustment:  2.0,
 				},
 				// ADJUSTMENT_RATE: 1.0
 				// Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
@@ -1849,14 +1849,14 @@ func TestAllocationSet_ReconcileAllocations(t *testing.T) {
 				// RAM	|    15	    |	  2	     |     1 	  |	  1
 				// GPU	|    0	    |	  0      |     1 	  |	  1
 				"cluster2/namespace2/pod-pqr/container6": {
-					CPUAdjustment: 5.666667,
-					RAMAdjustment: 6.5,
-					GPUAdjustment: -1.0,
+					CPUCostAdjustment: 5.666667,
+					RAMCostAdjustment: 6.5,
+					GPUCostAdjustment: -1.0,
 				},
 				"cluster2/namespace3/pod-stu/container7": {
-					CPUAdjustment: 5.666667,
-					RAMAdjustment: 6.5,
-					GPUAdjustment: -1.0,
+					CPUCostAdjustment: 5.666667,
+					RAMCostAdjustment: 6.5,
+					GPUCostAdjustment: -1.0,
 				},
 				// ADJUSTMENT_RATE: 1.0
 				// Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
@@ -1864,14 +1864,14 @@ func TestAllocationSet_ReconcileAllocations(t *testing.T) {
 				// RAM	|    10	    |	  2	     |     1 	  |	  1
 				// GPU	|    10	    |	 24      |     1 	  |	  1
 				"cluster2/namespace3/pod-vwx/container8": {
-					CPUAdjustment: 4.0,
-					RAMAdjustment: 4.0,
-					GPUAdjustment: -0.583333,
+					CPUCostAdjustment: 4.0,
+					RAMCostAdjustment: 4.0,
+					GPUCostAdjustment: -0.583333,
 				},
 				"cluster2/namespace3/pod-vwx/container9": {
-					CPUAdjustment: 4.0,
-					RAMAdjustment: 4.0,
-					GPUAdjustment: -0.583333,
+					CPUCostAdjustment: 4.0,
+					RAMCostAdjustment: 4.0,
+					GPUCostAdjustment: -0.583333,
 				},
 			},
 		},
@@ -1885,9 +1885,9 @@ func TestAllocationSet_ReconcileAllocations(t *testing.T) {
 				// RAM	|     4	    |	  6	     |    11 	  |	  1
 				// GPU	|     1	    |	 24      |     1 	  |	  1
 				"cluster1/namespace1/pod1/container1": {
-					CPUAdjustment: 5.25,
-					RAMAdjustment: -4.333333,
-					GPUAdjustment: -0.583333,
+					CPUCostAdjustment: 5.25,
+					RAMCostAdjustment: -4.333333,
+					GPUCostAdjustment: -0.583333,
 				},
 				// ADJUSTMENT_RATE: 10
 				// Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
@@ -1895,29 +1895,29 @@ func TestAllocationSet_ReconcileAllocations(t *testing.T) {
 				// RAM	|     4	    |	  6	     |     1 	  |	  1
 				// GPU	|     1	    |	 24      |     1 	  |	  1
 				"cluster1/namespace1/pod-abc/container2": {
-					CPUAdjustment: 5.25,
-					RAMAdjustment: 5.6666667,
-					GPUAdjustment: -0.583333,
+					CPUCostAdjustment: 5.25,
+					RAMCostAdjustment: 5.6666667,
+					GPUCostAdjustment: -0.583333,
 				},
 				"cluster1/namespace1/pod-def/container3": {
-					CPUAdjustment: 5.25,
-					RAMAdjustment: 5.6666667,
-					GPUAdjustment: -0.583333,
+					CPUCostAdjustment: 5.25,
+					RAMCostAdjustment: 5.6666667,
+					GPUCostAdjustment: -0.583333,
 				},
 				"cluster1/namespace2/pod-ghi/container4": {
-					CPUAdjustment: 5.25,
-					RAMAdjustment: 5.6666667,
-					GPUAdjustment: -0.583333,
+					CPUCostAdjustment: 5.25,
+					RAMCostAdjustment: 5.6666667,
+					GPUCostAdjustment: -0.583333,
 				},
 				"cluster1/namespace2/pod-ghi/container5": {
-					CPUAdjustment: 5.25,
-					RAMAdjustment: 5.6666667,
-					GPUAdjustment: -0.583333,
+					CPUCostAdjustment: 5.25,
+					RAMCostAdjustment: 5.6666667,
+					GPUCostAdjustment: -0.583333,
 				},
 				"cluster1/namespace2/pod-jkl/container6": {
-					CPUAdjustment: 5.25,
-					RAMAdjustment: 5.6666667,
-					GPUAdjustment: -0.583333,
+					CPUCostAdjustment: 5.25,
+					RAMCostAdjustment: 5.6666667,
+					GPUCostAdjustment: -0.583333,
 				},
 				// ADJUSTMENT_RATE: 1.0
 				// Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
@@ -1925,16 +1925,16 @@ func TestAllocationSet_ReconcileAllocations(t *testing.T) {
 				// RAM	|    15	    |	  3	     |     1 	  |	  1
 				// GPU	|    0	    |	  0      |     1 	  |	  1
 				"cluster2/namespace2/pod-mno/container4": {
-					CPUAdjustment: 4.0,
-					RAMAdjustment: 4.0,
-					GPUAdjustment: -1.0,
-					PVAdjustment:  -0.5,
+					CPUCostAdjustment: 4.0,
+					RAMCostAdjustment: 4.0,
+					GPUCostAdjustment: -1.0,
+					PVCostAdjustment:  -0.5,
 				},
 				"cluster2/namespace2/pod-mno/container5": {
-					CPUAdjustment: 4.0,
-					RAMAdjustment: 4.0,
-					GPUAdjustment: -1.0,
-					PVAdjustment:  -0.5,
+					CPUCostAdjustment: 4.0,
+					RAMCostAdjustment: 4.0,
+					GPUCostAdjustment: -1.0,
+					PVCostAdjustment:  -0.5,
 				},
 				// ADJUSTMENT_RATE: 1.0
 				// Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
@@ -1942,14 +1942,14 @@ func TestAllocationSet_ReconcileAllocations(t *testing.T) {
 				// RAM	|    15	    |	  2	     |     1 	  |	  1
 				// GPU	|    0	    |	  0      |     1 	  |	  1
 				"cluster2/namespace2/pod-pqr/container6": {
-					CPUAdjustment: 5.666667,
-					RAMAdjustment: 6.5,
-					GPUAdjustment: -1.0,
+					CPUCostAdjustment: 5.666667,
+					RAMCostAdjustment: 6.5,
+					GPUCostAdjustment: -1.0,
 				},
 				"cluster2/namespace3/pod-stu/container7": {
-					CPUAdjustment: 5.666667,
-					RAMAdjustment: 6.5,
-					GPUAdjustment: -1.0,
+					CPUCostAdjustment: 5.666667,
+					RAMCostAdjustment: 6.5,
+					GPUCostAdjustment: -1.0,
 				},
 				// ADJUSTMENT_RATE: 1.0
 				// Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
@@ -1957,14 +1957,14 @@ func TestAllocationSet_ReconcileAllocations(t *testing.T) {
 				// RAM	|    10	    |	  2	     |     1 	  |	  1
 				// GPU	|    10	    |	 24      |     1 	  |	  1
 				"cluster2/namespace3/pod-vwx/container8": {
-					CPUAdjustment: 4.0,
-					RAMAdjustment: 4.0,
-					GPUAdjustment: -0.583333,
+					CPUCostAdjustment: 4.0,
+					RAMCostAdjustment: 4.0,
+					GPUCostAdjustment: -0.583333,
 				},
 				"cluster2/namespace3/pod-vwx/container9": {
-					CPUAdjustment: 4.0,
-					RAMAdjustment: 4.0,
-					GPUAdjustment: -0.583333,
+					CPUCostAdjustment: 4.0,
+					RAMCostAdjustment: 4.0,
+					GPUCostAdjustment: -0.583333,
 				},
 			},
 		},
@@ -1972,7 +1972,7 @@ func TestAllocationSet_ReconcileAllocations(t *testing.T) {
 
 	for name, testcase := range cases {
 		t.Run(name, func(t *testing.T) {
-			err = as.ReconcileAllocations(testcase.assetSet)
+			err = as.Reconcile(testcase.assetSet)
 			reconAllocs := as.allocations
 			if err != nil {
 				t.Fatalf("unexpected error: %s", err)
@@ -1983,17 +1983,17 @@ func TestAllocationSet_ReconcileAllocations(t *testing.T) {
 					t.Fatalf("expected allocation %s", allocationName)
 				}
 
-				if !util.IsApproximately(reconAllocs[allocationName].CPUAdjustment, testAlloc.CPUAdjustment) {
-					t.Fatalf("expected CPU Adjustment for %s to be %f; got %f", allocationName, testAlloc.CPUAdjustment, reconAllocs[allocationName].CPUAdjustment)
+				if !util.IsApproximately(reconAllocs[allocationName].CPUCostAdjustment, testAlloc.CPUCostAdjustment) {
+					t.Fatalf("expected CPU Adjustment for %s to be %f; got %f", allocationName, testAlloc.CPUCostAdjustment, reconAllocs[allocationName].CPUCostAdjustment)
 				}
-				if !util.IsApproximately(reconAllocs[allocationName].RAMAdjustment, testAlloc.RAMAdjustment) {
-					t.Fatalf("expected RAM Adjustment for %s to be %f; got %f", allocationName, testAlloc.RAMAdjustment, reconAllocs[allocationName].RAMAdjustment)
+				if !util.IsApproximately(reconAllocs[allocationName].RAMCostAdjustment, testAlloc.RAMCostAdjustment) {
+					t.Fatalf("expected RAM Adjustment for %s to be %f; got %f", allocationName, testAlloc.RAMCostAdjustment, reconAllocs[allocationName].RAMCostAdjustment)
 				}
-				if !util.IsApproximately(reconAllocs[allocationName].GPUAdjustment, testAlloc.GPUAdjustment) {
-					t.Fatalf("expected GPU Adjustment for %s to be %f; got %f", allocationName, testAlloc.GPUAdjustment, reconAllocs[allocationName].GPUAdjustment)
+				if !util.IsApproximately(reconAllocs[allocationName].GPUCostAdjustment, testAlloc.GPUCostAdjustment) {
+					t.Fatalf("expected GPU Adjustment for %s to be %f; got %f", allocationName, testAlloc.GPUCostAdjustment, reconAllocs[allocationName].GPUCostAdjustment)
 				}
-				if !util.IsApproximately(reconAllocs[allocationName].PVAdjustment, testAlloc.PVAdjustment) {
-					t.Fatalf("expected PV Adjustment for %s to be %f; got %f", allocationName, testAlloc.PVAdjustment, reconAllocs[allocationName].PVAdjustment)
+				if !util.IsApproximately(reconAllocs[allocationName].PVCostAdjustment, testAlloc.PVCostAdjustment) {
+					t.Fatalf("expected PV Adjustment for %s to be %f; got %f", allocationName, testAlloc.PVCostAdjustment, reconAllocs[allocationName].PVCostAdjustment)
 				}
 			}
 		})

+ 26 - 4
pkg/kubecost/asset.go

@@ -1663,6 +1663,7 @@ type Node struct {
 	NodeType     string
 	CPUCoreHours float64
 	RAMByteHours float64
+	GPUHours     float64
 	CPUBreakdown *Breakdown
 	RAMBreakdown *Breakdown
 	CPUCost      float64
@@ -1889,6 +1890,7 @@ func (n *Node) add(that *Node) {
 
 	n.CPUCoreHours += that.CPUCoreHours
 	n.RAMByteHours += that.RAMByteHours
+	n.GPUHours += that.GPUHours
 
 	n.CPUCost += that.CPUCost
 	n.GPUCost += that.GPUCost
@@ -1912,6 +1914,7 @@ func (n *Node) Clone() Asset {
 		NodeType:     n.NodeType,
 		CPUCoreHours: n.CPUCoreHours,
 		RAMByteHours: n.RAMByteHours,
+		GPUHours:     n.GPUHours,
 		CPUBreakdown: n.CPUBreakdown.Clone(),
 		RAMBreakdown: n.RAMBreakdown.Clone(),
 		CPUCost:      n.CPUCost,
@@ -1960,6 +1963,9 @@ func (n *Node) Equal(a Asset) bool {
 	if n.RAMByteHours != that.RAMByteHours {
 		return false
 	}
+	if n.GPUHours != that.GPUHours {
+		return false
+	}
 	if !n.CPUBreakdown.Equal(that.CPUBreakdown) {
 		return false
 	}
@@ -2000,13 +2006,14 @@ func (n *Node) MarshalJSON() ([]byte, error) {
 	jsonEncodeFloat64(buffer, "ramBytes", n.RAMBytes(), ",")
 	jsonEncodeFloat64(buffer, "cpuCoreHours", n.CPUCoreHours, ",")
 	jsonEncodeFloat64(buffer, "ramByteHours", n.RAMByteHours, ",")
+	jsonEncodeFloat64(buffer, "GPUHours", n.GPUHours, ",")
 	jsonEncode(buffer, "cpuBreakdown", n.CPUBreakdown, ",")
 	jsonEncode(buffer, "ramBreakdown", n.RAMBreakdown, ",")
 	jsonEncodeFloat64(buffer, "preemptible", n.Preemptible, ",")
 	jsonEncodeFloat64(buffer, "discount", n.Discount, ",")
 	jsonEncodeFloat64(buffer, "cpuCost", n.CPUCost, ",")
 	jsonEncodeFloat64(buffer, "gpuCost", n.GPUCost, ",")
-	jsonEncodeFloat64(buffer, "gpuCount", n.GPUCount, ",")
+	jsonEncodeFloat64(buffer, "gpuCount", n.GPUs(), ",")
 	jsonEncodeFloat64(buffer, "ramCost", n.RAMCost, ",")
 	jsonEncodeFloat64(buffer, "adjustment", n.Adjustment(), ",")
 	jsonEncodeFloat64(buffer, "totalCost", n.TotalCost(), "")
@@ -2047,15 +2054,30 @@ func (n *Node) CPUCores() float64 {
 // and a 16GiB-RAM node running for the last 20 hours of the same 24-hour window
 // would produce:
 //   (12*10 + 16*20) / 24 = 18.333GiB RAM
-// However, any number of cores running for the full span of a window will
-// report the actual number of cores of the static node; e.g. the above
+// However, any number of bytes running for the full span of a window will
+// report the actual number of bytes of the static node; e.g. the above
 // scenario for one entire 24-hour window:
-//   (12*24 + 16*24) / 24 = (12 + 16) = 28 cores
+//   (12*24 + 16*24) / 24 = (12 + 16) = 28GiB RAM
 func (n *Node) RAMBytes() float64 {
 	// [b*hr]*([min/hr]*[1/min]) = [b*hr]/[hr] = b
 	return n.RAMByteHours * (60.0 / n.Minutes())
 }
 
+// GPUs returns the amount of GPUs belonging to the node. This could be
+// fractional because it's the number of gpu*hours divided by the number of
+// hours running; e.g. the sum of a 2 gpu node running for the first 10 hours
+// and a 1 gpu node running for the last 20 hours of the same 24-hour window
+// would produce:
+//   (2*10 + 1*20) / 24 = 1.667 GPUs
+// However, any number of GPUs running for the full span of a window will
+// report the actual number of GPUs of the static node; e.g. the above
+// scenario for one entire 24-hour window:
+//   (2*24 + 1*24) / 24 = (2 + 1) = 3 GPUs
+func (n *Node) GPUs() float64 {
+	// [b*hr]*([min/hr]*[1/min]) = [b*hr]/[hr] = b
+	return n.GPUHours * (60.0 / n.Minutes())
+}
+
 // LoadBalancer is an Asset representing a single load balancer in a cluster
 // TODO: add GB of ingress processed, numForwardingRules once we start recording those to prometheus metric
 type LoadBalancer struct {

+ 16 - 0
pkg/kubecost/asset_test.go

@@ -65,6 +65,7 @@ func generateAssetSet(start time.Time) *AssetSet {
 	node1.Discount = 0.5
 	node1.CPUCoreHours = 2.0 * hours
 	node1.RAMByteHours = 4.0 * gb * hours
+	node1.GPUHours = 1.0 * hours
 	node1.SetAdjustment(1.0)
 	node1.SetLabels(map[string]string{"test": "test"})
 
@@ -75,6 +76,7 @@ func generateAssetSet(start time.Time) *AssetSet {
 	node2.Discount = 0.5
 	node2.CPUCoreHours = 2.0 * hours
 	node2.RAMByteHours = 4.0 * gb * hours
+	node2.GPUHours = 0.0 * hours
 	node2.SetAdjustment(1.5)
 
 	node3 := NewNode("node3", "cluster1", "gcp-node3", *window.Clone().start, *window.Clone().end, window.Clone())
@@ -84,6 +86,7 @@ func generateAssetSet(start time.Time) *AssetSet {
 	node3.Discount = 0.5
 	node3.CPUCoreHours = 2.0 * hours
 	node3.RAMByteHours = 4.0 * gb * hours
+	node3.GPUHours = 2.0 * hours
 	node3.SetAdjustment(-0.5)
 
 	node4 := NewNode("node4", "cluster2", "gcp-node4", *window.Clone().start, *window.Clone().end, window.Clone())
@@ -93,6 +96,7 @@ func generateAssetSet(start time.Time) *AssetSet {
 	node4.Discount = 0.25
 	node4.CPUCoreHours = 4.0 * hours
 	node4.RAMByteHours = 12.0 * gb * hours
+	node4.GPUHours = 0.0 * hours
 	node4.SetAdjustment(-1.0)
 
 	node5 := NewNode("node5", "cluster3", "aws-node5", *window.Clone().start, *window.Clone().end, window.Clone())
@@ -102,6 +106,7 @@ func generateAssetSet(start time.Time) *AssetSet {
 	node5.Discount = 0.0
 	node5.CPUCoreHours = 8.0 * hours
 	node5.RAMByteHours = 24.0 * gb * hours
+	node5.GPUHours = 0.0 * hours
 	node5.SetAdjustment(2.0)
 
 	disk1 := NewDisk("disk1", "cluster1", "gcp-disk1", *window.Clone().start, *window.Clone().end, window.Clone())
@@ -481,6 +486,7 @@ func TestNode_Add(t *testing.T) {
 	node1 := NewNode("node1", "cluster1", "node1", *windows[0].start, *windows[0].end, windows[0])
 	node1.CPUCoreHours = 1.0 * hours
 	node1.RAMByteHours = 2.0 * gb * hours
+	node1.GPUHours = 0.0 * hours
 	node1.GPUCost = 0.0
 	node1.CPUCost = 8.0
 	node1.RAMCost = 4.0
@@ -502,6 +508,7 @@ func TestNode_Add(t *testing.T) {
 	node2 := NewNode("node2", "cluster1", "node2", *windows[0].start, *windows[0].end, windows[0])
 	node2.CPUCoreHours = 1.0 * hours
 	node2.RAMByteHours = 2.0 * gb * hours
+	node2.GPUHours = 0.0 * hours
 	node2.GPUCost = 0.0
 	node2.CPUCost = 3.0
 	node2.RAMCost = 1.0
@@ -566,6 +573,7 @@ func TestNode_Add(t *testing.T) {
 	node3 := NewNode("node3", "cluster1", "node3", *windows[0].start, *windows[0].end, windows[0])
 	node3.CPUCoreHours = 0 * hours
 	node3.RAMByteHours = 0 * hours
+	node3.GPUHours = 0.0 * hours
 	node3.GPUCost = 0
 	node3.CPUCost = 0.0
 	node3.RAMCost = 0.0
@@ -575,6 +583,7 @@ func TestNode_Add(t *testing.T) {
 	node4 := NewNode("node4", "cluster1", "node4", *windows[0].start, *windows[0].end, windows[0])
 	node4.CPUCoreHours = 0 * hours
 	node4.RAMByteHours = 0 * hours
+	node4.GPUHours = 0.0 * hours
 	node4.GPUCost = 0
 	node4.CPUCost = 0.0
 	node4.RAMCost = 0.0
@@ -595,6 +604,7 @@ func TestNode_Add(t *testing.T) {
 	nodeA1 := NewNode("nodeA1", "cluster1", "nodeA1", *windows[0].start, *windows[0].end, windows[0])
 	nodeA1.CPUCoreHours = 1.0 * hours
 	nodeA1.RAMByteHours = 2.0 * gb * hours
+	nodeA1.GPUHours = 0.0 * hours
 	nodeA1.GPUCost = 0.0
 	nodeA1.CPUCost = 8.0
 	nodeA1.RAMCost = 4.0
@@ -604,6 +614,7 @@ func TestNode_Add(t *testing.T) {
 	nodeA2 := NewNode("nodeA2", "cluster1", "nodeA2", *windows[1].start, *windows[1].end, windows[1])
 	nodeA2.CPUCoreHours = 1.0 * hours
 	nodeA2.RAMByteHours = 2.0 * gb * hours
+	nodeA2.GPUHours = 0.0 * hours
 	nodeA2.GPUCost = 0.0
 	nodeA2.CPUCost = 3.0
 	nodeA2.RAMCost = 1.0
@@ -637,6 +648,9 @@ func TestNode_Add(t *testing.T) {
 	if nodeAT.RAMBytes() != 2.0*gb {
 		t.Fatalf("Node.Add: expected %f; got %f", 2.0*gb, nodeAT.RAMBytes())
 	}
+	if nodeAT.GPUs() != 0.0 {
+		t.Fatalf("Node.Add: expected %f; got %f", 0.0, nodeAT.GPUs())
+	}
 
 	// Check that the original assets are unchanged
 	if !util.IsApproximately(nodeA1.TotalCost(), 10.0) {
@@ -664,8 +678,10 @@ func TestNode_MarshalJSON(t *testing.T) {
 	})
 	node.CPUCost = 9.0
 	node.RAMCost = 0.0
+	node.RAMCost = 21.0
 	node.CPUCoreHours = 123.0
 	node.RAMByteHours = 13323.0
+	node.GPUHours = 123.0
 	node.SetAdjustment(1.0)
 
 	_, err := json.Marshal(node)

+ 1 - 1
pkg/kubecost/bingen.go

@@ -26,4 +26,4 @@ package kubecost
 // @bingen:generate:AllocationAnnotations
 // @bingen:generate:RawAllocationOnlyData
 
-//go:generate bingen -package=kubecost -version=11 -buffer=github.com/kubecost/cost-model/pkg/util
+//go:generate bingen -package=kubecost -version=12 -buffer=github.com/kubecost/cost-model/pkg/util

+ 52 - 36
pkg/kubecost/kubecost_codecs.go

@@ -25,7 +25,7 @@ const (
 	GeneratorPackageName string = "kubecost"
 
 	// CodecVersion is the version passed into the generator
-	CodecVersion uint8 = 11
+	CodecVersion uint8 = 12
 )
 
 //--------------------------------------------------------------------------
@@ -164,8 +164,10 @@ func (target *Allocation) MarshalBinary() (data []byte, err error) {
 	buff.WriteFloat64(target.CPUCoreRequestAverage)  // write float64
 	buff.WriteFloat64(target.CPUCoreUsageAverage)    // write float64
 	buff.WriteFloat64(target.CPUCost)                // write float64
+	buff.WriteFloat64(target.CPUCostAdjustment)      // write float64
 	buff.WriteFloat64(target.GPUHours)               // write float64
 	buff.WriteFloat64(target.GPUCost)                // write float64
+	buff.WriteFloat64(target.GPUCostAdjustment)      // write float64
 	buff.WriteFloat64(target.NetworkCost)            // write float64
 	buff.WriteFloat64(target.LoadBalancerCost)       // write float64
 	buff.WriteFloat64(target.PVByteHours)            // write float64
@@ -174,6 +176,7 @@ func (target *Allocation) MarshalBinary() (data []byte, err error) {
 	buff.WriteFloat64(target.RAMBytesRequestAverage) // write float64
 	buff.WriteFloat64(target.RAMBytesUsageAverage)   // write float64
 	buff.WriteFloat64(target.RAMCost)                // write float64
+	buff.WriteFloat64(target.RAMCostAdjustment)      // write float64
 	buff.WriteFloat64(target.SharedCost)             // write float64
 	buff.WriteFloat64(target.ExternalCost)           // write float64
 	if target.RawAllocationOnly == nil {
@@ -282,53 +285,62 @@ func (target *Allocation) UnmarshalBinary(data []byte) (err error) {
 	target.CPUCost = s
 
 	t := buff.ReadFloat64() // read float64
-	target.GPUHours = t
+	target.CPUCostAdjustment = t
 
 	u := buff.ReadFloat64() // read float64
-	target.GPUCost = u
+	target.GPUHours = u
 
 	w := buff.ReadFloat64() // read float64
-	target.NetworkCost = w
+	target.GPUCost = w
 
 	x := buff.ReadFloat64() // read float64
-	target.LoadBalancerCost = x
+	target.GPUCostAdjustment = x
 
 	y := buff.ReadFloat64() // read float64
-	target.PVByteHours = y
+	target.NetworkCost = y
 
 	aa := buff.ReadFloat64() // read float64
-	target.PVCost = aa
+	target.LoadBalancerCost = aa
 
 	bb := buff.ReadFloat64() // read float64
-	target.RAMByteHours = bb
+	target.PVByteHours = bb
 
 	cc := buff.ReadFloat64() // read float64
-	target.RAMBytesRequestAverage = cc
+	target.PVCost = cc
 
 	dd := buff.ReadFloat64() // read float64
-	target.RAMBytesUsageAverage = dd
+	target.RAMByteHours = dd
 
 	ee := buff.ReadFloat64() // read float64
-	target.RAMCost = ee
+	target.RAMBytesRequestAverage = ee
 
 	ff := buff.ReadFloat64() // read float64
-	target.SharedCost = ff
+	target.RAMBytesUsageAverage = ff
 
 	gg := buff.ReadFloat64() // read float64
-	target.ExternalCost = gg
+	target.RAMCost = gg
+
+	hh := buff.ReadFloat64() // read float64
+	target.RAMCostAdjustment = hh
+
+	kk := buff.ReadFloat64() // read float64
+	target.SharedCost = kk
+
+	ll := buff.ReadFloat64() // read float64
+	target.ExternalCost = ll
 
 	if buff.ReadUInt8() == uint8(0) {
 		target.RawAllocationOnly = nil
 	} else {
 		// --- [begin][read][struct](RawAllocationOnlyData) ---
-		hh := &RawAllocationOnlyData{}
-		kk := buff.ReadInt()     // byte array length
-		ll := buff.ReadBytes(kk) // byte array
-		errE := hh.UnmarshalBinary(ll)
+		mm := &RawAllocationOnlyData{}
+		nn := buff.ReadInt()     // byte array length
+		oo := buff.ReadBytes(nn) // byte array
+		errE := mm.UnmarshalBinary(oo)
 		if errE != nil {
 			return errE
 		}
-		target.RawAllocationOnly = hh
+		target.RawAllocationOnly = mm
 		// --- [end][read][struct](RawAllocationOnlyData) ---
 
 	}
@@ -2658,6 +2670,7 @@ func (target *Node) MarshalBinary() (data []byte, err error) {
 	buff.WriteString(target.NodeType)      // write string
 	buff.WriteFloat64(target.CPUCoreHours) // write float64
 	buff.WriteFloat64(target.RAMByteHours) // write float64
+	buff.WriteFloat64(target.GPUHours)     // write float64
 	if target.CPUBreakdown == nil {
 		buff.WriteUInt8(uint8(0)) // write nil byte
 	} else {
@@ -2807,18 +2820,21 @@ func (target *Node) UnmarshalBinary(data []byte) (err error) {
 	x := buff.ReadFloat64() // read float64
 	target.RAMByteHours = x
 
+	y := buff.ReadFloat64() // read float64
+	target.GPUHours = y
+
 	if buff.ReadUInt8() == uint8(0) {
 		target.CPUBreakdown = nil
 	} else {
 		// --- [begin][read][struct](Breakdown) ---
-		y := &Breakdown{}
-		aa := buff.ReadInt()     // byte array length
-		bb := buff.ReadBytes(aa) // byte array
-		errE := y.UnmarshalBinary(bb)
+		aa := &Breakdown{}
+		bb := buff.ReadInt()     // byte array length
+		cc := buff.ReadBytes(bb) // byte array
+		errE := aa.UnmarshalBinary(cc)
 		if errE != nil {
 			return errE
 		}
-		target.CPUBreakdown = y
+		target.CPUBreakdown = aa
 		// --- [end][read][struct](Breakdown) ---
 
 	}
@@ -2826,34 +2842,34 @@ func (target *Node) UnmarshalBinary(data []byte) (err error) {
 		target.RAMBreakdown = nil
 	} else {
 		// --- [begin][read][struct](Breakdown) ---
-		cc := &Breakdown{}
-		dd := buff.ReadInt()     // byte array length
-		ee := buff.ReadBytes(dd) // byte array
-		errF := cc.UnmarshalBinary(ee)
+		dd := &Breakdown{}
+		ee := buff.ReadInt()     // byte array length
+		ff := buff.ReadBytes(ee) // byte array
+		errF := dd.UnmarshalBinary(ff)
 		if errF != nil {
 			return errF
 		}
-		target.RAMBreakdown = cc
+		target.RAMBreakdown = dd
 		// --- [end][read][struct](Breakdown) ---
 
 	}
-	ff := buff.ReadFloat64() // read float64
-	target.CPUCost = ff
-
 	gg := buff.ReadFloat64() // read float64
-	target.GPUCost = gg
+	target.CPUCost = gg
 
 	hh := buff.ReadFloat64() // read float64
-	target.GPUCount = hh
+	target.GPUCost = hh
 
 	kk := buff.ReadFloat64() // read float64
-	target.RAMCost = kk
+	target.GPUCount = kk
 
 	ll := buff.ReadFloat64() // read float64
-	target.Discount = ll
+	target.RAMCost = ll
 
 	mm := buff.ReadFloat64() // read float64
-	target.Preemptible = mm
+	target.Discount = mm
+
+	nn := buff.ReadFloat64() // read float64
+	target.Preemptible = nn
 
 	return nil
 }