فهرست منبع

Merge pull request #743 from kubecost/sean/aws-lb-alloc

Load Balancer WIP
Sean Holcomb 5 سال پیش
والد
کامیت
d14596125f
5فایلهای تغییر یافته به همراه335 افزوده شده و 212 حذف شده
  1. 92 2
      pkg/costmodel/allocation.go
  2. 8 1
      pkg/kubecost/allocation.go
  3. 220 197
      pkg/kubecost/allocation_test.go
  4. 1 1
      pkg/kubecost/bingen.go
  5. 14 11
      pkg/kubecost/kubecost_codecs.go

+ 92 - 2
pkg/costmodel/allocation.go

@@ -49,6 +49,8 @@ const (
 	queryFmtStatefulSetLabels     = `avg_over_time(statefulSet_match_labels[%s]%s)`
 	queryFmtStatefulSetLabels     = `avg_over_time(statefulSet_match_labels[%s]%s)`
 	queryFmtDaemonSetLabels       = `sum(avg_over_time(kube_pod_owner{owner_kind="DaemonSet"}[%s]%s)) by (pod, owner_name, namespace, cluster_id)`
 	queryFmtDaemonSetLabels       = `sum(avg_over_time(kube_pod_owner{owner_kind="DaemonSet"}[%s]%s)) by (pod, owner_name, namespace, cluster_id)`
 	queryFmtJobLabels             = `sum(avg_over_time(kube_pod_owner{owner_kind="Job"}[%s]%s)) by (pod, owner_name, namespace ,cluster_id)`
 	queryFmtJobLabels             = `sum(avg_over_time(kube_pod_owner{owner_kind="Job"}[%s]%s)) by (pod, owner_name, namespace ,cluster_id)`
+	queryFmtLBCostPerHr           = `avg(avg_over_time(kubecost_load_balancer_cost[%s]%s)) by (namespace, service_name, cluster_id)`
+	queryFmtLBActiveMins          = `count(kubecost_load_balancer_cost) by (namespace, service_name, cluster_id)[%s:%s]%s`
 )
 )
 
 
 // ComputeAllocation uses the CostModel instance to compute an AllocationSet
 // ComputeAllocation uses the CostModel instance to compute an AllocationSet
