|
@@ -4,6 +4,10 @@ import (
|
|
|
"math"
|
|
"math"
|
|
|
"sort"
|
|
"sort"
|
|
|
"strconv"
|
|
"strconv"
|
|
|
|
|
+ "time"
|
|
|
|
|
+
|
|
|
|
|
+ costAnalyzerCloud "github.com/kubecost/cost-model/cloud"
|
|
|
|
|
+ prometheusClient "github.com/prometheus/client_golang/api"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
type Aggregation struct {
|
|
type Aggregation struct {
|
|
@@ -23,28 +27,104 @@ type Aggregation struct {
|
|
|
GPUCost float64 `json:"gpuCost"`
|
|
GPUCost float64 `json:"gpuCost"`
|
|
|
PVCost float64 `json:"pvCost"`
|
|
PVCost float64 `json:"pvCost"`
|
|
|
NetworkCost float64 `json:"networkCost"`
|
|
NetworkCost float64 `json:"networkCost"`
|
|
|
|
|
+ SharedCost float64 `json:"sharedCost"`
|
|
|
TotalCost float64 `json:"totalCost"`
|
|
TotalCost float64 `json:"totalCost"`
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func AggregateCostModel(costData map[string]*CostData, discount float64, aggregationField string, aggregationSubField string) map[string]*Aggregation {
|
|
|
|
|
|
|
+type SharedResourceInfo struct {
|
|
|
|
|
+ ShareResources bool
|
|
|
|
|
+ SharedNamespace map[string]bool
|
|
|
|
|
+ LabelSelectors map[string]string
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (s *SharedResourceInfo) IsSharedResource(costDatum *CostData) bool {
|
|
|
|
|
+ if _, ok := s.SharedNamespace[costDatum.Namespace]; ok {
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+ for labelName, labelValue := range s.LabelSelectors {
|
|
|
|
|
+ if val, ok := costDatum.Labels[labelName]; ok {
|
|
|
|
|
+ if val == labelValue {
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return false
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func NewSharedResourceInfo(shareResources bool, sharedNamespaces []string, labelnames []string, labelvalues []string) *SharedResourceInfo {
|
|
|
|
|
+ sr := &SharedResourceInfo{
|
|
|
|
|
+ ShareResources: shareResources,
|
|
|
|
|
+ SharedNamespace: make(map[string]bool),
|
|
|
|
|
+ LabelSelectors: make(map[string]string),
|
|
|
|
|
+ }
|
|
|
|
|
+ for _, ns := range sharedNamespaces {
|
|
|
|
|
+ sr.SharedNamespace[ns] = true
|
|
|
|
|
+ }
|
|
|
|
|
+ sr.SharedNamespace["kube-system"] = true // kube-system should be split by default
|
|
|
|
|
+ for i := range labelnames {
|
|
|
|
|
+ sr.LabelSelectors[labelnames[i]] = labelvalues[i]
|
|
|
|
|
+ }
|
|
|
|
|
+ return sr
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func ComputeIdleCoefficient(costData map[string]*CostData, cli prometheusClient.Client, cloud costAnalyzerCloud.Provider, discount float64, windowString, offset string) (float64, error) {
|
|
|
|
|
+ windowDuration, err := time.ParseDuration(windowString)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return 0.0, err
|
|
|
|
|
+ }
|
|
|
|
|
+ totals, err := ClusterCosts(cli, cloud, windowString, offset)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return 0.0, err
|
|
|
|
|
+ }
|
|
|
|
|
+ totalClusterCost, err := strconv.ParseFloat(totals.TotalCost[0][1], 64)
|
|
|
|
|
+ if err != nil || totalClusterCost == 0.0 {
|
|
|
|
|
+ return 0.0, err
|
|
|
|
|
+ }
|
|
|
|
|
+ totalClusterCostOverWindow := (totalClusterCost / 730) * windowDuration.Hours() * (1 - discount)
|
|
|
|
|
+ totalContainerCost := 0.0
|
|
|
|
|
+ for _, costDatum := range costData {
|
|
|
|
|
+ cpuv, ramv, gpuv, pvvs := getPriceVectors(costDatum, discount, 1)
|
|
|
|
|
+ totalContainerCost += totalVector(cpuv)
|
|
|
|
|
+ totalContainerCost += totalVector(ramv)
|
|
|
|
|
+ totalContainerCost += totalVector(gpuv)
|
|
|
|
|
+ for _, pv := range pvvs {
|
|
|
|
|
+ totalContainerCost += totalVector(pv)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return (totalContainerCost / totalClusterCostOverWindow), nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func AggregateCostModel(costData map[string]*CostData, discount float64, idleCoefficient float64, sr *SharedResourceInfo, aggregationField string, aggregationSubField string) map[string]*Aggregation {
|
|
|
aggregations := make(map[string]*Aggregation)
|
|
aggregations := make(map[string]*Aggregation)
|
|
|
|
|
+ sharedResourceCost := 0.0
|
|
|
for _, costDatum := range costData {
|
|
for _, costDatum := range costData {
|
|
|
- if aggregationField == "cluster" {
|
|
|
|
|
- aggregationHelper(costDatum, aggregationField, aggregationSubField, costDatum.ClusterID, aggregations, discount)
|
|
|
|
|
- } else if aggregationField == "namespace" {
|
|
|
|
|
- aggregationHelper(costDatum, aggregationField, aggregationSubField, costDatum.Namespace, aggregations, discount)
|
|
|
|
|
- } else if aggregationField == "service" {
|
|
|
|
|
- if len(costDatum.Services) > 0 {
|
|
|
|
|
- aggregationHelper(costDatum, aggregationField, aggregationSubField, costDatum.Services[0], aggregations, discount)
|
|
|
|
|
- }
|
|
|
|
|
- } else if aggregationField == "deployment" {
|
|
|
|
|
- if len(costDatum.Deployments) > 0 {
|
|
|
|
|
- aggregationHelper(costDatum, aggregationField, aggregationSubField, costDatum.Deployments[0], aggregations, discount)
|
|
|
|
|
|
|
+ if sr != nil && sr.ShareResources && sr.IsSharedResource(costDatum) {
|
|
|
|
|
+ cpuv, ramv, gpuv, pvvs := getPriceVectors(costDatum, discount, idleCoefficient)
|
|
|
|
|
+ sharedResourceCost += totalVector(cpuv)
|
|
|
|
|
+ sharedResourceCost += totalVector(ramv)
|
|
|
|
|
+ sharedResourceCost += totalVector(gpuv)
|
|
|
|
|
+ for _, pv := range pvvs {
|
|
|
|
|
+ sharedResourceCost += totalVector(pv)
|
|
|
}
|
|
}
|
|
|
- } else if aggregationField == "label" {
|
|
|
|
|
- if costDatum.Labels != nil {
|
|
|
|
|
- if subfieldName, ok := costDatum.Labels[aggregationSubField]; ok {
|
|
|
|
|
- aggregationHelper(costDatum, aggregationField, aggregationSubField, subfieldName, aggregations, discount)
|
|
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if aggregationField == "cluster" {
|
|
|
|
|
+ aggregationHelper(costDatum, aggregationField, aggregationSubField, costDatum.ClusterID, aggregations, discount, idleCoefficient)
|
|
|
|
|
+ } else if aggregationField == "namespace" {
|
|
|
|
|
+ aggregationHelper(costDatum, aggregationField, aggregationSubField, costDatum.Namespace, aggregations, discount, idleCoefficient)
|
|
|
|
|
+ } else if aggregationField == "service" {
|
|
|
|
|
+ if len(costDatum.Services) > 0 {
|
|
|
|
|
+ aggregationHelper(costDatum, aggregationField, aggregationSubField, costDatum.Services[0], aggregations, discount, idleCoefficient)
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if aggregationField == "deployment" {
|
|
|
|
|
+ if len(costDatum.Deployments) > 0 {
|
|
|
|
|
+ aggregationHelper(costDatum, aggregationField, aggregationSubField, costDatum.Deployments[0], aggregations, discount, idleCoefficient)
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if aggregationField == "label" {
|
|
|
|
|
+ if costDatum.Labels != nil {
|
|
|
|
|
+ if subfieldName, ok := costDatum.Labels[aggregationSubField]; ok {
|
|
|
|
|
+ aggregationHelper(costDatum, aggregationField, aggregationSubField, subfieldName, aggregations, discount, idleCoefficient)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -54,12 +134,13 @@ func AggregateCostModel(costData map[string]*CostData, discount float64, aggrega
|
|
|
agg.RAMCost = totalVector(agg.RAMCostVector)
|
|
agg.RAMCost = totalVector(agg.RAMCostVector)
|
|
|
agg.GPUCost = totalVector(agg.GPUCostVector)
|
|
agg.GPUCost = totalVector(agg.GPUCostVector)
|
|
|
agg.PVCost = totalVector(agg.PVCostVector)
|
|
agg.PVCost = totalVector(agg.PVCostVector)
|
|
|
- agg.TotalCost = agg.CPUCost + agg.RAMCost + agg.GPUCost + agg.PVCost
|
|
|
|
|
|
|
+ agg.SharedCost = sharedResourceCost / float64(len(aggregations))
|
|
|
|
|
+ agg.TotalCost = agg.CPUCost + agg.RAMCost + agg.GPUCost + agg.PVCost + agg.SharedCost
|
|
|
}
|
|
}
|
|
|
return aggregations
|
|
return aggregations
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func aggregationHelper(costDatum *CostData, aggregator string, aggregatorSubField string, key string, aggregations map[string]*Aggregation, discount float64) {
|
|
|
|
|
|
|
+func aggregationHelper(costDatum *CostData, aggregator string, aggregatorSubField string, key string, aggregations map[string]*Aggregation, discount float64, idleCoefficient float64) {
|
|
|
if _, ok := aggregations[key]; !ok {
|
|
if _, ok := aggregations[key]; !ok {
|
|
|
agg := &Aggregation{}
|
|
agg := &Aggregation{}
|
|
|
agg.Aggregator = aggregator
|
|
agg.Aggregator = aggregator
|
|
@@ -68,15 +149,15 @@ func aggregationHelper(costDatum *CostData, aggregator string, aggregatorSubFiel
|
|
|
agg.Cluster = costDatum.ClusterID
|
|
agg.Cluster = costDatum.ClusterID
|
|
|
aggregations[key] = agg
|
|
aggregations[key] = agg
|
|
|
}
|
|
}
|
|
|
- mergeVectors(costDatum, aggregations[key], discount)
|
|
|
|
|
|
|
+ mergeVectors(costDatum, aggregations[key], discount, idleCoefficient)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func mergeVectors(costDatum *CostData, aggregation *Aggregation, discount float64) {
|
|
|
|
|
|
|
+func mergeVectors(costDatum *CostData, aggregation *Aggregation, discount float64, idleCoefficient float64) {
|
|
|
aggregation.CPUAllocation = addVectors(costDatum.CPUAllocation, aggregation.CPUAllocation)
|
|
aggregation.CPUAllocation = addVectors(costDatum.CPUAllocation, aggregation.CPUAllocation)
|
|
|
aggregation.RAMAllocation = addVectors(costDatum.RAMAllocation, aggregation.RAMAllocation)
|
|
aggregation.RAMAllocation = addVectors(costDatum.RAMAllocation, aggregation.RAMAllocation)
|
|
|
aggregation.GPUAllocation = addVectors(costDatum.GPUReq, aggregation.GPUAllocation)
|
|
aggregation.GPUAllocation = addVectors(costDatum.GPUReq, aggregation.GPUAllocation)
|
|
|
|
|
|
|
|
- cpuv, ramv, gpuv, pvvs := getPriceVectors(costDatum, discount)
|
|
|
|
|
|
|
+ cpuv, ramv, gpuv, pvvs := getPriceVectors(costDatum, discount, idleCoefficient)
|
|
|
aggregation.CPUCostVector = addVectors(cpuv, aggregation.CPUCostVector)
|
|
aggregation.CPUCostVector = addVectors(cpuv, aggregation.CPUCostVector)
|
|
|
aggregation.RAMCostVector = addVectors(ramv, aggregation.RAMCostVector)
|
|
aggregation.RAMCostVector = addVectors(ramv, aggregation.RAMCostVector)
|
|
|
aggregation.GPUCostVector = addVectors(gpuv, aggregation.GPUCostVector)
|
|
aggregation.GPUCostVector = addVectors(gpuv, aggregation.GPUCostVector)
|
|
@@ -85,13 +166,13 @@ func mergeVectors(costDatum *CostData, aggregation *Aggregation, discount float6
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func getPriceVectors(costDatum *CostData, discount float64) ([]*Vector, []*Vector, []*Vector, [][]*Vector) {
|
|
|
|
|
|
|
+func getPriceVectors(costDatum *CostData, discount float64, idleCoefficient float64) ([]*Vector, []*Vector, []*Vector, [][]*Vector) {
|
|
|
cpuv := make([]*Vector, 0, len(costDatum.CPUAllocation))
|
|
cpuv := make([]*Vector, 0, len(costDatum.CPUAllocation))
|
|
|
for _, val := range costDatum.CPUAllocation {
|
|
for _, val := range costDatum.CPUAllocation {
|
|
|
cost, _ := strconv.ParseFloat(costDatum.NodeData.VCPUCost, 64)
|
|
cost, _ := strconv.ParseFloat(costDatum.NodeData.VCPUCost, 64)
|
|
|
cpuv = append(cpuv, &Vector{
|
|
cpuv = append(cpuv, &Vector{
|
|
|
Timestamp: math.Round(val.Timestamp/10) * 10,
|
|
Timestamp: math.Round(val.Timestamp/10) * 10,
|
|
|
- Value: val.Value * cost * (1 - discount),
|
|
|
|
|
|
|
+ Value: val.Value * cost * (1 - discount) * 1 / idleCoefficient,
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
ramv := make([]*Vector, 0, len(costDatum.RAMAllocation))
|
|
ramv := make([]*Vector, 0, len(costDatum.RAMAllocation))
|
|
@@ -99,7 +180,7 @@ func getPriceVectors(costDatum *CostData, discount float64) ([]*Vector, []*Vecto
|
|
|
cost, _ := strconv.ParseFloat(costDatum.NodeData.RAMCost, 64)
|
|
cost, _ := strconv.ParseFloat(costDatum.NodeData.RAMCost, 64)
|
|
|
ramv = append(ramv, &Vector{
|
|
ramv = append(ramv, &Vector{
|
|
|
Timestamp: math.Round(val.Timestamp/10) * 10,
|
|
Timestamp: math.Round(val.Timestamp/10) * 10,
|
|
|
- Value: (val.Value / 1024 / 1024 / 1024) * cost * (1 - discount),
|
|
|
|
|
|
|
+ Value: (val.Value / 1024 / 1024 / 1024) * cost * (1 - discount) * 1 / idleCoefficient,
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
gpuv := make([]*Vector, 0, len(costDatum.GPUReq))
|
|
gpuv := make([]*Vector, 0, len(costDatum.GPUReq))
|
|
@@ -107,7 +188,7 @@ func getPriceVectors(costDatum *CostData, discount float64) ([]*Vector, []*Vecto
|
|
|
cost, _ := strconv.ParseFloat(costDatum.NodeData.GPUCost, 64)
|
|
cost, _ := strconv.ParseFloat(costDatum.NodeData.GPUCost, 64)
|
|
|
gpuv = append(gpuv, &Vector{
|
|
gpuv = append(gpuv, &Vector{
|
|
|
Timestamp: math.Round(val.Timestamp/10) * 10,
|
|
Timestamp: math.Round(val.Timestamp/10) * 10,
|
|
|
- Value: val.Value * cost * (1 - discount),
|
|
|
|
|
|
|
+ Value: val.Value * cost * (1 - discount) * 1 / idleCoefficient,
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
pvvs := make([][]*Vector, 0, len(costDatum.PVCData))
|
|
pvvs := make([][]*Vector, 0, len(costDatum.PVCData))
|
|
@@ -118,7 +199,7 @@ func getPriceVectors(costDatum *CostData, discount float64) ([]*Vector, []*Vecto
|
|
|
for _, val := range pvcData.Values {
|
|
for _, val := range pvcData.Values {
|
|
|
pvv = append(pvv, &Vector{
|
|
pvv = append(pvv, &Vector{
|
|
|
Timestamp: math.Round(val.Timestamp/10) * 10,
|
|
Timestamp: math.Round(val.Timestamp/10) * 10,
|
|
|
- Value: (val.Value / 1024 / 1024 / 1024) * cost * (1 - discount),
|
|
|
|
|
|
|
+ Value: (val.Value / 1024 / 1024 / 1024) * cost * (1 - discount) * 1 / idleCoefficient,
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
pvvs = append(pvvs, pvv)
|
|
pvvs = append(pvvs, pvv)
|
|
@@ -196,4 +277,4 @@ func addVectors(req []*Vector, used []*Vector) []*Vector {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return allocation
|
|
return allocation
|
|
|
-}
|
|
|
|
|
|
|
+}
|