Просмотр исходного кода

Aggregation API: scale cost vectors by resolution coefficient, i.e. convert hourly cost to resolution-ly cost.

Niko Kovacevic 6 лет назад
Родитель
Сommit
d3628380b7
2 измененных файлов с 51 добавлено и 36 удалено
  1. 42 28
      costmodel/aggregations.go
  2. 9 8
      costmodel/router.go

+ 42 - 28
costmodel/aggregations.go

@@ -103,8 +103,7 @@ func NewSharedResourceInfo(shareResources bool, sharedNamespaces []string, label
 	return sr
 }
 
-func ComputeIdleCoefficient(costData map[string]*CostData, cli prometheusClient.Client, cp cloud.Provider, discount float64, windowString, offset string) (map[string]float64, error) {
-
+func ComputeIdleCoefficient(costData map[string]*CostData, cli prometheusClient.Client, cp cloud.Provider, discount float64, windowString, offset, resolution string) (map[string]float64, error) {
 	coefficients := make(map[string]float64)
 
 	windowDuration, err := time.ParseDuration(windowString)
@@ -112,6 +111,9 @@ func ComputeIdleCoefficient(costData map[string]*CostData, cli prometheusClient.
 		return nil, err
 	}
 
+	resolutionDuration, err := parseDuration(resolution)
+	resolutionCoefficient := resolutionDuration.Hours()
+
 	allTotals, err := ClusterCostsForAllClusters(cli, cp, windowString, offset)
 	if err != nil {
 		return nil, err
@@ -143,7 +145,7 @@ func ComputeIdleCoefficient(costData map[string]*CostData, cli prometheusClient.
 		totalContainerCost := 0.0
 		for _, costDatum := range costData {
 			if costDatum.ClusterID == cid {
-				cpuv, ramv, gpuv, pvvs, _ := getPriceVectors(cp, costDatum, "", discount, 1)
+				cpuv, ramv, gpuv, pvvs, _ := getPriceVectors(cp, costDatum, "", discount, 1, resolutionCoefficient)
 				totalContainerCost += totalVectors(cpuv)
 				totalContainerCost += totalVectors(ramv)
 				totalContainerCost += totalVectors(gpuv)
@@ -162,13 +164,14 @@ func ComputeIdleCoefficient(costData map[string]*CostData, cli prometheusClient.
 
 // AggregationOptions provides optional parameters to AggregateCostData, allowing callers to perform more complex operations
 type AggregationOptions struct {
-	DataCount          int                // number of cost data points expected; ensures proper rate calculation if data is incomplete
-	Discount           float64            // percent by which to discount CPU, RAM, and GPU cost
-	IdleCoefficients   map[string]float64 // scales costs by amount of idle resources on a per-cluster basis
-	IncludeEfficiency  bool               // set to true to receive efficiency/usage data
-	IncludeTimeSeries  bool               // set to true to receive time series data
-	Rate               string             // set to "hourly", "daily", or "monthly" to receive cost rate, rather than cumulative cost
-	SharedResourceInfo *SharedResourceInfo
+	DataCount             int                // number of cost data points expected; ensures proper rate calculation if data is incomplete
+	Discount              float64            // percent by which to discount CPU, RAM, and GPU cost
+	IdleCoefficients      map[string]float64 // scales costs by amount of idle resources on a per-cluster basis
+	IncludeEfficiency     bool               // set to true to receive efficiency/usage data
+	IncludeTimeSeries     bool               // set to true to receive time series data
+	Rate                  string             // set to "hourly", "daily", or "monthly" to receive cost rate, rather than cumulative cost
+	ResolutionCoefficient float64            // coefficient for converting hourly costs to per-resolution cost; e.g. 6 for a 6h resolution
+	SharedResourceInfo    *SharedResourceInfo
 }
 
 // AggregateCostData aggregates raw cost data by field; e.g. namespace, cluster, service, or label. In the case of label, callers
@@ -189,6 +192,11 @@ func AggregateCostData(costData map[string]*CostData, field string, subfields []
 		idleCoefficients = make(map[string]float64)
 	}
 
+	resolutionCoefficient := opts.ResolutionCoefficient
+	if resolutionCoefficient == 0.0 {
+		resolutionCoefficient = 1.0
+	}
+
 	// aggregations collects key-value pairs of resource group-to-aggregated data
 	// e.g. namespace-to-data or label-value-to-data
 	aggregations := make(map[string]*Aggregation)
@@ -203,7 +211,7 @@ func AggregateCostData(costData map[string]*CostData, field string, subfields []
 			idleCoefficient = 1.0
 		}
 		if sr != nil && sr.ShareResources && sr.IsSharedResource(costDatum) {
-			cpuv, ramv, gpuv, pvvs, netv := getPriceVectors(cp, costDatum, rate, discount, idleCoefficient)
+			cpuv, ramv, gpuv, pvvs, netv := getPriceVectors(cp, costDatum, rate, discount, idleCoefficient, resolutionCoefficient)
 			sharedResourceCost += totalVectors(cpuv)
 			sharedResourceCost += totalVectors(ramv)
 			sharedResourceCost += totalVectors(gpuv)
@@ -213,32 +221,32 @@ func AggregateCostData(costData map[string]*CostData, field string, subfields []
 			}
 		} else {
 			if field == "cluster" {
-				aggregateDatum(cp, aggregations, costDatum, field, subfields, rate, costDatum.ClusterID, discount, idleCoefficient)
+				aggregateDatum(cp, aggregations, costDatum, field, subfields, rate, costDatum.ClusterID, discount, idleCoefficient, resolutionCoefficient)
 			} else if field == "namespace" {
-				aggregateDatum(cp, aggregations, costDatum, field, subfields, rate, costDatum.Namespace, discount, idleCoefficient)
+				aggregateDatum(cp, aggregations, costDatum, field, subfields, rate, costDatum.Namespace, discount, idleCoefficient, resolutionCoefficient)
 			} else if field == "service" {
 				if len(costDatum.Services) > 0 {
-					aggregateDatum(cp, aggregations, costDatum, field, subfields, rate, costDatum.Namespace+"/"+costDatum.Services[0], discount, idleCoefficient)
+					aggregateDatum(cp, aggregations, costDatum, field, subfields, rate, costDatum.Namespace+"/"+costDatum.Services[0], discount, idleCoefficient, resolutionCoefficient)
 				}
 			} else if field == "deployment" {
 				if len(costDatum.Deployments) > 0 {
-					aggregateDatum(cp, aggregations, costDatum, field, subfields, rate, costDatum.Namespace+"/"+costDatum.Deployments[0], discount, idleCoefficient)
+					aggregateDatum(cp, aggregations, costDatum, field, subfields, rate, costDatum.Namespace+"/"+costDatum.Deployments[0], discount, idleCoefficient, resolutionCoefficient)
 				}
 			} else if field == "daemonset" {
 				if len(costDatum.Daemonsets) > 0 {
-					aggregateDatum(cp, aggregations, costDatum, field, subfields, rate, costDatum.Namespace+"/"+costDatum.Daemonsets[0], discount, idleCoefficient)
+					aggregateDatum(cp, aggregations, costDatum, field, subfields, rate, costDatum.Namespace+"/"+costDatum.Daemonsets[0], discount, idleCoefficient, resolutionCoefficient)
 				}
 			} else if field == "label" {
 				if costDatum.Labels != nil {
 					for _, sf := range subfields {
 						if subfieldName, ok := costDatum.Labels[sf]; ok {
-							aggregateDatum(cp, aggregations, costDatum, field, subfields, rate, subfieldName, discount, idleCoefficient)
+							aggregateDatum(cp, aggregations, costDatum, field, subfields, rate, subfieldName, discount, idleCoefficient, resolutionCoefficient)
 							break
 						}
 					}
 				}
 			} else if field == "pod" {
-				aggregateDatum(cp, aggregations, costDatum, field, subfields, rate, costDatum.Namespace+"/"+costDatum.PodName, discount, idleCoefficient)
+				aggregateDatum(cp, aggregations, costDatum, field, subfields, rate, costDatum.Namespace+"/"+costDatum.PodName, discount, idleCoefficient, resolutionCoefficient)
 			}
 		}
 	}
@@ -328,7 +336,7 @@ func AggregateCostData(costData map[string]*CostData, field string, subfields []
 	return aggregations
 }
 
-func aggregateDatum(cp cloud.Provider, aggregations map[string]*Aggregation, costDatum *CostData, field string, subfields []string, rate string, key string, discount float64, idleCoefficient float64) {
+func aggregateDatum(cp cloud.Provider, aggregations map[string]*Aggregation, costDatum *CostData, field string, subfields []string, rate string, key string, discount float64, idleCoefficient float64, resolutionCoefficient float64) {
 	// add new entry to aggregation results if a new key is encountered
 	if _, ok := aggregations[key]; !ok {
 		agg := &Aggregation{}
@@ -340,10 +348,10 @@ func aggregateDatum(cp cloud.Provider, aggregations map[string]*Aggregation, cos
 		aggregations[key] = agg
 	}
 
-	mergeVectors(cp, costDatum, aggregations[key], rate, discount, idleCoefficient)
+	mergeVectors(cp, costDatum, aggregations[key], rate, discount, idleCoefficient, resolutionCoefficient)
 }
 
-func mergeVectors(cp cloud.Provider, costDatum *CostData, aggregation *Aggregation, rate string, discount float64, idleCoefficient float64) {
+func mergeVectors(cp cloud.Provider, costDatum *CostData, aggregation *Aggregation, rate string, discount float64, idleCoefficient float64, resolutionCoefficient float64) {
 	aggregation.CPUAllocationVectors = addVectors(costDatum.CPUAllocation, aggregation.CPUAllocationVectors)
 	aggregation.CPURequestedVectors = addVectors(costDatum.CPUReq, aggregation.CPURequestedVectors)
 	aggregation.CPUUsedVectors = addVectors(costDatum.CPUUsed, aggregation.CPUUsedVectors)
@@ -354,7 +362,7 @@ func mergeVectors(cp cloud.Provider, costDatum *CostData, aggregation *Aggregati
 
 	aggregation.GPUAllocation = addVectors(costDatum.GPUReq, aggregation.GPUAllocation)
 
-	cpuv, ramv, gpuv, pvvs, netv := getPriceVectors(cp, costDatum, rate, discount, idleCoefficient)
+	cpuv, ramv, gpuv, pvvs, netv := getPriceVectors(cp, costDatum, rate, discount, idleCoefficient, resolutionCoefficient)
 	aggregation.CPUCostVector = addVectors(cpuv, aggregation.CPUCostVector)
 	aggregation.RAMCostVector = addVectors(ramv, aggregation.RAMCostVector)
 	aggregation.GPUCostVector = addVectors(gpuv, aggregation.GPUCostVector)
@@ -364,7 +372,7 @@ func mergeVectors(cp cloud.Provider, costDatum *CostData, aggregation *Aggregati
 	}
 }
 
-func getPriceVectors(cp cloud.Provider, costDatum *CostData, rate string, discount float64, idleCoefficient float64) ([]*Vector, []*Vector, []*Vector, [][]*Vector, []*Vector) {
+func getPriceVectors(cp cloud.Provider, costDatum *CostData, rate string, discount float64, idleCoefficient float64, resolutionCoefficient float64) ([]*Vector, []*Vector, []*Vector, [][]*Vector, []*Vector) {
 	cpuCostStr := costDatum.NodeData.VCPUCost
 	ramCostStr := costDatum.NodeData.RAMCost
 	gpuCostStr := costDatum.NodeData.GPUCost
@@ -411,7 +419,7 @@ func getPriceVectors(cp cloud.Provider, costDatum *CostData, rate string, discou
 	for _, val := range costDatum.CPUAllocation {
 		cpuv = append(cpuv, &Vector{
 			Timestamp: math.Round(val.Timestamp/10) * 10,
-			Value:     (val.Value * cpuCost * (1 - discount) / idleCoefficient) * rateCoeff,
+			Value:     (val.Value * cpuCost * (1 - discount) / idleCoefficient) * rateCoeff * resolutionCoefficient,
 		})
 	}
 
@@ -419,7 +427,7 @@ func getPriceVectors(cp cloud.Provider, costDatum *CostData, rate string, discou
 	for _, val := range costDatum.RAMAllocation {
 		ramv = append(ramv, &Vector{
 			Timestamp: math.Round(val.Timestamp/10) * 10,
-			Value:     ((val.Value / 1024 / 1024 / 1024) * ramCost * (1 - discount) / idleCoefficient) * rateCoeff,
+			Value:     ((val.Value / 1024 / 1024 / 1024) * ramCost * (1 - discount) / idleCoefficient) * rateCoeff * resolutionCoefficient,
 		})
 	}
 
@@ -427,7 +435,7 @@ func getPriceVectors(cp cloud.Provider, costDatum *CostData, rate string, discou
 	for _, val := range costDatum.GPUReq {
 		gpuv = append(gpuv, &Vector{
 			Timestamp: math.Round(val.Timestamp/10) * 10,
-			Value:     (val.Value * gpuCost * (1 - discount) / idleCoefficient) * rateCoeff,
+			Value:     (val.Value * gpuCost * (1 - discount) / idleCoefficient) * rateCoeff * resolutionCoefficient,
 		})
 	}
 
@@ -445,14 +453,20 @@ func getPriceVectors(cp cloud.Provider, costDatum *CostData, rate string, discou
 			for _, val := range pvcData.Values {
 				pvv = append(pvv, &Vector{
 					Timestamp: math.Round(val.Timestamp/10) * 10,
-					Value:     ((val.Value / 1024 / 1024 / 1024) * cost / idleCoefficient) * rateCoeff,
+					Value:     ((val.Value / 1024 / 1024 / 1024) * cost / idleCoefficient) * rateCoeff * resolutionCoefficient,
 				})
 			}
 			pvvs = append(pvvs, pvv)
 		}
 	}
 
-	netv := costDatum.NetworkData
+	netv := make([]*Vector, 0, len(costDatum.NetworkData))
+	for _, val := range costDatum.NetworkData {
+		netv = append(netv, &Vector{
+			Timestamp: math.Round(val.Timestamp/10) * 10,
+			Value:     val.Value * rateCoeff * resolutionCoefficient,
+		})
+	}
 
 	return cpuv, ramv, gpuv, pvvs, netv
 }

+ 9 - 8
costmodel/router.go

@@ -507,7 +507,7 @@ func (a *Accesses) AggregateCostModel(w http.ResponseWriter, r *http.Request, ps
 			klog.Infof("Setting offset to 3h")
 			offset = "3h"
 		}
-		idleCoefficients, err = ComputeIdleCoefficient(costData, pClient, a.Cloud, discount, windowStr, offset)
+		idleCoefficients, err = ComputeIdleCoefficient(costData, pClient, a.Cloud, discount, windowStr, offset, resolution)
 		if err != nil {
 			klog.V(1).Infof("error computing idle coefficient: windowString=%s, offset=%s, err=%s", windowStr, offset, err)
 			w.Write(wrapData(nil, err))
@@ -543,13 +543,14 @@ func (a *Accesses) AggregateCostModel(w http.ResponseWriter, r *http.Request, ps
 
 	// aggregate cost model data by given fields and cache the result for the default expiration
 	opts := &AggregationOptions{
-		DataCount:          dataCount,
-		Discount:           discount,
-		IdleCoefficients:   idleCoefficients,
-		IncludeEfficiency:  includeEfficiency,
-		IncludeTimeSeries:  includeTimeSeries,
-		Rate:               rate,
-		SharedResourceInfo: sr,
+		DataCount:             dataCount,
+		Discount:              discount,
+		IdleCoefficients:      idleCoefficients,
+		IncludeEfficiency:     includeEfficiency,
+		IncludeTimeSeries:     includeTimeSeries,
+		Rate:                  rate,
+		ResolutionCoefficient: resolutionHours,
+		SharedResourceInfo:    sr,
 	}
 	result := AggregateCostData(costData, field, subfields, a.Cloud, opts)
 	a.AggregateCache.Set(aggKey, result, cache.DefaultExpiration)