@@ -192,6 +194,12 @@ func (cm *CostModel) ComputeAllocation(start, end time.Time, resolution time.Dur
 	queryJobLabels := fmt.Sprintf(queryFmtJobLabels, durStr, offStr)
 	queryJobLabels := fmt.Sprintf(queryFmtJobLabels, durStr, offStr)
 	resChJobLabels := ctx.Query(queryJobLabels)
 	resChJobLabels := ctx.Query(queryJobLabels)
 
 
+	queryLBCostPerHr := fmt.Sprintf(queryFmtLBCostPerHr, durStr, offStr)
+	resChLBCostPerHr := ctx.Query(queryLBCostPerHr)
+
+	queryLBActiveMins := fmt.Sprintf(queryFmtLBActiveMins, durStr, resStr, offStr)
+	resChLBActiveMins := ctx.Query(queryLBActiveMins)
+
 	resCPUCoresAllocated, _ := resChCPUCoresAllocated.Await()
 	resCPUCoresAllocated, _ := resChCPUCoresAllocated.Await()
 	resCPURequests, _ := resChCPURequests.Await()
 	resCPURequests, _ := resChCPURequests.Await()
 	resCPUUsage, _ := resChCPUUsage.Await()
 	resCPUUsage, _ := resChCPUUsage.Await()
@@ -228,6 +236,8 @@ func (cm *CostModel) ComputeAllocation(start, end time.Time, resolution time.Dur
 	resStatefulSetLabels, _ := resChStatefulSetLabels.Await()
 	resStatefulSetLabels, _ := resChStatefulSetLabels.Await()
 	resDaemonSetLabels, _ := resChDaemonSetLabels.Await()
 	resDaemonSetLabels, _ := resChDaemonSetLabels.Await()
 	resJobLabels, _ := resChJobLabels.Await()
 	resJobLabels, _ := resChJobLabels.Await()
+	resLBCostPerHr, _ := resChLBCostPerHr.Await()
+	resLBActiveMins, _ := resChLBActiveMins.Await()
 
 
 	if ctx.HasErrors() {
 	if ctx.HasErrors() {
 		for _, err := range ctx.Errors() {
 		for _, err := range ctx.Errors() {
@@ -259,7 +269,8 @@ func (cm *CostModel) ComputeAllocation(start, end time.Time, resolution time.Dur
 	applyAnnotations(podMap, namespaceAnnotations, podAnnotations)
 	applyAnnotations(podMap, namespaceAnnotations, podAnnotations)
 
 
 	serviceLabels := getServiceLabels(resServiceLabels)
 	serviceLabels := getServiceLabels(resServiceLabels)
-	applyServicesToPods(podMap, podLabels, serviceLabels)
+	allocsByService := map[serviceKey][]*kubecost.Allocation{}
+	applyServicesToPods(podMap, podLabels, allocsByService, serviceLabels)
 
 
 	podDeploymentMap := labelsToPodControllerMap(podLabels, resToDeploymentLabels(resDeploymentLabels))
 	podDeploymentMap := labelsToPodControllerMap(podLabels, resToDeploymentLabels(resDeploymentLabels))
 	podStatefulSetMap := labelsToPodControllerMap(podLabels, resToStatefulSetLabels(resStatefulSetLabels))
 	podStatefulSetMap := labelsToPodControllerMap(podLabels, resToStatefulSetLabels(resStatefulSetLabels))
@@ -308,6 +319,9 @@ func (cm *CostModel) ComputeAllocation(start, end time.Time, resolution time.Dur
 	// cluster representing each cluster's unmounted PVs (if necessary).
 	// cluster representing each cluster's unmounted PVs (if necessary).
 	applyUnmountedPVs(window, podMap, pvMap, pvcMap)
 	applyUnmountedPVs(window, podMap, pvMap, pvcMap)
 
 
+	lbMap := getLoadBalancerCosts(resLBCostPerHr, resLBActiveMins, resolution)
+	applyLoadBalancersToPods(lbMap, allocsByService)
+
 	// (3) Build out AllocationSet from Pod map
 	// (3) Build out AllocationSet from Pod map
 
 
 	for _, pod := range podMap {
 	for _, pod := range podMap {
@@ -1129,7 +1143,7 @@ func resToPodJobMap(resJobLabels []*prom.QueryResult) map[podKey]controllerKey {
 	return jobLabels
 	return jobLabels
 }
 }
 
 
-func applyServicesToPods(podMap map[podKey]*Pod, podLabels map[podKey]map[string]string, serviceLabels map[serviceKey]map[string]string) {
+func applyServicesToPods(podMap map[podKey]*Pod, podLabels map[podKey]map[string]string, allocsByService map[serviceKey][]*kubecost.Allocation, serviceLabels map[serviceKey]map[string]string) {
 	podServicesMap := map[podKey][]serviceKey{}
 	podServicesMap := map[podKey][]serviceKey{}
 
 
 	// For each service, turn the labels into a selector and attempt to
 	// For each service, turn the labels into a selector and attempt to
@@ -1163,8 +1177,10 @@ func applyServicesToPods(podMap map[podKey]*Pod, podLabels map[podKey]map[string
 				services := []string{}
 				services := []string{}
 				for _, sKey := range sKeys {
 				for _, sKey := range sKeys {
 					services = append(services, sKey.Service)
 					services = append(services, sKey.Service)
+					allocsByService[sKey] = append(allocsByService[sKey], alloc)
 				}
 				}
 				alloc.Properties.SetServices(services)
 				alloc.Properties.SetServices(services)
+
 			}
 			}
 		}
 		}
 	}
 	}
@@ -1587,6 +1603,80 @@ func applyUnmountedPVCs(window kubecost.Window, podMap map[podKey]*Pod, pvcMap m
 	}
 	}
 }
 }
 
 
+// LB describes the start and end time of a Load Balancer along with cost
+type LB struct {
+	TotalCost float64
+	Start     time.Time
+	End       time.Time
+}
+
+func getLoadBalancerCosts(resLBCost, resLBActiveMins []*prom.QueryResult, resolution time.Duration) map[serviceKey]*LB {
+	lbMap := make(map[serviceKey]*LB)
+	lbHourlyCosts := make(map[serviceKey]float64)
+	for _, res := range resLBCost {
+		serviceKey, err := resultServiceKey(res, "cluster_id", "namespace", "service_name")
+		if err != nil {
+			continue
+		}
+		lbHourlyCosts[serviceKey] = res.Values[0].Value
+	}
+	for _, res := range resLBActiveMins {
+		serviceKey, err := resultServiceKey(res, "cluster_id", "namespace", "service_name")
+		if err != nil || len(res.Values) == 0 {
+			continue
+		}
+		if _, ok := lbHourlyCosts[serviceKey]; !ok {
+			log.Warningf("CostModel: failed to find hourly cost for Load Balancer: %v", serviceKey)
+			continue
+		}
+
+		s := time.Unix(int64(res.Values[0].Timestamp), 0)
+		// subtract resolution from start time to cover full time period
+		s = s.Add(-resolution)
+		e := time.Unix(int64(res.Values[len(res.Values)-1].Timestamp), 0)
+		hours := e.Sub(s).Hours()
+
+		lbMap[serviceKey] = &LB{
+			TotalCost: lbHourlyCosts[serviceKey] * hours,
+			Start:     s,
+			End:       e,
+		}
+	}
+	return lbMap
+}
+
+func applyLoadBalancersToPods(lbMap map[serviceKey]*LB, allocsByService map[serviceKey][]*kubecost.Allocation) {
+	for sKey, lb := range lbMap {
+		totalHours := 0.0
+		allocHours := make(map[*kubecost.Allocation]float64)
+		// Add portion of load balancing cost to each allocation
+		// proportional to the total number of hours allocations used the load balancer
+		for _, alloc := range allocsByService[sKey] {
+			// Determine the (start, end) of the relationship between the
+			// given LB and the associated Allocation so that a precise
+			// number of hours can be used to compute cumulative cost.
+			s, e := alloc.Start, alloc.End
+			if lb.Start.After(alloc.Start) {
+				s = lb.Start
+			}
+			if lb.End.Before(alloc.End) {
+				e = lb.End
+			}
+			hours := e.Sub(s).Hours()
+			// A negative number of hours signifies no overlap between the windows
+			if hours > 0 {
+				totalHours += hours
+				allocHours[alloc] = hours
+			}
+		}
+
+		// Distribute cost of service once total hours is calculated
+		for alloc, hours := range allocHours {
+			alloc.LoadBalancerCost += lb.TotalCost * hours / totalHours
+		}
+	}
+}
+
 // getNodePricing determines node pricing, given a key and a mapping from keys
 // getNodePricing determines node pricing, given a key and a mapping from keys
 // to their NodePricing instances, as well as the custom pricing configuration
 // to their NodePricing instances, as well as the custom pricing configuration
 // inherent to the CostModel instance. If custom pricing is set, use that. If
 // inherent to the CostModel instance. If custom pricing is set, use that. If

+ 8 - 1
pkg/kubecost/allocation.go

@@ -62,6 +62,7 @@ type Allocation struct {
 	GPUHours               float64    `json:"gpuHours"`
 	GPUHours               float64    `json:"gpuHours"`
 	GPUCost                float64    `json:"gpuCost"`
 	GPUCost                float64    `json:"gpuCost"`
 	NetworkCost            float64    `json:"networkCost"`
 	NetworkCost            float64    `json:"networkCost"`
+	LoadBalancerCost       float64    `json:"loadBalancerCost"`
 	PVByteHours            float64    `json:"pvByteHours"`
 	PVByteHours            float64    `json:"pvByteHours"`
 	PVCost                 float64    `json:"pvCost"`
 	PVCost                 float64    `json:"pvCost"`
 	RAMByteHours           float64    `json:"ramByteHours"`
 	RAMByteHours           float64    `json:"ramByteHours"`
@@ -114,6 +115,7 @@ func (a *Allocation) Clone() *Allocation {
 		GPUHours:               a.GPUHours,
 		GPUHours:               a.GPUHours,
 		GPUCost:                a.GPUCost,
 		GPUCost:                a.GPUCost,
 		NetworkCost:            a.NetworkCost,
 		NetworkCost:            a.NetworkCost,
+		LoadBalancerCost:       a.LoadBalancerCost,
 		PVByteHours:            a.PVByteHours,
 		PVByteHours:            a.PVByteHours,
 		PVCost:                 a.PVCost,
 		PVCost:                 a.PVCost,
 		RAMByteHours:           a.RAMByteHours,
 		RAMByteHours:           a.RAMByteHours,
@@ -164,6 +166,9 @@ func (a *Allocation) Equal(that *Allocation) bool {
 	if !util.IsApproximately(a.NetworkCost, that.NetworkCost) {
 	if !util.IsApproximately(a.NetworkCost, that.NetworkCost) {
 		return false
 		return false
 	}
 	}
+	if !util.IsApproximately(a.LoadBalancerCost, that.LoadBalancerCost) {
+		return false
+	}
 	if !util.IsApproximately(a.PVByteHours, that.PVByteHours) {
 	if !util.IsApproximately(a.PVByteHours, that.PVByteHours) {
 		return false
 		return false
 	}
 	}
@@ -188,7 +193,7 @@ func (a *Allocation) Equal(that *Allocation) bool {
 
 
 // TotalCost is the total cost of the Allocation
 // TotalCost is the total cost of the Allocation
 func (a *Allocation) TotalCost() float64 {
 func (a *Allocation) TotalCost() float64 {
-	return a.CPUCost + a.GPUCost + a.RAMCost + a.PVCost + a.NetworkCost + a.SharedCost + a.ExternalCost
+	return a.CPUCost + a.GPUCost + a.RAMCost + a.PVCost + a.NetworkCost + a.SharedCost + a.ExternalCost + a.LoadBalancerCost
 }
 }
 
 
 // CPUEfficiency is the ratio of usage to request. If there is no request and
 // CPUEfficiency is the ratio of usage to request. If there is no request and
@@ -275,6 +280,7 @@ func (a *Allocation) MarshalJSON() ([]byte, error) {
 	jsonEncodeFloat64(buffer, "gpuHours", a.GPUHours, ",")
 	jsonEncodeFloat64(buffer, "gpuHours", a.GPUHours, ",")
 	jsonEncodeFloat64(buffer, "gpuCost", a.GPUCost, ",")
 	jsonEncodeFloat64(buffer, "gpuCost", a.GPUCost, ",")
 	jsonEncodeFloat64(buffer, "networkCost", a.NetworkCost, ",")
 	jsonEncodeFloat64(buffer, "networkCost", a.NetworkCost, ",")
+	jsonEncodeFloat64(buffer, "loadBalancerCost", a.LoadBalancerCost, ",")
 	jsonEncodeFloat64(buffer, "pvBytes", a.PVBytes(), ",")
 	jsonEncodeFloat64(buffer, "pvBytes", a.PVBytes(), ",")
 	jsonEncodeFloat64(buffer, "pvByteHours", a.PVByteHours, ",")
 	jsonEncodeFloat64(buffer, "pvByteHours", a.PVByteHours, ",")
 	jsonEncodeFloat64(buffer, "pvCost", a.PVCost, ",")
 	jsonEncodeFloat64(buffer, "pvCost", a.PVCost, ",")
@@ -426,6 +432,7 @@ func (a *Allocation) add(that *Allocation) {
 	a.RAMCost += that.RAMCost
 	a.RAMCost += that.RAMCost
 	a.PVCost += that.PVCost
 	a.PVCost += that.PVCost
 	a.NetworkCost += that.NetworkCost
 	a.NetworkCost += that.NetworkCost
+	a.LoadBalancerCost += that.LoadBalancerCost
 	a.SharedCost += that.SharedCost
 	a.SharedCost += that.SharedCost
 	a.ExternalCost += that.ExternalCost
 	a.ExternalCost += that.ExternalCost
 }
 }

+ 220 - 197
pkg/kubecost/allocation_test.go

@@ -45,6 +45,7 @@ func NewUnitAllocation(name string, start time.Time, resolution time.Duration, p
 		GPUHours:               1,
 		GPUHours:               1,
 		GPUCost:                1,
 		GPUCost:                1,
 		NetworkCost:            1,
 		NetworkCost:            1,
+		LoadBalancerCost:       1,
 		PVByteHours:            1,
 		PVByteHours:            1,
 		PVCost:                 1,
 		PVCost:                 1,
 		RAMByteHours:           1,
 		RAMByteHours:           1,
@@ -58,7 +59,7 @@ func NewUnitAllocation(name string, start time.Time, resolution time.Duration, p
 		alloc.PVByteHours = 0.0
 		alloc.PVByteHours = 0.0
 		alloc.PVCost = 0.0
 		alloc.PVCost = 0.0
 		alloc.NetworkCost = 0.0
 		alloc.NetworkCost = 0.0
-
+		alloc.LoadBalancerCost = 0.0
 		alloc.CPUCoreHours += 1.0
 		alloc.CPUCoreHours += 1.0
 		alloc.CPUCost += 1.0
 		alloc.CPUCost += 1.0
 		alloc.RAMByteHours += 1.0
 		alloc.RAMByteHours += 1.0
@@ -138,6 +139,7 @@ func TestAllocation_Add(t *testing.T) {
 		RAMBytesUsageAverage:   8.0 * gib,
 		RAMBytesUsageAverage:   8.0 * gib,
 		RAMCost:                8.0 * hrs2 * ramPrice,
 		RAMCost:                8.0 * hrs2 * ramPrice,
 		NetworkCost:            0.01,
 		NetworkCost:            0.01,
+		LoadBalancerCost:       0.05,
 		SharedCost:             0.00,
 		SharedCost:             0.00,
 		ExternalCost:           1.00,
 		ExternalCost:           1.00,
 	}
 	}
@@ -175,6 +177,9 @@ func TestAllocation_Add(t *testing.T) {
 	if !util.IsApproximately(a1.NetworkCost+a2.NetworkCost, act.NetworkCost) {
 	if !util.IsApproximately(a1.NetworkCost+a2.NetworkCost, act.NetworkCost) {
 		t.Fatalf("Allocation.Add: expected %f; actual %f", a1.NetworkCost+a2.NetworkCost, act.NetworkCost)
 		t.Fatalf("Allocation.Add: expected %f; actual %f", a1.NetworkCost+a2.NetworkCost, act.NetworkCost)
 	}
 	}
+	if !util.IsApproximately(a1.LoadBalancerCost+a2.LoadBalancerCost, act.LoadBalancerCost) {
+		t.Fatalf("Allocation.Add: expected %f; actual %f", a1.LoadBalancerCost+a2.LoadBalancerCost, act.LoadBalancerCost)
+	}
 	if !util.IsApproximately(a1.SharedCost+a2.SharedCost, act.SharedCost) {
 	if !util.IsApproximately(a1.SharedCost+a2.SharedCost, act.SharedCost) {
 		t.Fatalf("Allocation.Add: expected %f; actual %f", a1.SharedCost+a2.SharedCost, act.SharedCost)
 		t.Fatalf("Allocation.Add: expected %f; actual %f", a1.SharedCost+a2.SharedCost, act.SharedCost)
 	}
 	}
@@ -283,6 +288,7 @@ func TestAllocation_Share(t *testing.T) {
 		RAMBytesUsageAverage:   8.0 * gib,
 		RAMBytesUsageAverage:   8.0 * gib,
 		RAMCost:                8.0 * hrs2 * ramPrice,
 		RAMCost:                8.0 * hrs2 * ramPrice,
 		NetworkCost:            0.01,
 		NetworkCost:            0.01,
+		LoadBalancerCost:       0.05,
 		SharedCost:             0.00,
 		SharedCost:             0.00,
 		ExternalCost:           1.00,
 		ExternalCost:           1.00,
 	}
 	}
@@ -325,6 +331,9 @@ func TestAllocation_Share(t *testing.T) {
 	if !util.IsApproximately(a1.NetworkCost, act.NetworkCost) {
 	if !util.IsApproximately(a1.NetworkCost, act.NetworkCost) {
 		t.Fatalf("Allocation.Share: expected %f; actual %f", a1.NetworkCost, act.NetworkCost)
 		t.Fatalf("Allocation.Share: expected %f; actual %f", a1.NetworkCost, act.NetworkCost)
 	}
 	}
+	if !util.IsApproximately(a1.LoadBalancerCost, act.LoadBalancerCost) {
+		t.Fatalf("Allocation.Share: expected %f; actual %f", a1.LoadBalancerCost, act.LoadBalancerCost)
+	}
 	if !util.IsApproximately(a1.ExternalCost, act.ExternalCost) {
 	if !util.IsApproximately(a1.ExternalCost, act.ExternalCost) {
 		t.Fatalf("Allocation.Share: expected %f; actual %f", a1.ExternalCost, act.ExternalCost)
 		t.Fatalf("Allocation.Share: expected %f; actual %f", a1.ExternalCost, act.ExternalCost)
 	}
 	}
@@ -405,6 +414,7 @@ func TestAllocation_MarshalJSON(t *testing.T) {
 		GPUHours:               1.0 * hrs,
 		GPUHours:               1.0 * hrs,
 		GPUCost:                1.0 * hrs * gpuPrice,
 		GPUCost:                1.0 * hrs * gpuPrice,
 		NetworkCost:            0.05,
 		NetworkCost:            0.05,
+		LoadBalancerCost:       0.02,
 		PVByteHours:            100.0 * gib * hrs,
 		PVByteHours:            100.0 * gib * hrs,
 		PVCost:                 100.0 * hrs * pvPrice,
 		PVCost:                 100.0 * hrs * pvPrice,
 		RAMByteHours:           8.0 * gib * hrs,
 		RAMByteHours:           8.0 * gib * hrs,
@@ -667,7 +677,7 @@ func assertAllocationTotals(t *testing.T, as *AllocationSet, msg string, exps ma
 	as.Each(func(k string, a *Allocation) {
 	as.Each(func(k string, a *Allocation) {
 		if exp, ok := exps[a.Name]; ok {
 		if exp, ok := exps[a.Name]; ok {
 			if math.Round(a.TotalCost()*100) != math.Round(exp*100) {
 			if math.Round(a.TotalCost()*100) != math.Round(exp*100) {
-				t.Fatalf("AllocationSet.AggregateBy[%s]: expected total cost %.2f, actual %.2f", msg, exp, a.TotalCost())
+				t.Fatalf("AllocationSet.AggregateBy[%s]: expected total cost %f, actual %f", msg, exp, a.TotalCost())
 			}
 			}
 		} else {
 		} else {
 			t.Fatalf("AllocationSet.AggregateBy[%s]: unexpected allocation: %s", msg, a.Name)
 			t.Fatalf("AllocationSet.AggregateBy[%s]: unexpected allocation: %s", msg, a.Name)
@@ -700,45 +710,45 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	// Test AggregateBy against the following workload topology, which is
 	// Test AggregateBy against the following workload topology, which is
 	// generated by generateAllocationSet:
 	// generated by generateAllocationSet:
 
 
-	// | Hierarchy                              | Cost |  CPU |  RAM |  GPU |   PV |  Net |
-	// +----------------------------------------+------+------+------+------+------+------+
+	// | Hierarchy                              | Cost |  CPU |  RAM |  GPU |   PV |  Net |  LB  |
+	// +----------------------------------------+------+------+------+------+------+------+------+
 	//   cluster1:
 	//   cluster1:
-	//     idle:                                  20.00   5.00  15.00   0.00   0.00   0.00
+	//     idle:                                  20.00   5.00  15.00   0.00   0.00   0.00   0.00
 	//     namespace1:
 	//     namespace1:
 	//       pod1:
 	//       pod1:
-	//         container1: [app=app1, env=env1]   15.00   1.00  11.00   1.00   1.00   1.00
+	//         container1: [app=app1, env=env1]   16.00   1.00  11.00   1.00   1.00   1.00   1.00
 	//       pod-abc: (deployment1)
 	//       pod-abc: (deployment1)
-	//         container2:                         5.00   1.00   1.00   1.00   1.00   1.00
+	//         container2:                         6.00   1.00   1.00   1.00   1.00   1.00   1.00
 	//       pod-def: (deployment1)
 	//       pod-def: (deployment1)
-	//         container3:                         5.00   1.00   1.00   1.00   1.00   1.00
+	//         container3:                         6.00   1.00   1.00   1.00   1.00   1.00   1.00
 	//     namespace2:
 	//     namespace2:
 	//       pod-ghi: (deployment2)
 	//       pod-ghi: (deployment2)
-	//         container4: [app=app2, env=env2]    5.00   1.00   1.00   1.00   1.00   1.00
-	//         container5: [app=app2, env=env2]    5.00   1.00   1.00   1.00   1.00   1.00
+	//         container4: [app=app2, env=env2]    6.00   1.00   1.00   1.00   1.00   1.00   1.00
+	//         container5: [app=app2, env=env2]    6.00   1.00   1.00   1.00   1.00   1.00   1.00
 	//       pod-jkl: (daemonset1)
 	//       pod-jkl: (daemonset1)
-	//         container6: {service1}              5.00   1.00   1.00   1.00   1.00   1.00
-	// +-----------------------------------------+------+------+------+------+------+------+
-	//   cluster1 subtotal                        60.00  11.00  31.00   6.00   6.00   6.00
-	// +-----------------------------------------+------+------+------+------+------+------+
+	//         container6: {service1}              6.00   1.00   1.00   1.00   1.00   1.00   1.00
+	// +-----------------------------------------+------+------+------+------+------+------+------+
+	//   cluster1 subtotal                        66.00  11.00  31.00   6.00   6.00   6.00   6.00
+	// +-----------------------------------------+------+------+------+------+------+------+------+
 	//   cluster2:
 	//   cluster2:
-	//     idle:                                  10.00   5.00   5.00   0.00   0.00   0.00
+	//     idle:                                  10.00   5.00   5.00   0.00   0.00   0.00   0.00
 	//     namespace2:
 	//     namespace2:
 	//       pod-mno: (deployment2)
 	//       pod-mno: (deployment2)
-	//         container4: [app=app2]              5.00   1.00   1.00   1.00   1.00   1.00
-	//         container5: [app=app2]              5.00   1.00   1.00   1.00   1.00   1.00
+	//         container4: [app=app2]              6.00   1.00   1.00   1.00   1.00   1.00   1.00
+	//         container5: [app=app2]              6.00   1.00   1.00   1.00   1.00   1.00   1.00
 	//       pod-pqr: (daemonset1)
 	//       pod-pqr: (daemonset1)
-	//         container6: {service1}              5.00   1.00   1.00   1.00   1.00   1.00
+	//         container6: {service1}              6.00   1.00   1.00   1.00   1.00   1.00   1.00
 	//     namespace3:
 	//     namespace3:
 	//       pod-stu: (deployment3)
 	//       pod-stu: (deployment3)
-	//         container7: an[team=team1]          5.00   1.00   1.00   1.00   1.00   1.00
+	//         container7: an[team=team1]          6.00   1.00   1.00   1.00   1.00   1.00   1.00
 	//       pod-vwx: (statefulset1)
 	//       pod-vwx: (statefulset1)
-	//         container8: an[team=team2]          5.00   1.00   1.00   1.00   1.00   1.00
-	//         container9: an[team=team1]          5.00   1.00   1.00   1.00   1.00   1.00
-	// +----------------------------------------+------+------+------+------+------+------+
-	//   cluster2 subtotal                        40.00  11.00  11.00   6.00   6.00   6.00
-	// +----------------------------------------+------+------+------+------+------+------+
-	//   total                                   100.00  22.00  42.00  12.00  12.00  12.00
-	// +----------------------------------------+------+------+------+------+------+------+
+	//         container8: an[team=team2]          6.00   1.00   1.00   1.00   1.00   1.00   1.00
+	//         container9: an[team=team1]          6.00   1.00   1.00   1.00   1.00   1.00   1.00
+	// +----------------------------------------+------+------+------+------+------+------+------+
+	//   cluster2 subtotal                        46.00  11.00  11.00   6.00   6.00   6.00   6.00
+	// +----------------------------------------+------+------+------+------+------+------+------+
+	//   total                                   112.00  22.00  42.00  12.00  12.00  12.00  12.00
+	// +----------------------------------------+------+------+------+------+------+------+------+
 
 
 	// Scenarios to test:
 	// Scenarios to test:
 
 
@@ -809,7 +819,7 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	// There are two clusters, so each gets an idle entry when they are split
 	// There are two clusters, so each gets an idle entry when they are split
 	numSplitIdle := 2
 	numSplitIdle := 2
 
 
-	activeTotalCost := 70.0
+	activeTotalCost := 82.0
 	idleTotalCost := 30.0
 	idleTotalCost := 30.0
 	sharedOverheadHourlyCost := 7.0
 	sharedOverheadHourlyCost := 7.0
 
 
@@ -838,8 +848,8 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	err = as.AggregateBy(Properties{ClusterProp: ""}, nil)
 	err = as.AggregateBy(Properties{ClusterProp: ""}, nil)
 	assertAllocationSetTotals(t, as, "1a", err, numClusters+numIdle, activeTotalCost+idleTotalCost)
 	assertAllocationSetTotals(t, as, "1a", err, numClusters+numIdle, activeTotalCost+idleTotalCost)
 	assertAllocationTotals(t, as, "1a", map[string]float64{
 	assertAllocationTotals(t, as, "1a", map[string]float64{
-		"cluster1": 40.00,
-		"cluster2": 30.00,
+		"cluster1": 46.00,
+		"cluster2": 36.00,
 		IdleSuffix: 30.00,
 		IdleSuffix: 30.00,
 	})
 	})
 	assertAllocationWindow(t, as, "1a", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "1a", startYesterday, endYesterday, 1440.0)
@@ -849,9 +859,9 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	err = as.AggregateBy(Properties{NamespaceProp: true}, nil)
 	err = as.AggregateBy(Properties{NamespaceProp: true}, nil)
 	assertAllocationSetTotals(t, as, "1b", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost)
 	assertAllocationSetTotals(t, as, "1b", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost)
 	assertAllocationTotals(t, as, "1b", map[string]float64{
 	assertAllocationTotals(t, as, "1b", map[string]float64{
-		"namespace1": 25.00,
-		"namespace2": 30.00,
-		"namespace3": 15.00,
+		"namespace1": 28.00,
+		"namespace2": 36.00,
+		"namespace3": 18.00,
 		IdleSuffix:   30.00,
 		IdleSuffix:   30.00,
 	})
 	})
 	assertAllocationWindow(t, as, "1b", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "1b", startYesterday, endYesterday, 1440.0)
@@ -861,15 +871,15 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	err = as.AggregateBy(Properties{PodProp: true}, nil)
 	err = as.AggregateBy(Properties{PodProp: true}, nil)
 	assertAllocationSetTotals(t, as, "1c", err, numPods+numIdle, activeTotalCost+idleTotalCost)
 	assertAllocationSetTotals(t, as, "1c", err, numPods+numIdle, activeTotalCost+idleTotalCost)
 	assertAllocationTotals(t, as, "1c", map[string]float64{
 	assertAllocationTotals(t, as, "1c", map[string]float64{
-		"pod-jkl":  5.00,
-		"pod-stu":  5.00,
-		"pod-abc":  5.00,
-		"pod-pqr":  5.00,
-		"pod-def":  5.00,
-		"pod-vwx":  10.00,
-		"pod1":     15.00,
-		"pod-mno":  10.00,
-		"pod-ghi":  10.00,
+		"pod-jkl":  6.00,
+		"pod-stu":  6.00,
+		"pod-abc":  6.00,
+		"pod-pqr":  6.00,
+		"pod-def":  6.00,
+		"pod-vwx":  12.00,
+		"pod1":     16.00,
+		"pod-mno":  12.00,
+		"pod-ghi":  12.00,
 		IdleSuffix: 30.00,
 		IdleSuffix: 30.00,
 	})
 	})
 	assertAllocationWindow(t, as, "1c", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "1c", startYesterday, endYesterday, 1440.0)
@@ -879,15 +889,15 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	err = as.AggregateBy(Properties{ContainerProp: true}, nil)
 	err = as.AggregateBy(Properties{ContainerProp: true}, nil)
 	assertAllocationSetTotals(t, as, "1d", err, numContainers+numIdle, activeTotalCost+idleTotalCost)
 	assertAllocationSetTotals(t, as, "1d", err, numContainers+numIdle, activeTotalCost+idleTotalCost)
 	assertAllocationTotals(t, as, "1d", map[string]float64{
 	assertAllocationTotals(t, as, "1d", map[string]float64{
-		"container2": 5.00,
-		"container9": 5.00,
-		"container6": 10.00,
-		"container3": 5.00,
-		"container4": 10.00,
-		"container7": 5.00,
-		"container8": 5.00,
-		"container5": 10.00,
-		"container1": 15.00,
+		"container2": 6.00,
+		"container9": 6.00,
+		"container6": 12.00,
+		"container3": 6.00,
+		"container4": 12.00,
+		"container7": 6.00,
+		"container8": 6.00,
+		"container5": 12.00,
+		"container1": 16.00,
 		IdleSuffix:   30.00,
 		IdleSuffix:   30.00,
 	})
 	})
 	assertAllocationWindow(t, as, "1d", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "1d", startYesterday, endYesterday, 1440.0)
@@ -897,11 +907,11 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	err = as.AggregateBy(Properties{ControllerKindProp: true}, nil)
 	err = as.AggregateBy(Properties{ControllerKindProp: true}, nil)
 	assertAllocationSetTotals(t, as, "1e", err, numControllerKinds+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
 	assertAllocationSetTotals(t, as, "1e", err, numControllerKinds+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
 	assertAllocationTotals(t, as, "1e", map[string]float64{
 	assertAllocationTotals(t, as, "1e", map[string]float64{
-		"daemonset":       10.00,
-		"deployment":      35.00,
-		"statefulset":     10.00,
+		"daemonset":       12.00,
+		"deployment":      42.00,
+		"statefulset":     12.00,
 		IdleSuffix:        30.00,
 		IdleSuffix:        30.00,
-		UnallocatedSuffix: 15.00,
+		UnallocatedSuffix: 16.00,
 	})
 	})
 	assertAllocationWindow(t, as, "1e", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "1e", startYesterday, endYesterday, 1440.0)
 
 
@@ -910,13 +920,13 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	err = as.AggregateBy(Properties{ControllerProp: true}, nil)
 	err = as.AggregateBy(Properties{ControllerProp: true}, nil)
 	assertAllocationSetTotals(t, as, "1f", err, numControllers+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
 	assertAllocationSetTotals(t, as, "1f", err, numControllers+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
 	assertAllocationTotals(t, as, "1f", map[string]float64{
 	assertAllocationTotals(t, as, "1f", map[string]float64{
-		"deployment/deployment2":   20.00,
-		"daemonset/daemonset1":     10.00,
-		"deployment/deployment3":   5.00,
-		"statefulset/statefulset1": 10.00,
-		"deployment/deployment1":   10.00,
+		"deployment/deployment2":   24.00,
+		"daemonset/daemonset1":     12.00,
+		"deployment/deployment3":   6.00,
+		"statefulset/statefulset1": 12.00,
+		"deployment/deployment1":   12.00,
 		IdleSuffix:                 30.00,
 		IdleSuffix:                 30.00,
-		UnallocatedSuffix:          15.00,
+		UnallocatedSuffix:          16.00,
 	})
 	})
 	assertAllocationWindow(t, as, "1f", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "1f", startYesterday, endYesterday, 1440.0)
 
 
@@ -925,9 +935,9 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	err = as.AggregateBy(Properties{ServiceProp: true}, nil)
 	err = as.AggregateBy(Properties{ServiceProp: true}, nil)
 	assertAllocationSetTotals(t, as, "1g", err, numServices+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
 	assertAllocationSetTotals(t, as, "1g", err, numServices+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
 	assertAllocationTotals(t, as, "1g", map[string]float64{
 	assertAllocationTotals(t, as, "1g", map[string]float64{
-		"service1":        10.00,
+		"service1":        12.00,
 		IdleSuffix:        30.00,
 		IdleSuffix:        30.00,
-		UnallocatedSuffix: 60.00,
+		UnallocatedSuffix: 70.00,
 	})
 	})
 	assertAllocationWindow(t, as, "1g", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "1g", startYesterday, endYesterday, 1440.0)
 
 
@@ -936,10 +946,10 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	err = as.AggregateBy(Properties{LabelProp: map[string]string{"app": ""}}, nil)
 	err = as.AggregateBy(Properties{LabelProp: map[string]string{"app": ""}}, nil)
 	assertAllocationSetTotals(t, as, "1h", err, numLabelApps+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
 	assertAllocationSetTotals(t, as, "1h", err, numLabelApps+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
 	assertAllocationTotals(t, as, "1h", map[string]float64{
 	assertAllocationTotals(t, as, "1h", map[string]float64{
-		"app=app1":        15.00,
-		"app=app2":        20.00,
+		"app=app1":        16.00,
+		"app=app2":        24.00,
 		IdleSuffix:        30.00,
 		IdleSuffix:        30.00,
-		UnallocatedSuffix: 35.00,
+		UnallocatedSuffix: 42.00,
 	})
 	})
 	assertAllocationWindow(t, as, "1h", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "1h", startYesterday, endYesterday, 1440.0)
 
 
@@ -948,9 +958,9 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	err = as.AggregateBy(Properties{ControllerKindProp: "deployment"}, nil)
 	err = as.AggregateBy(Properties{ControllerKindProp: "deployment"}, nil)
 	assertAllocationSetTotals(t, as, "1i", err, 1+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
 	assertAllocationSetTotals(t, as, "1i", err, 1+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
 	assertAllocationTotals(t, as, "1i", map[string]float64{
 	assertAllocationTotals(t, as, "1i", map[string]float64{
-		"deployment":      35.00,
+		"deployment":      42.00,
 		IdleSuffix:        30.00,
 		IdleSuffix:        30.00,
-		UnallocatedSuffix: 35.00,
+		UnallocatedSuffix: 40.00,
 	})
 	})
 	assertAllocationWindow(t, as, "1i", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "1i", startYesterday, endYesterday, 1440.0)
 
 
@@ -959,10 +969,10 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	err = as.AggregateBy(Properties{AnnotationProp: map[string]string{"team": ""}}, nil)
 	err = as.AggregateBy(Properties{AnnotationProp: map[string]string{"team": ""}}, nil)
 	assertAllocationSetTotals(t, as, "1j", err, 2+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
 	assertAllocationSetTotals(t, as, "1j", err, 2+numIdle+numUnallocated, activeTotalCost+idleTotalCost)
 	assertAllocationTotals(t, as, "1j", map[string]float64{
 	assertAllocationTotals(t, as, "1j", map[string]float64{
-		"team=team1":      10.00,
-		"team=team2":      5.00,
+		"team=team1":      12.00,
+		"team=team2":      6.00,
 		IdleSuffix:        30.00,
 		IdleSuffix:        30.00,
-		UnallocatedSuffix: 55.00,
+		UnallocatedSuffix: 64.00,
 	})
 	})
 	assertAllocationWindow(t, as, "1i", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "1i", startYesterday, endYesterday, 1440.0)
 
 
@@ -978,11 +988,11 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	// sets should be {idle, unallocated, app1/env1, app2/env2, app2/unallocated}
 	// sets should be {idle, unallocated, app1/env1, app2/env2, app2/unallocated}
 	assertAllocationSetTotals(t, as, "2d", err, numIdle+numUnallocated+3, activeTotalCost+idleTotalCost)
 	assertAllocationSetTotals(t, as, "2d", err, numIdle+numUnallocated+3, activeTotalCost+idleTotalCost)
 	assertAllocationTotals(t, as, "2d", map[string]float64{
 	assertAllocationTotals(t, as, "2d", map[string]float64{
-		"app=app1/env=env1":             15.00,
-		"app=app2/env=env2":             10.00,
-		"app=app2/" + UnallocatedSuffix: 10.00,
+		"app=app1/env=env1":             16.00,
+		"app=app2/env=env2":             12.00,
+		"app=app2/" + UnallocatedSuffix: 12.00,
 		IdleSuffix:                      30.00,
 		IdleSuffix:                      30.00,
-		UnallocatedSuffix:               35.00,
+		UnallocatedSuffix:               42.00,
 	})
 	})
 
 
 	// 2e AggregationProperties=(Cluster, Label:app, Label:environment)
 	// 2e AggregationProperties=(Cluster, Label:app, Label:environment)
@@ -990,12 +1000,12 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	err = as.AggregateBy(Properties{ClusterProp: "", LabelProp: map[string]string{"app": "", "env": ""}}, nil)
 	err = as.AggregateBy(Properties{ClusterProp: "", LabelProp: map[string]string{"app": "", "env": ""}}, nil)
 	assertAllocationSetTotals(t, as, "2e", err, 6, activeTotalCost+idleTotalCost)
 	assertAllocationSetTotals(t, as, "2e", err, 6, activeTotalCost+idleTotalCost)
 	assertAllocationTotals(t, as, "2e", map[string]float64{
 	assertAllocationTotals(t, as, "2e", map[string]float64{
-		"cluster1/app=app2/env=env2":             10.00,
+		"cluster1/app=app2/env=env2":             12.00,
 		"__idle__":                               30.00,
 		"__idle__":                               30.00,
-		"cluster1/app=app1/env=env1":             15.00,
-		"cluster1/" + UnallocatedSuffix:          15.00,
-		"cluster2/app=app2/" + UnallocatedSuffix: 10.00,
-		"cluster2/" + UnallocatedSuffix:          20.00,
+		"cluster1/app=app1/env=env1":             16.00,
+		"cluster1/" + UnallocatedSuffix:          18.00,
+		"cluster2/app=app2/" + UnallocatedSuffix: 12.00,
+		"cluster2/" + UnallocatedSuffix:          24.00,
 	})
 	})
 
 
 	// 2f AggregationProperties=(annotation:team, pod)
 	// 2f AggregationProperties=(annotation:team, pod)
@@ -1003,16 +1013,16 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	err = as.AggregateBy(Properties{AnnotationProp: map[string]string{"team": ""}, PodProp: ""}, nil)
 	err = as.AggregateBy(Properties{AnnotationProp: map[string]string{"team": ""}, PodProp: ""}, nil)
 	assertAllocationSetTotals(t, as, "2e", err, 11, activeTotalCost+idleTotalCost)
 	assertAllocationSetTotals(t, as, "2e", err, 11, activeTotalCost+idleTotalCost)
 	assertAllocationTotals(t, as, "2e", map[string]float64{
 	assertAllocationTotals(t, as, "2e", map[string]float64{
-		"pod-jkl/" + UnallocatedSuffix: 5.00,
-		"pod-stu/team=team1":           5.00,
-		"pod-abc/" + UnallocatedSuffix: 5.00,
-		"pod-pqr/" + UnallocatedSuffix: 5.00,
-		"pod-def/" + UnallocatedSuffix: 5.00,
-		"pod-vwx/team=team1":           5.00,
-		"pod-vwx/team=team2":           5.00,
-		"pod1/" + UnallocatedSuffix:    15.00,
-		"pod-mno/" + UnallocatedSuffix: 10.00,
-		"pod-ghi/" + UnallocatedSuffix: 10.00,
+		"pod-jkl/" + UnallocatedSuffix: 6.00,
+		"pod-stu/team=team1":           6.00,
+		"pod-abc/" + UnallocatedSuffix: 6.00,
+		"pod-pqr/" + UnallocatedSuffix: 6.00,
+		"pod-def/" + UnallocatedSuffix: 6.00,
+		"pod-vwx/team=team1":           6.00,
+		"pod-vwx/team=team2":           6.00,
+		"pod1/" + UnallocatedSuffix:    16.00,
+		"pod-mno/" + UnallocatedSuffix: 12.00,
+		"pod-ghi/" + UnallocatedSuffix: 12.00,
 		IdleSuffix:                     30.00,
 		IdleSuffix:                     30.00,
 	})
 	})
 
 
@@ -1021,38 +1031,38 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	// // 3  Share idle
 	// // 3  Share idle
 
 
 	// 3a AggregationProperties=(Namespace) ShareIdle=ShareWeighted
 	// 3a AggregationProperties=(Namespace) ShareIdle=ShareWeighted
-	// namespace1: 39.6875 = 25.00 + 5.00*(3.00/6.00) + 15.0*(13.0/16.0)
-	// namespace2: 40.3125 = 30.00 + 5.0*(3.0/6.0) + 15.0*(3.0/16.0) + 5.0*(3.0/6.0) + 5.0*(3.0/6.0)
-	// namespace3: 20.0000 = 15.00 + 5.0*(3.0/6.0) + 5.0*(3.0/6.0)
+	// namespace1: 42.6875 = 28.00 + 5.00*(3.00/6.00) + 15.0*(13.0/16.0)
+	// namespace2: 46.3125 = 36.00 + 5.0*(3.0/6.0) + 15.0*(3.0/16.0) + 5.0*(3.0/6.0) + 5.0*(3.0/6.0)
+	// namespace3: 23.0000 = 18.00 + 5.0*(3.0/6.0) + 5.0*(3.0/6.0)
 	as = generateAllocationSet(start)
 	as = generateAllocationSet(start)
 	err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareWeighted})
 	err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareWeighted})
 	assertAllocationSetTotals(t, as, "3a", err, numNamespaces, activeTotalCost+idleTotalCost)
 	assertAllocationSetTotals(t, as, "3a", err, numNamespaces, activeTotalCost+idleTotalCost)
 	assertAllocationTotals(t, as, "3a", map[string]float64{
 	assertAllocationTotals(t, as, "3a", map[string]float64{
-		"namespace1": 39.69,
-		"namespace2": 40.31,
-		"namespace3": 20.00,
+		"namespace1": 42.69,
+		"namespace2": 46.31,
+		"namespace3": 23.00,
 	})
 	})
 	assertAllocationWindow(t, as, "3a", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "3a", startYesterday, endYesterday, 1440.0)
 
 
 	// 3b AggregationProperties=(Namespace) ShareIdle=ShareEven
 	// 3b AggregationProperties=(Namespace) ShareIdle=ShareEven
-	// namespace1: 35.0000 = 25.00 + 5.00*(1.0/2.0) + 15.0*(1.0/2.0)
-	// namespace2: 45.0000 = 30.00 + 5.0*(1.0/2.0) + 15.0*(1.0/2.0) + 5.0*(1.0/2.0) + 5.0*(1.0/2.0)
-	// namespace3: 20.0000 = 15.00 + 5.0*(1.0/2.0) + 5.0*(1.0/2.0)
+	// namespace1: 38.0000 = 28.00 + 5.00*(1.0/2.0) + 15.0*(1.0/2.0)
+	// namespace2: 51.0000 = 36.00 + 5.0*(1.0/2.0) + 15.0*(1.0/2.0) + 5.0*(1.0/2.0) + 5.0*(1.0/2.0)
+	// namespace3: 23.0000 = 18.00 + 5.0*(1.0/2.0) + 5.0*(1.0/2.0)
 	as = generateAllocationSet(start)
 	as = generateAllocationSet(start)
 	err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareEven})
 	err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{ShareIdle: ShareEven})
 	assertAllocationSetTotals(t, as, "3a", err, numNamespaces, activeTotalCost+idleTotalCost)
 	assertAllocationSetTotals(t, as, "3a", err, numNamespaces, activeTotalCost+idleTotalCost)
 	assertAllocationTotals(t, as, "3a", map[string]float64{
 	assertAllocationTotals(t, as, "3a", map[string]float64{
-		"namespace1": 35.00,
-		"namespace2": 45.00,
-		"namespace3": 20.00,
+		"namespace1": 38.00,
+		"namespace2": 51.00,
+		"namespace3": 23.00,
 	})
 	})
 	assertAllocationWindow(t, as, "3b", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "3b", startYesterday, endYesterday, 1440.0)
 
 
 	// 4  Share resources
 	// 4  Share resources
 
 
 	// 4a Share namespace ShareEven
 	// 4a Share namespace ShareEven
-	// namespace1: 32.5000 = 25.00 + 15.00*(1.0/2.0)
-	// namespace2: 37.5000 = 30.00 + 15.00*(1.0/2.0)
+	// namespace1: 37.5000 = 28.00 + 18.00*(1.0/2.0)
+	// namespace2: 45.5000 = 36.00 + 18.00*(1.0/2.0)
 	// idle:       30.0000
 	// idle:       30.0000
 	as = generateAllocationSet(start)
 	as = generateAllocationSet(start)
 	err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
 	err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
@@ -1061,8 +1071,8 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	})
 	})
 	assertAllocationSetTotals(t, as, "4a", err, numNamespaces, activeTotalCost+idleTotalCost)
 	assertAllocationSetTotals(t, as, "4a", err, numNamespaces, activeTotalCost+idleTotalCost)
 	assertAllocationTotals(t, as, "4a", map[string]float64{
 	assertAllocationTotals(t, as, "4a", map[string]float64{
-		"namespace1": 32.50,
-		"namespace2": 37.50,
+		"namespace1": 37.00,
+		"namespace2": 45.00,
 		IdleSuffix:   30.00,
 		IdleSuffix:   30.00,
 	})
 	})
 	assertAllocationWindow(t, as, "4a", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "4a", startYesterday, endYesterday, 1440.0)
@@ -1078,16 +1088,16 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	})
 	})
 	assertAllocationSetTotals(t, as, "4b", err, numNamespaces, activeTotalCost+idleTotalCost)
 	assertAllocationSetTotals(t, as, "4b", err, numNamespaces, activeTotalCost+idleTotalCost)
 	assertAllocationTotals(t, as, "4b", map[string]float64{
 	assertAllocationTotals(t, as, "4b", map[string]float64{
-		"namespace1": 31.82,
-		"namespace2": 38.18,
+		"namespace1": 35.88,
+		"namespace2": 46.125,
 		IdleSuffix:   30.00,
 		IdleSuffix:   30.00,
 	})
 	})
 	assertAllocationWindow(t, as, "4b", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "4b", startYesterday, endYesterday, 1440.0)
 
 
 	// 4c Share label ShareEven
 	// 4c Share label ShareEven
-	// namespace1: 15.0000 = 25.00 - 15.00 + 15.00*(1.0/3.0)
-	// namespace2: 35.0000 = 30.00 + 15.00*(1.0/3.0)
-	// namespace3: 20.0000 = 15.00 + 15.00*(1.0/3.0)
+	// namespace1: 17.3333 = 28.00 - 16.00 + 16.00*(1.0/3.0)
+	// namespace2: 41.3333 = 36.00 + 16.00*(1.0/3.0)
+	// namespace3: 23.3333 = 18.00 + 16.00*(1.0/3.0)
 	// idle:       30.0000
 	// idle:       30.0000
 	as = generateAllocationSet(start)
 	as = generateAllocationSet(start)
 	err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
 	err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
@@ -1096,17 +1106,17 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	})
 	})
 	assertAllocationSetTotals(t, as, "4c", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost)
 	assertAllocationSetTotals(t, as, "4c", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost)
 	assertAllocationTotals(t, as, "4c", map[string]float64{
 	assertAllocationTotals(t, as, "4c", map[string]float64{
-		"namespace1": 15.00,
-		"namespace2": 35.00,
-		"namespace3": 20.00,
+		"namespace1": 17.33,
+		"namespace2": 41.33,
+		"namespace3": 23.33,
 		IdleSuffix:   30.00,
 		IdleSuffix:   30.00,
 	})
 	})
 	assertAllocationWindow(t, as, "4c", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "4c", startYesterday, endYesterday, 1440.0)
 
 
 	// 4d Share overhead ShareWeighted
 	// 4d Share overhead ShareWeighted
-	// namespace1: 85 = 25.00 + (7.0*24.0)*(25.00/70.00)
-	// namespace2: 102 = 30.00 + (7.0*24.0)*(30.00/70.00)
-	// namespace3: 51 = 15.00 + (7.0*24.0)*(15.00/70.00)
+	// namespace1: 85.366 = 28.00 + (7.0*24.0)*(28.00/82.00)
+	// namespace2: 109.756 = 36.00 + (7.0*24.0)*(36.00/82.00)
+	// namespace3: 54.878 = 18.00 + (7.0*24.0)*(18.00/82.00)
 	// idle:       30.0000
 	// idle:       30.0000
 	as = generateAllocationSet(start)
 	as = generateAllocationSet(start)
 	err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
 	err = as.AggregateBy(Properties{NamespaceProp: true}, &AllocationAggregationOptions{
@@ -1115,9 +1125,9 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	})
 	})
 	assertAllocationSetTotals(t, as, "4d", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost+(sharedOverheadHourlyCost*24.0))
 	assertAllocationSetTotals(t, as, "4d", err, numNamespaces+numIdle, activeTotalCost+idleTotalCost+(sharedOverheadHourlyCost*24.0))
 	assertAllocationTotals(t, as, "4d", map[string]float64{
 	assertAllocationTotals(t, as, "4d", map[string]float64{
-		"namespace1": 85.00,
-		"namespace2": 102.00,
-		"namespace3": 51.00,
+		"namespace1": 85.366,
+		"namespace2": 109.756,
+		"namespace3": 54.878,
 		IdleSuffix:   30.00,
 		IdleSuffix:   30.00,
 	})
 	})
 	assertAllocationWindow(t, as, "4d", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "4d", startYesterday, endYesterday, 1440.0)
@@ -1144,9 +1154,9 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 		FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
 		FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
 		ShareIdle:   ShareNone,
 		ShareIdle:   ShareNone,
 	})
 	})
-	assertAllocationSetTotals(t, as, "5a", err, 2, 60.0)
+	assertAllocationSetTotals(t, as, "5a", err, 2, 66.0)
 	assertAllocationTotals(t, as, "5a", map[string]float64{
 	assertAllocationTotals(t, as, "5a", map[string]float64{
-		"cluster1": 40.00,
+		"cluster1": 46.00,
 		IdleSuffix: 20.00,
 		IdleSuffix: 20.00,
 	})
 	})
 	assertAllocationWindow(t, as, "5a", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "5a", startYesterday, endYesterday, 1440.0)
@@ -1157,9 +1167,9 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 		FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
 		FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
 		ShareIdle:   ShareWeighted,
 		ShareIdle:   ShareWeighted,
 	})
 	})
