Sean Holcomb 5 лет назад
Родитель
Сommit
0c072797f0
3 измененных файлов с 150 добавлено и 0 удалено
  1. 86 0
      pkg/costmodel/allocation.go
  2. 63 0
      pkg/costmodel/allocation_test.go
  3. 1 0
      pkg/kubecost/allocation_test.go

+ 86 - 0
pkg/costmodel/allocation.go

@@ -49,6 +49,8 @@ const (
 	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)`
 	queryFmtJobLabels             = `sum(avg_over_time(kube_pod_owner{owner_kind="Job"}[%s]%s)) by (pod, owner_name, namespace ,cluster_id)`
+	queryFmtLBCostHr              = `avg_over_time((avg(kubecost_load_balancer_cost) by (namespace, service_name, cluster_id))[%s:%s]%s)`
+	queryFmtLBActiveMins            = `count(kubecost_load_balancer_cost) by (namespace, service_name, cluster_id)[%s:%s]%s`
 )
 
 // 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)
 	resChJobLabels := ctx.Query(queryJobLabels)
 
+	queryLBCostHr := fmt.Sprintf(queryFmtLBCostHr, durStr, resStr, offStr)
+	resChLBCost := ctx.Query(queryLBCostHr)
+
+	queryLBActiveMins := fmt.Sprintf(queryFmtLBActiveMins, durStr, resStr, offStr)
+	resChLBActiveMins := ctx.Query(queryLBActiveMins)
+
 	resCPUCoresAllocated, _ := resChCPUCoresAllocated.Await()
 	resCPURequests, _ := resChCPURequests.Await()
 	resCPUUsage, _ := resChCPUUsage.Await()
@@ -228,6 +236,8 @@ func (cm *CostModel) ComputeAllocation(start, end time.Time, resolution time.Dur
 	resStatefulSetLabels, _ := resChStatefulSetLabels.Await()
 	resDaemonSetLabels, _ := resChDaemonSetLabels.Await()
 	resJobLabels, _ := resChJobLabels.Await()
+	resLBCost, _ := resChLBCost.Await()
+	resLBActiveMins, _ := resChLBActiveMins.Await()
 
 	if ctx.HasErrors() {
 		for _, err := range ctx.Errors() {
@@ -272,6 +282,7 @@ func (cm *CostModel) ComputeAllocation(start, end time.Time, resolution time.Dur
 
 	// TODO breakdown network costs?
 
+
 	// Build out a map of Nodes with resource costs, discounts, and node types
 	// for converting resource allocation data to cumulative costs.
 	nodeMap := map[nodeKey]*NodePricing{}
@@ -308,6 +319,12 @@ func (cm *CostModel) ComputeAllocation(start, end time.Time, resolution time.Dur
 	// cluster representing each cluster's unmounted PVs (if necessary).
 	applyUnmountedPVs(window, podMap, pvMap, pvcMap)
 
+	log.Infof("-------------lb code----------")
+	log.Infof("lb cost query %v, %s", len(resLBCost), queryLBCostHr)
+	log.Infof("lb active mins query %v, %s", len(resLBActiveMins), queryLBActiveMins)
+	lbCosts := getLoadBalancerCosts(resLBCost, resLBActiveMins)
+	applyLoadBalancersToPods(window, podMap, lbCosts)
+
 	// (3) Build out AllocationSet from Pod map
 
 	for _, pod := range podMap {
@@ -1586,6 +1603,75 @@ func applyUnmountedPVCs(window kubecost.Window, podMap map[podKey]*Pod, pvcMap m
 	}
 }
 
+func getLoadBalancerCosts(resLBCost, resLBActiveMins []*prom.QueryResult) map[serviceKey]float64 {
+	log.Infof("--LB Costs")
+	lbCostMap := make(map[serviceKey]float64)
+	for _, res := range resLBCost {
+		serviceKey, err := resultServiceKey(res, "cluster_id", "namespace", "service_name")
+		if err != nil {
+			log.Infof("--LB Costs err %v", err)
+			continue
+		}
+		lbCostMap[serviceKey] = res.Values[0].Value
+		log.Infof("--LB Costs %v, %v", serviceKey.String(), res.Values[0].Value)
+	}
+	for _, res := range resLBActiveMins {
+		serviceKey, err := resultServiceKey(res, "cluster_id", "namespace", "service_name")
+		if err != nil {
+			log.Infof("--LB Hours err %v", err)
+			continue
+		}
+		if len(res.Values) == 0 {
+			continue
+		}
+
+		s := time.Unix(int64(res.Values[0].Timestamp), 0)
+		e := time.Unix(int64(res.Values[len(res.Values)-1].Timestamp), 0)
+		hours := e.Sub(s).Hours()
+		lbCostMap[serviceKey] *= hours
+		log.Infof("--LB Hours %v, %v", serviceKey.String(), hours)
+	}
+	return lbCostMap
+}
+
+func applyLoadBalancersToPods(window kubecost.Window, podMap map[podKey]*Pod, lbCostMap map[serviceKey]float64) {
+	for sKey, cost := range lbCostMap {
+		log.Infof("LB COST %v, %v", sKey.String(), cost)
+		lbAllocs := []*kubecost.Allocation{}
+		// Search through pods with matching cluster and namespace to the lb
+		for pKey, pod := range podMap {
+			if sKey.Cluster != pKey.Cluster || sKey.Namespace != pKey.Namespace {
+				continue
+			}
+			// Create list of Allocations that use Load Balancer service
+			for _, alloc := range pod.Allocations {
+				services, _ := alloc.Properties.GetServices()
+				for _, srv := range services {
+					if srv == sKey.Service {
+						lbAllocs = append(lbAllocs, alloc)
+					}
+				}
+			}
+		}
+		log.Infof("LB Allocs %v", lbAllocs)
+		// Add portion of load balancing cost to each allocation
+		// proportional to the total number of allocations that use the load balancer
+		for _, alloc := range lbAllocs {
+			alloc.NetworkCost += cost / float64(len(lbAllocs))
+			log.Infof("LB Alloc network cost %v", alloc.NetworkCost)
+		}
+
+	}
+}
+
+func WindowOverLap(start1, end1, start2, end2 time.Time) time.Duration {
+	//if start1.Before(start2) {
+	//	if {end1.After()}
+	//} else {
+	//
+	//}
+}
+
 // getNodePricing determines node pricing, given a key and a mapping from keys
 // to their NodePricing instances, as well as the custom pricing configuration
 // inherent to the CostModel instance. If custom pricing is set, use that. If

+ 63 - 0
pkg/costmodel/allocation_test.go

@@ -0,0 +1,63 @@
+package costmodel
+
+import (
+	"fmt"
+	"testing"
+	"time"
+)
+
+func TestWindowOverlap(t *testing.T) {
+	loc, _ := time.LoadLocation("UTC")
+	cases := map[string]struct {
+		start1 time.Time
+		end1 time.Time
+		start2 time.Time
+		end2 time.Time
+		expected time.Duration
+	}{
+		"Same timeframe": {
+			start1: time.Date(2020, 3, 15, 1, 0, 0, 0, loc),
+			end1: time.Date(2020, 3, 15, 2, 0, 0, 0, loc),
+			start2: time.Date(2020, 3, 15, 1, 0, 0, 0, loc),
+			end2: time.Date(2020, 3, 15, 2, 0, 0, 0, loc),
+			expected: time.Hour,
+		},
+		"sub-timeframe": {
+			start1: time.Date(2020, 3, 15, 1, 0, 0, 0, loc),
+			end1: time.Date(2020, 3, 15, 2, 0, 0, 0, loc),
+			start2: time.Date(2020, 3, 15, 1, 15, 0, 0, loc),
+			end2: time.Date(2020, 3, 15, 1, 30, 0, 0, loc),
+			expected: time.Hour / 2,
+		},
+		"sub-timeframe 2": {
+			start1: time.Date(2020, 3, 15, 1, 0, 0, 0, loc),
+			end1: time.Date(2020, 3, 15, 2, 0, 0, 0, loc),
+			start2: time.Date(2020, 3, 15, 0, 0, 0, 0, loc),
+			end2: time.Date(2020, 3, 15, 3, 0, 0, 0, loc),
+			expected: time.Hour,
+		},
+		"1 before 2": {
+			start1: time.Date(2020, 3, 15, 1, 0, 0, 0, loc),
+			end1: time.Date(2020, 3, 15, 2, 0, 0, 0, loc),
+			start2: time.Date(2020, 3, 15, 3, 0, 0, 0, loc),
+			end2: time.Date(2020, 3, 15, 4, 0, 0, 0, loc),
+			expected: 0,
+		},
+		"2 before 1": {
+			start1: time.Date(2020, 3, 15, 5, 0, 0, 0, loc),
+			end1: time.Date(2020, 3, 15, 6, 0, 0, 0, loc),
+			start2: time.Date(2020, 3, 15, 3, 0, 0, 0, loc),
+			end2: time.Date(2020, 3, 15, 4, 0, 0, 0, loc),
+			expected: 0,
+		},
+		"partial overlap ": {
+			start1: time.Date(2020, 3, 15, 5, 0, 0, 0, loc),
+			end1: time.Date(2020, 3, 15, 6, 0, 0, 0, loc),
+			start2: time.Date(2020, 3, 15, 3, 0, 0, 0, loc),
+			end2: time.Date(2020, 3, 15, 4, 0, 0, 0, loc),
+			expected: 0,
+		},
+
+	}
+	fmt.Println(cases)
+}

+ 1 - 0
pkg/kubecost/allocation_test.go

@@ -1935,6 +1935,7 @@ func TestAllocationSetRange_InsertRange(t *testing.T) {
 	})
 }
 
+
 // TODO niko/etl
 // func TestAllocationSetRange_Length(t *testing.T) {}