-	assertAllocationSetTotals(t, as, "5b", err, 1, 60.0)
+	assertAllocationSetTotals(t, as, "5b", err, 1, 66.0)
 	assertAllocationTotals(t, as, "5b", map[string]float64{
 	assertAllocationTotals(t, as, "5b", map[string]float64{
-		"cluster1": 60.00,
+		"cluster1": 66.00,
 	})
 	})
 	assertAllocationWindow(t, as, "5b", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "5b", startYesterday, endYesterday, 1440.0)
 
 
@@ -1169,10 +1179,10 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 		FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
 		FilterFuncs: []AllocationMatchFunc{isCluster("cluster1")},
 		ShareIdle:   ShareNone,
 		ShareIdle:   ShareNone,
 	})
 	})
-	assertAllocationSetTotals(t, as, "5c", err, 3, 60.0)
+	assertAllocationSetTotals(t, as, "5c", err, 3, 66.0)
 	assertAllocationTotals(t, as, "5c", map[string]float64{
 	assertAllocationTotals(t, as, "5c", map[string]float64{
-		"namespace1": 25.00,
-		"namespace2": 15.00,
+		"namespace1": 28.00,
+		"namespace2": 18.00,
 		IdleSuffix:   20.00,
 		IdleSuffix:   20.00,
 	})
 	})
 	assertAllocationWindow(t, as, "5c", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "5c", startYesterday, endYesterday, 1440.0)
@@ -1183,10 +1193,10 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 		FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
 		FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
 		ShareIdle:   ShareNone,
 		ShareIdle:   ShareNone,
 	})
 	})
-	assertAllocationSetTotals(t, as, "5d", err, 3, 40.31)
+	assertAllocationSetTotals(t, as, "5d", err, 3, 46.31)
 	assertAllocationTotals(t, as, "5d", map[string]float64{
 	assertAllocationTotals(t, as, "5d", map[string]float64{
-		"cluster1": 15.00,
-		"cluster2": 15.00,
+		"cluster1": 18.00,
+		"cluster2": 18.00,
 		IdleSuffix: 10.31,
 		IdleSuffix: 10.31,
 	})
 	})
 	assertAllocationWindow(t, as, "5d", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "5d", startYesterday, endYesterday, 1440.0)
@@ -1198,9 +1208,9 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{SplitIdle: true})
 	err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{SplitIdle: true})
 	assertAllocationSetTotals(t, as, "6a", err, numNamespaces+numSplitIdle, activeTotalCost+idleTotalCost)
 	assertAllocationSetTotals(t, as, "6a", err, numNamespaces+numSplitIdle, activeTotalCost+idleTotalCost)
 	assertAllocationTotals(t, as, "6a", map[string]float64{
 	assertAllocationTotals(t, as, "6a", map[string]float64{
-		"namespace1":                           25.00,
-		"namespace2":                           30.00,
-		"namespace3":                           15.00,
+		"namespace1":                           28.00,
+		"namespace2":                           36.00,
+		"namespace3":                           18.00,
 		fmt.Sprintf("cluster1/%s", IdleSuffix): 20.00,
 		fmt.Sprintf("cluster1/%s", IdleSuffix): 20.00,
 		fmt.Sprintf("cluster2/%s", IdleSuffix): 10.00,
 		fmt.Sprintf("cluster2/%s", IdleSuffix): 10.00,
 	})
 	})
@@ -1208,36 +1218,36 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 
 
 	// 6b Share idle weighted with filters
 	// 6b Share idle weighted with filters
 	// Should match values from unfiltered aggregation (3a)
 	// Should match values from unfiltered aggregation (3a)
-	// namespace2: 40.3125 = 30.00 + 5.0*(3.0/6.0) + 15.0*(3.0/16.0) + 5.0*(3.0/6.0) + 5.0*(3.0/6.0)
+	// namespace2: 46.3125 = 36.00 + 5.0*(3.0/6.0) + 15.0*(3.0/16.0) + 5.0*(3.0/6.0) + 5.0*(3.0/6.0)
 	as = generateAllocationSet(start)
 	as = generateAllocationSet(start)
 	err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
 	err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
 		FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
 		FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
 		ShareIdle:   ShareWeighted,
 		ShareIdle:   ShareWeighted,
 	})
 	})
-	assertAllocationSetTotals(t, as, "6b", err, 1, 40.31)
+	assertAllocationSetTotals(t, as, "6b", err, 1, 46.31)
 	assertAllocationTotals(t, as, "6b", map[string]float64{
 	assertAllocationTotals(t, as, "6b", map[string]float64{
-		"namespace2": 40.31,
+		"namespace2": 46.31,
 	})
 	})
 	assertAllocationWindow(t, as, "6b", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "6b", startYesterday, endYesterday, 1440.0)
 
 
 	// 6c Share idle even with filters
 	// 6c Share idle even with filters
 	// Should match values from unfiltered aggregation (3b)
 	// Should match values from unfiltered aggregation (3b)
-	// namespace2: 45.0000 = 30.00 + 5.0*(1.0/2.0) + 15.0*(1.0/2.0) + 5.0*(1.0/2.0) + 5.0*(1.0/2.0)
+	// namespace2: 51.0000 = 36.00 + 5.0*(1.0/2.0) + 15.0*(1.0/2.0) + 5.0*(1.0/2.0) + 5.0*(1.0/2.0)
 	as = generateAllocationSet(start)
 	as = generateAllocationSet(start)
 	err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
 	err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
 		FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
 		FilterFuncs: []AllocationMatchFunc{isNamespace("namespace2")},
 		ShareIdle:   ShareEven,
 		ShareIdle:   ShareEven,
 	})
 	})
-	assertAllocationSetTotals(t, as, "6b", err, 1, 45.00)
+	assertAllocationSetTotals(t, as, "6b", err, 1, 51.00)
 	assertAllocationTotals(t, as, "6b", map[string]float64{
 	assertAllocationTotals(t, as, "6b", map[string]float64{
-		"namespace2": 45.00,
+		"namespace2": 51.00,
 	})
 	})
 	assertAllocationWindow(t, as, "6b", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "6b", startYesterday, endYesterday, 1440.0)
 
 
 	// 6d Share overhead with filters
 	// 6d Share overhead with filters
-	// namespace1: 85 = 25.00 + (7.0*24.0)*(25.00/70.00)
-	// namespace2: 102 = 30.00 + (7.0*24.0)*(30.00/70.00)
-	// namespace3: 51 = 15.00 + (7.0*24.0)*(15.00/70.00)
+	// namespace1: 85.366 = 28.00 + (7.0*24.0)*(28.00/82.00)
+	// namespace2: 109.756 = 36.00 + (7.0*24.0)*(36.00/82.00)
+	// namespace3: 54.878 = 18.00 + (7.0*24.0)*(18.00/82.00)
 	// idle:       30.0000
 	// idle:       30.0000
 	// Then namespace 2 is filtered.
 	// Then namespace 2 is filtered.
 	as = generateAllocationSet(start)
 	as = generateAllocationSet(start)
@@ -1246,20 +1256,20 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 		SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
 		SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
 		ShareSplit:        ShareWeighted,
 		ShareSplit:        ShareWeighted,
 	})
 	})
-	assertAllocationSetTotals(t, as, "6d", err, 2, 132.00)
+	assertAllocationSetTotals(t, as, "6d", err, 2, 139.756)
 	assertAllocationTotals(t, as, "6d", map[string]float64{
 	assertAllocationTotals(t, as, "6d", map[string]float64{
-		"namespace2": 102.00,
+		"namespace2": 109.756,
 		IdleSuffix:   30.00,
 		IdleSuffix:   30.00,
 	})
 	})
 	assertAllocationWindow(t, as, "6d", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "6d", startYesterday, endYesterday, 1440.0)
 
 
 	// 6e Share resources with filters
 	// 6e Share resources with filters
 	// --- Shared ---
 	// --- Shared ---
-	// namespace1: 25.00 (gets shared among namespace2 and namespace3)
+	// namespace1: 28.00 (gets shared among namespace2 and namespace3)
 	// --- Filtered ---
 	// --- Filtered ---
-	// namespace3: 23.33 = 15.00 + (25.00)*(15.00/45.00) (filtered out)
+	// namespace3: 27.33 = 18.00 + (28.00)*(18.00/54.00) (filtered out)
 	// --- Results ---
 	// --- Results ---
-	// namespace2: 46.67 = 30.00 + (25.00)*(15.00/45.00)
+	// namespace2: 54.667 = 36.00 + (28.00)*(36.00/54.00)
 	// idle:       30.0000
 	// idle:       30.0000
 	as = generateAllocationSet(start)
 	as = generateAllocationSet(start)
 	err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
 	err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
@@ -1267,9 +1277,9 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 		ShareFuncs:  []AllocationMatchFunc{isNamespace("namespace1")},
 		ShareFuncs:  []AllocationMatchFunc{isNamespace("namespace1")},
 		ShareSplit:  ShareWeighted,
 		ShareSplit:  ShareWeighted,
 	})
 	})
-	assertAllocationSetTotals(t, as, "6e", err, 2, 76.67)
+	assertAllocationSetTotals(t, as, "6e", err, 2, 84.667)
 	assertAllocationTotals(t, as, "6e", map[string]float64{
 	assertAllocationTotals(t, as, "6e", map[string]float64{
-		"namespace2": 46.67,
+		"namespace2": 54.667,
 		IdleSuffix:   30.00,
 		IdleSuffix:   30.00,
 	})
 	})
 	assertAllocationWindow(t, as, "6e", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "6e", startYesterday, endYesterday, 1440.0)
@@ -1278,20 +1288,20 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	//
 	//
 	// First, share idle weighted produces:
 	// First, share idle weighted produces:
 	//
 	//
-	// namespace1:      39.6875
-	//   initial cost   25.0000
+	// namespace1:      42.6875
+	//   initial cost   28.0000
 	//   cluster1.cpu    2.5000 = 5.00*(3.00/6.00)
 	//   cluster1.cpu    2.5000 = 5.00*(3.00/6.00)
 	//   cluster1.ram   12.1875 = 15.00*(13.0/16.0)
 	//   cluster1.ram   12.1875 = 15.00*(13.0/16.0)
 	//
 	//
-	// namespace2:      40.3125
-	//   initial cost   30.0000
+	// namespace2:      46.3125
+	//   initial cost   36.0000
 	//   cluster1.cpu    2.5000 = 5.00*(3.0/6.0)
 	//   cluster1.cpu    2.5000 = 5.00*(3.0/6.0)
 	//   cluster1.ram    2.8125 = 15.00*(3.0/16.0)
 	//   cluster1.ram    2.8125 = 15.00*(3.0/16.0)
 	//   cluster2.cpu    2.5000 = 5.00*(3.0/6.0)
 	//   cluster2.cpu    2.5000 = 5.00*(3.0/6.0)
 	//   cluster2.ram    2.5000 = 5.00*(3.0/6.0)
 	//   cluster2.ram    2.5000 = 5.00*(3.0/6.0)
 	//
 	//
-	// namespace3:      20.0000
-	//   initial cost   15.0000
+	// namespace3:      23.0000
+	//   initial cost   18.0000
 	//   cluster2.cpu    2.5000 = 5.00*(3.0/6.0)
 	//   cluster2.cpu    2.5000 = 5.00*(3.0/6.0)
 	//   cluster2.ram    2.5000 = 5.00*(3.0/6.0)
 	//   cluster2.ram    2.5000 = 5.00*(3.0/6.0)
 	//
 	//
@@ -1299,15 +1309,15 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	// computed before allocating idle (so that weighting idle differently
 	// computed before allocating idle (so that weighting idle differently
 	// doesn't adversely affect the sharing mechanism):
 	// doesn't adversely affect the sharing mechanism):
 	//
 	//
-	// namespace2:      66.7708
+	// namespace2:      74.7708
 	//   initial cost   30.0000
 	//   initial cost   30.0000
 	//   idle cost      10.3125
 	//   idle cost      10.3125
-	//   shared cost    26.4583 = (39.6875)*(30.0/45.0)
+	//   shared cost    28.4583 = (42.6875)*(36.0/54.0)
 	//
 	//
-	// namespace3:      33.2292
-	//   initial cost   15.0000
+	// namespace3:      37.2292
+	//   initial cost   18.0000
 	//   idle cost       5.0000
 	//   idle cost       5.0000
-	//   shared cost    13.2292 = (39.6875)*(15.0/45.0)
+	//   shared cost    14.2292 = (42.6875)*(18.0/54.0)
 	//
 	//
 	as = generateAllocationSet(start)
 	as = generateAllocationSet(start)
 	err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
 	err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
@@ -1317,8 +1327,8 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	})
 	})
 	assertAllocationSetTotals(t, as, "6f", err, 2, activeTotalCost+idleTotalCost)
 	assertAllocationSetTotals(t, as, "6f", err, 2, activeTotalCost+idleTotalCost)
 	assertAllocationTotals(t, as, "6f", map[string]float64{
 	assertAllocationTotals(t, as, "6f", map[string]float64{
-		"namespace2": 66.77,
-		"namespace3": 33.23,
+		"namespace2": 74.77,
+		"namespace3": 37.23,
 	})
 	})
 	assertAllocationWindow(t, as, "6f", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "6f", startYesterday, endYesterday, 1440.0)
 
 
@@ -1326,20 +1336,20 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	//
 	//
 	// First, share idle weighted produces:
 	// First, share idle weighted produces:
 	//
 	//
-	// namespace1:      39.6875
-	//   initial cost   25.0000
+	// namespace1:      42.6875
+	//   initial cost   28.0000
 	//   cluster1.cpu    2.5000 = 5.00*(3.00/6.00)
 	//   cluster1.cpu    2.5000 = 5.00*(3.00/6.00)
 	//   cluster1.ram   12.1875 = 15.00*(13.0/16.0)
 	//   cluster1.ram   12.1875 = 15.00*(13.0/16.0)
 	//
 	//
-	// namespace2:      40.3125
-	//   initial cost   30.0000
+	// namespace2:      46.3125
+	//   initial cost   36.0000
 	//   cluster1.cpu    2.5000 = 5.00*(3.0/6.0)
 	//   cluster1.cpu    2.5000 = 5.00*(3.0/6.0)
 	//   cluster1.ram    2.8125 = 15.00*(3.0/16.0)
 	//   cluster1.ram    2.8125 = 15.00*(3.0/16.0)
 	//   cluster2.cpu    2.5000 = 5.00*(3.0/6.0)
 	//   cluster2.cpu    2.5000 = 5.00*(3.0/6.0)
 	//   cluster2.ram    2.5000 = 5.00*(3.0/6.0)
 	//   cluster2.ram    2.5000 = 5.00*(3.0/6.0)
 	//
 	//
-	// namespace3:      20.0000
-	//   initial cost   15.0000
+	// namespace3:      23.0000
+	//   initial cost   18.0000
 	//   cluster2.cpu    2.5000 = 5.00*(3.0/6.0)
 	//   cluster2.cpu    2.5000 = 5.00*(3.0/6.0)
 	//   cluster2.ram    2.5000 = 5.00*(3.0/6.0)
 	//   cluster2.ram    2.5000 = 5.00*(3.0/6.0)
 	//
 	//
@@ -1347,17 +1357,17 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	// computed before allocating idle (so that weighting idle differently
 	// computed before allocating idle (so that weighting idle differently
 	// doesn't adversely affect the sharing mechanism):
 	// doesn't adversely affect the sharing mechanism):
 	//
 	//
-	// namespace2:      66.7708
-	//   initial cost   30.0000
+	// namespace2:      74.7708
+	//   initial cost   36.0000
 	//   idle cost      10.3125
 	//   idle cost      10.3125
-	//   shared cost    26.4583 = (39.6875)*(30.0/45.0)
+	//   shared cost    28.4583 = (42.6875)*(36.0/54.0)
 	//
 	//
-	// namespace3:      33.2292
-	//   initial cost   15.0000
+	// namespace3:      37.2292
+	//   initial cost   18.0000
 	//   idle cost       5.0000
 	//   idle cost       5.0000
-	//   shared cost    13.2292 = (39.6875)*(15.0/45.0)
+	//   shared cost    14.2292 = (42.6875)*(18.0/54.0)
 	//
 	//
-	// Then, filter for namespace2: 66.7708
+	// Then, filter for namespace2: 74.7708
 	//
 	//
 	as = generateAllocationSet(start)
 	as = generateAllocationSet(start)
 	err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
 	err = as.AggregateBy(Properties{NamespaceProp: ""}, &AllocationAggregationOptions{
@@ -1366,9 +1376,9 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 		ShareSplit:  ShareWeighted,
 		ShareSplit:  ShareWeighted,
 		ShareIdle:   ShareWeighted,
 		ShareIdle:   ShareWeighted,
 	})
 	})
-	assertAllocationSetTotals(t, as, "6g", err, 1, 66.77)
+	assertAllocationSetTotals(t, as, "6g", err, 1, 74.77)
 	assertAllocationTotals(t, as, "6g", map[string]float64{
 	assertAllocationTotals(t, as, "6g", map[string]float64{
-		"namespace2": 66.77,
+		"namespace2": 74.77,
 	})
 	})
 	assertAllocationWindow(t, as, "6g", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "6g", startYesterday, endYesterday, 1440.0)
 
 
@@ -1376,28 +1386,28 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	//
 	//
 	// Share idle weighted:
 	// Share idle weighted:
 	//
 	//
-	// namespace1:      39.6875
-	//   initial cost   25.0000
+	// namespace1:      42.6875
+	//   initial cost   28.0000
 	//   cluster1.cpu    2.5000 = 5.00*(3.00/6.00)
 	//   cluster1.cpu    2.5000 = 5.00*(3.00/6.00)
 	//   cluster1.ram   12.1875 = 15.00*(13.0/16.0)
 	//   cluster1.ram   12.1875 = 15.00*(13.0/16.0)
 	//
 	//
-	// namespace2:      40.3125
-	//   initial cost   30.0000
+	// namespace2:      46.3125
+	//   initial cost   36.0000
 	//   cluster1.cpu    2.5000 = 5.00*(3.0/6.0)
 	//   cluster1.cpu    2.5000 = 5.00*(3.0/6.0)
 	//   cluster1.ram    2.8125 = 15.00*(3.0/16.0)
 	//   cluster1.ram    2.8125 = 15.00*(3.0/16.0)
 	//   cluster2.cpu    2.5000 = 5.00*(3.0/6.0)
 	//   cluster2.cpu    2.5000 = 5.00*(3.0/6.0)
 	//   cluster2.ram    2.5000 = 5.00*(3.0/6.0)
 	//   cluster2.ram    2.5000 = 5.00*(3.0/6.0)
 	//
 	//
-	// namespace3:      20.0000
-	//   initial cost   15.0000
+	// namespace3:      23.0000
+	//   initial cost   18.0000
 	//   cluster2.cpu    2.5000 = 5.00*(3.0/6.0)
 	//   cluster2.cpu    2.5000 = 5.00*(3.0/6.0)
 	//   cluster2.ram    2.5000 = 5.00*(3.0/6.0)
 	//   cluster2.ram    2.5000 = 5.00*(3.0/6.0)
 	//
 	//
 	// Then share overhead:
 	// Then share overhead:
 	//
 	//
-	// namespace1:      99.6875 = 39.6875 + (7.0*24.0)*(25.00/70.00)
-	// namespace2:     112.3125 = 40.3125 + (7.0*24.0)*(30.00/70.00)
-	// namespace3:      56.0000 = 20.0000 + (7.0*24.0)*(15.00/70.00)
+	// namespace1:     100.0533 = 42.6875 + (7.0*24.0)*(28.00/82.00)
+	// namespace2:     120.0686 = 46.3125 + (7.0*24.0)*(36.00/82.00)
+	// namespace3:      59.8780 = 23.0000 + (7.0*24.0)*(18.00/82.00)
 	//
 	//
 	// Then namespace 2 is filtered.
 	// Then namespace 2 is filtered.
 	as = generateAllocationSet(start)
 	as = generateAllocationSet(start)
@@ -1407,9 +1417,9 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 		ShareIdle:         ShareWeighted,
 		ShareIdle:         ShareWeighted,
 		SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
 		SharedHourlyCosts: map[string]float64{"total": sharedOverheadHourlyCost},
 	})
 	})
-	assertAllocationSetTotals(t, as, "6h", err, 1, 112.31)
+	assertAllocationSetTotals(t, as, "6h", err, 1, 120.07)
 	assertAllocationTotals(t, as, "6h", map[string]float64{
 	assertAllocationTotals(t, as, "6h", map[string]float64{
-		"namespace2": 112.31,
+		"namespace2": 120.07,
 	})
 	})
 	assertAllocationWindow(t, as, "6h", startYesterday, endYesterday, 1440.0)
 	assertAllocationWindow(t, as, "6h", startYesterday, endYesterday, 1440.0)
 
 
@@ -1716,8 +1726,8 @@ func TestAllocationSetRange_Accumulate(t *testing.T) {
 	if result == nil {
 	if result == nil {
 		t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
 		t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
 	}
 	}
-	if result.TotalCost() != 5.0 {
-		t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
+	if result.TotalCost() != 6.0 {
+		t.Fatalf("accumulating AllocationSetRange: expected total cost 6.0; actual %f", result.TotalCost())
 	}
 	}
 
 
 	result, err = NewAllocationSetRange(todayAS, nil).Accumulate()
 	result, err = NewAllocationSetRange(todayAS, nil).Accumulate()
@@ -1727,8 +1737,8 @@ func TestAllocationSetRange_Accumulate(t *testing.T) {
 	if result == nil {
 	if result == nil {
 		t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
 		t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
 	}
 	}
-	if result.TotalCost() != 5.0 {
-		t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
+	if result.TotalCost() != 6.0 {
+		t.Fatalf("accumulating AllocationSetRange: expected total cost 6.0; actual %f", result.TotalCost())
 	}
 	}
 
 
 	result, err = NewAllocationSetRange(nil, todayAS, nil).Accumulate()
 	result, err = NewAllocationSetRange(nil, todayAS, nil).Accumulate()
@@ -1738,8 +1748,8 @@ func TestAllocationSetRange_Accumulate(t *testing.T) {
 	if result == nil {
 	if result == nil {
 		t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
 		t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
 	}
 	}
-	if result.TotalCost() != 5.0 {
-		t.Fatalf("accumulating AllocationSetRange: expected total cost 5.0; actual %f", result.TotalCost())
+	if result.TotalCost() != 6.0 {
+		t.Fatalf("accumulating AllocationSetRange: expected total cost 6.0; actual %f", result.TotalCost())
 	}
 	}
 
 
 	// Accumulate two non-nil should result in sum of both with appropriate start, end
 	// Accumulate two non-nil should result in sum of both with appropriate start, end
@@ -1750,8 +1760,8 @@ func TestAllocationSetRange_Accumulate(t *testing.T) {
 	if result == nil {
 	if result == nil {
 		t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
 		t.Fatalf("accumulating AllocationSetRange: expected AllocationSet; actual %s", result)
 	}
 	}
-	if result.TotalCost() != 10.0 {
-		t.Fatalf("accumulating AllocationSetRange: expected total cost 10.0; actual %f", result.TotalCost())
+	if result.TotalCost() != 12.0 {
+		t.Fatalf("accumulating AllocationSetRange: expected total cost 12.0; actual %f", result.TotalCost())
 	}
 	}
 	allocMap := result.Map()
 	allocMap := result.Map()
 	if len(allocMap) != 1 {
 	if len(allocMap) != 1 {
@@ -1779,6 +1789,9 @@ func TestAllocationSetRange_Accumulate(t *testing.T) {
 	if alloc.NetworkCost != 2.0 {
 	if alloc.NetworkCost != 2.0 {
 		t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.NetworkCost)
 		t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.NetworkCost)
 	}
 	}
+	if alloc.LoadBalancerCost != 2.0 {
+		t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.LoadBalancerCost)
+	}
 	if alloc.PVByteHours != 2.0 {
 	if alloc.PVByteHours != 2.0 {
 		t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.PVByteHours)
 		t.Fatalf("accumulating AllocationSetRange: expected 2.0; actual %f", alloc.PVByteHours)
 	}
 	}
@@ -1794,8 +1807,8 @@ func TestAllocationSetRange_Accumulate(t *testing.T) {
 	if alloc.RAMEfficiency() != 1.0 {
 	if alloc.RAMEfficiency() != 1.0 {
 		t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.RAMEfficiency())
 		t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.RAMEfficiency())
 	}
 	}
-	if alloc.TotalCost() != 10.0 {
-		t.Fatalf("accumulating AllocationSetRange: expected 10.0; actual %f", alloc.TotalCost())
+	if alloc.TotalCost() != 12.0 {
+		t.Fatalf("accumulating AllocationSetRange: expected 12.0; actual %f", alloc.TotalCost())
 	}
 	}
 	if alloc.TotalEfficiency() != 1.0 {
 	if alloc.TotalEfficiency() != 1.0 {
 		t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.TotalEfficiency())
 		t.Fatalf("accumulating AllocationSetRange: expected 1.0; actual %f", alloc.TotalEfficiency())
@@ -1896,6 +1909,9 @@ func TestAllocationSetRange_InsertRange(t *testing.T) {
 			if !util.IsApproximately(a.NetworkCost, unit.NetworkCost) {
 			if !util.IsApproximately(a.NetworkCost, unit.NetworkCost) {
 				t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
 				t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
 			}
 			}
+			if !util.IsApproximately(a.LoadBalancerCost, unit.LoadBalancerCost) {
+				t.Fatalf("allocation %s: expected %f; got %f", k, unit.LoadBalancerCost, a.LoadBalancerCost)
+			}
 			if !util.IsApproximately(a.TotalCost(), unit.TotalCost()) {
 			if !util.IsApproximately(a.TotalCost(), unit.TotalCost()) {
 				t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
 				t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
 			}
 			}
@@ -1944,6 +1960,10 @@ func TestAllocationSetRange_InsertRange(t *testing.T) {
 		if !util.IsApproximately(a.NetworkCost, 2*unit.NetworkCost) {
 		if !util.IsApproximately(a.NetworkCost, 2*unit.NetworkCost) {
 			t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
 			t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
 		}
 		}
+		if !util.IsApproximately(a.LoadBalancerCost, 2*unit.LoadBalancerCost) {
+			t.Fatalf("allocation %s: expected %f; got %f", k, unit.LoadBalancerCost, a.LoadBalancerCost)
+		}
+
 		if !util.IsApproximately(a.TotalCost(), 2*unit.TotalCost()) {
 		if !util.IsApproximately(a.TotalCost(), 2*unit.TotalCost()) {
 			t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
 			t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
 		}
 		}
@@ -1977,6 +1997,9 @@ func TestAllocationSetRange_InsertRange(t *testing.T) {
 		if !util.IsApproximately(a.NetworkCost, unit.NetworkCost) {
 		if !util.IsApproximately(a.NetworkCost, unit.NetworkCost) {
 			t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
 			t.Fatalf("allocation %s: expected %f; got %f", k, unit.NetworkCost, a.NetworkCost)
 		}
 		}
+		if !util.IsApproximately(a.LoadBalancerCost, unit.LoadBalancerCost) {
+			t.Fatalf("allocation %s: expected %f; got %f", k, unit.LoadBalancerCost, a.LoadBalancerCost)
+		}
 		if !util.IsApproximately(a.TotalCost(), unit.TotalCost()) {
 		if !util.IsApproximately(a.TotalCost(), unit.TotalCost()) {
 			t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
 			t.Fatalf("allocation %s: expected %f; got %f", k, unit.TotalCost(), a.TotalCost())
 		}
 		}

+ 1 - 1
pkg/kubecost/bingen.go

@@ -21,4 +21,4 @@ package kubecost
 // @bingen:generate:AllocationSet
 // @bingen:generate:AllocationSet
 // @bingen:generate:AllocationSetRange
 // @bingen:generate:AllocationSetRange
 
 
-//go:generate bingen -package=kubecost -version=8 -buffer=github.com/kubecost/cost-model/pkg/util
+//go:generate bingen -package=kubecost -version=9 -buffer=github.com/kubecost/cost-model/pkg/util

+ 14 - 11
pkg/kubecost/kubecost_codecs.go

@@ -14,11 +14,10 @@ package kubecost
 import (
 import (
 	"encoding"
 	"encoding"
 	"fmt"
 	"fmt"
+	util "github.com/kubecost/cost-model/pkg/util"
 	"reflect"
 	"reflect"
 	"strings"
 	"strings"
 	"time"
 	"time"
-
-	util "github.com/kubecost/cost-model/pkg/util"
 )
 )
 
 
 const (
 const (
@@ -26,7 +25,7 @@ const (
 	GeneratorPackageName string = "kubecost"
 	GeneratorPackageName string = "kubecost"
 
 
 	// CodecVersion is the version passed into the generator
 	// CodecVersion is the version passed into the generator
-	CodecVersion uint8 = 8
+	CodecVersion uint8 = 9
 )
 )
 
 
 //--------------------------------------------------------------------------
 //--------------------------------------------------------------------------
@@ -160,6 +159,7 @@ func (target *Allocation) MarshalBinary() (data []byte, err error) {
 	buff.WriteFloat64(target.GPUHours)               // write float64
 	buff.WriteFloat64(target.GPUHours)               // write float64
 	buff.WriteFloat64(target.GPUCost)                // write float64
 	buff.WriteFloat64(target.GPUCost)                // write float64
 	buff.WriteFloat64(target.NetworkCost)            // write float64
 	buff.WriteFloat64(target.NetworkCost)            // write float64
+	buff.WriteFloat64(target.LoadBalancerCost)       // write float64
 	buff.WriteFloat64(target.PVByteHours)            // write float64
 	buff.WriteFloat64(target.PVByteHours)            // write float64
 	buff.WriteFloat64(target.PVCost)                 // write float64
 	buff.WriteFloat64(target.PVCost)                 // write float64
 	buff.WriteFloat64(target.RAMByteHours)           // write float64
 	buff.WriteFloat64(target.RAMByteHours)           // write float64
@@ -264,28 +264,31 @@ func (target *Allocation) UnmarshalBinary(data []byte) (err error) {
 	target.NetworkCost = w
 	target.NetworkCost = w
 
 
 	x := buff.ReadFloat64() // read float64
 	x := buff.ReadFloat64() // read float64
-	target.PVByteHours = x
+	target.LoadBalancerCost = x
 
 
 	y := buff.ReadFloat64() // read float64
 	y := buff.ReadFloat64() // read float64
-	target.PVCost = y
+	target.PVByteHours = y
 
 
 	aa := buff.ReadFloat64() // read float64
 	aa := buff.ReadFloat64() // read float64
-	target.RAMByteHours = aa
+	target.PVCost = aa
 
 
 	bb := buff.ReadFloat64() // read float64
 	bb := buff.ReadFloat64() // read float64
-	target.RAMBytesRequestAverage = bb
+	target.RAMByteHours = bb
 
 
 	cc := buff.ReadFloat64() // read float64
 	cc := buff.ReadFloat64() // read float64
-	target.RAMBytesUsageAverage = cc
+	target.RAMBytesRequestAverage = cc
 
 
 	dd := buff.ReadFloat64() // read float64
 	dd := buff.ReadFloat64() // read float64
-	target.RAMCost = dd
+	target.RAMBytesUsageAverage = dd
 
 
 	ee := buff.ReadFloat64() // read float64
 	ee := buff.ReadFloat64() // read float64
-	target.SharedCost = ee
+	target.RAMCost = ee
 
 
 	ff := buff.ReadFloat64() // read float64
 	ff := buff.ReadFloat64() // read float64
-	target.ExternalCost = ff
+	target.SharedCost = ff
+
+	gg := buff.ReadFloat64() // read float64
+	target.ExternalCost = gg
 
 
 	return nil
 	return nil
 }
 }