|
@@ -38,23 +38,23 @@ const (
|
|
|
|
|
|
|
|
// TODO move this to a package-accessible helper
|
|
// TODO move this to a package-accessible helper
|
|
|
type PromQueryContext struct {
|
|
type PromQueryContext struct {
|
|
|
- client prometheus.Client
|
|
|
|
|
- ec *util.ErrorCollector
|
|
|
|
|
- wg *sync.WaitGroup
|
|
|
|
|
|
|
+ Client prometheus.Client
|
|
|
|
|
+ ErrorCollector *util.ErrorCollector
|
|
|
|
|
+ WaitGroup *sync.WaitGroup
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// TODO move this to a package-accessible helper function once dependencies are able to
|
|
// TODO move this to a package-accessible helper function once dependencies are able to
|
|
|
// be extricated from costmodel package (PromQueryResult -> util.Vector). Otherwise, circular deps.
|
|
// be extricated from costmodel package (PromQueryResult -> util.Vector). Otherwise, circular deps.
|
|
|
func AsyncPromQuery(query string, resultCh chan []*PromQueryResult, ctx PromQueryContext) {
|
|
func AsyncPromQuery(query string, resultCh chan []*PromQueryResult, ctx PromQueryContext) {
|
|
|
- if ctx.wg != nil {
|
|
|
|
|
- defer ctx.wg.Done()
|
|
|
|
|
|
|
+ if ctx.WaitGroup != nil {
|
|
|
|
|
+ defer ctx.WaitGroup.Done()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- raw, promErr := Query(ctx.client, query)
|
|
|
|
|
- ctx.ec.Report(promErr)
|
|
|
|
|
|
|
+ raw, promErr := Query(ctx.Client, query)
|
|
|
|
|
+ ctx.ErrorCollector.Report(promErr)
|
|
|
|
|
|
|
|
results, parseErr := NewQueryResults(raw)
|
|
results, parseErr := NewQueryResults(raw)
|
|
|
- ctx.ec.Report(parseErr)
|
|
|
|
|
|
|
+ ctx.ErrorCollector.Report(parseErr)
|
|
|
|
|
|
|
|
resultCh <- results
|
|
resultCh <- results
|
|
|
}
|
|
}
|
|
@@ -62,18 +62,30 @@ func AsyncPromQuery(query string, resultCh chan []*PromQueryResult, ctx PromQuer
|
|
|
// Costs represents cumulative and monthly cluster costs over a given duration. Costs
|
|
// Costs represents cumulative and monthly cluster costs over a given duration. Costs
|
|
|
// are broken down by cores, memory, and storage.
|
|
// are broken down by cores, memory, and storage.
|
|
|
type ClusterCosts struct {
|
|
type ClusterCosts struct {
|
|
|
- Start *time.Time `json:"startTime"`
|
|
|
|
|
- End *time.Time `json:"endTime"`
|
|
|
|
|
- CPUCumulative float64 `json:"cpuCumulativeCost"`
|
|
|
|
|
- CPUMonthly float64 `json:"cpuMonthlyCost"`
|
|
|
|
|
- GPUCumulative float64 `json:"gpuCumulativeCost"`
|
|
|
|
|
- GPUMonthly float64 `json:"gpuMonthlyCost"`
|
|
|
|
|
- RAMCumulative float64 `json:"ramCumulativeCost"`
|
|
|
|
|
- RAMMonthly float64 `json:"ramMonthlyCost"`
|
|
|
|
|
- StorageCumulative float64 `json:"storageCumulativeCost"`
|
|
|
|
|
- StorageMonthly float64 `json:"storageMonthlyCost"`
|
|
|
|
|
- TotalCumulative float64 `json:"totalCumulativeCost"`
|
|
|
|
|
- TotalMonthly float64 `json:"totalMonthlyCost"`
|
|
|
|
|
|
|
+ Start *time.Time `json:"startTime"`
|
|
|
|
|
+ End *time.Time `json:"endTime"`
|
|
|
|
|
+ CPUCumulative float64 `json:"cpuCumulativeCost"`
|
|
|
|
|
+ CPUMonthly float64 `json:"cpuMonthlyCost"`
|
|
|
|
|
+ CPUBreakdown *ClusterCostsBreakdown `json:"cpuBreakdown"`
|
|
|
|
|
+ GPUCumulative float64 `json:"gpuCumulativeCost"`
|
|
|
|
|
+ GPUMonthly float64 `json:"gpuMonthlyCost"`
|
|
|
|
|
+ RAMCumulative float64 `json:"ramCumulativeCost"`
|
|
|
|
|
+ RAMMonthly float64 `json:"ramMonthlyCost"`
|
|
|
|
|
+ RAMBreakdown *ClusterCostsBreakdown `json:"ramBreakdown"`
|
|
|
|
|
+ StorageCumulative float64 `json:"storageCumulativeCost"`
|
|
|
|
|
+ StorageMonthly float64 `json:"storageMonthlyCost"`
|
|
|
|
|
+ StorageBreakdown *ClusterCostsBreakdown `json:"storageBreakdown"`
|
|
|
|
|
+ TotalCumulative float64 `json:"totalCumulativeCost"`
|
|
|
|
|
+ TotalMonthly float64 `json:"totalMonthlyCost"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ClusterCostsBreakdown provides percentage-based breakdown of a resource by
|
|
|
|
|
+// categories: user for user-space (i.e. non-system) usage, system, and idle.
|
|
|
|
|
+type ClusterCostsBreakdown struct {
|
|
|
|
|
+ Idle float64 `json:"idle"`
|
|
|
|
|
+ Other float64 `json:"other"`
|
|
|
|
|
+ System float64 `json:"system"`
|
|
|
|
|
+ User float64 `json:"user"`
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// NewClusterCostsFromCumulative takes cumulative cost data over a given time range, computes
|
|
// NewClusterCostsFromCumulative takes cumulative cost data over a given time range, computes
|
|
@@ -112,42 +124,6 @@ func NewClusterCostsFromCumulative(cpu, gpu, ram, storage float64, window, offse
|
|
|
return cc, nil
|
|
return cc, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// NewClusterCostsFromMonthly takes monthly-rate cost data over a given time range, computes
|
|
|
|
|
-// the associated cumulative cost data, and returns the Costs.
|
|
|
|
|
-func NewClusterCostsFromMonthly(cpuMonthly, gpuMonthly, ramMonthly, storageMonthly float64, window, offset string, dataHours float64) (*ClusterCosts, error) {
|
|
|
|
|
- start, end, err := util.ParseTimeRange(window, offset)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return nil, err
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // If the number of hours is not given (i.e. is zero) compute one from the window and offset
|
|
|
|
|
- if dataHours == 0 {
|
|
|
|
|
- dataHours = end.Sub(*start).Hours()
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Do not allow zero-length windows to prevent divide-by-zero issues
|
|
|
|
|
- if dataHours == 0 {
|
|
|
|
|
- return nil, fmt.Errorf("illegal time range: window %s, offset %s", window, offset)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- cc := &ClusterCosts{
|
|
|
|
|
- Start: start,
|
|
|
|
|
- End: end,
|
|
|
|
|
- CPUMonthly: cpuMonthly,
|
|
|
|
|
- GPUMonthly: gpuMonthly,
|
|
|
|
|
- RAMMonthly: ramMonthly,
|
|
|
|
|
- StorageMonthly: storageMonthly,
|
|
|
|
|
- TotalMonthly: cpuMonthly + gpuMonthly + ramMonthly + storageMonthly,
|
|
|
|
|
- CPUCumulative: cpuMonthly / util.HoursPerMonth * dataHours,
|
|
|
|
|
- GPUCumulative: gpuMonthly / util.HoursPerMonth * dataHours,
|
|
|
|
|
- RAMCumulative: ramMonthly / util.HoursPerMonth * dataHours,
|
|
|
|
|
- StorageCumulative: storageMonthly / util.HoursPerMonth * dataHours,
|
|
|
|
|
- }
|
|
|
|
|
- cc.TotalCumulative = cc.CPUCumulative + cc.GPUCumulative + cc.RAMCumulative + cc.StorageCumulative
|
|
|
|
|
-
|
|
|
|
|
- return cc, nil
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
// ComputeClusterCosts gives the cumulative and monthly-rate cluster costs over a window of time for all clusters.
|
|
// ComputeClusterCosts gives the cumulative and monthly-rate cluster costs over a window of time for all clusters.
|
|
|
func ComputeClusterCosts(client prometheus.Client, provider cloud.Provider, window, offset string) (map[string]*ClusterCosts, error) {
|
|
func ComputeClusterCosts(client prometheus.Client, provider cloud.Provider, window, offset string) (map[string]*ClusterCosts, error) {
|
|
|
// Compute number of minutes in the full interval, for use interpolating missed scrapes or scaling missing data
|
|
// Compute number of minutes in the full interval, for use interpolating missed scrapes or scaling missing data
|
|
@@ -178,7 +154,22 @@ func ComputeClusterCosts(client prometheus.Client, provider cloud.Provider, wind
|
|
|
avg(avg_over_time(pv_hourly_cost[%s:1m]%s)) by (persistentvolume, cluster_id) / 60
|
|
avg(avg_over_time(pv_hourly_cost[%s:1m]%s)) by (persistentvolume, cluster_id) / 60
|
|
|
) by (cluster_id) %s`
|
|
) by (cluster_id) %s`
|
|
|
|
|
|
|
|
- queryTotalLocalStorage := provider.GetLocalStorageQuery(window, offset, false)
|
|
|
|
|
|
|
+ const fmtQueryCPUModePct = `sum(rate(node_cpu_seconds_total[%s]%s)) by (cluster_id, mode) / ignoring(mode)
|
|
|
|
|
+ group_left sum(rate(node_cpu_seconds_total[%s]%s)) by (cluster_id)`
|
|
|
|
|
+
|
|
|
|
|
+ const fmtQueryRAMSystemPct = `sum(sum_over_time(container_memory_usage_bytes{container_name!="",namespace="kube-system"}[%s:1m]%s)) by (cluster_id)
|
|
|
|
|
+ / sum(sum_over_time(kube_node_status_capacity_memory_bytes[%s:1m]%s)) by (cluster_id)`
|
|
|
|
|
+
|
|
|
|
|
+ const fmtQueryRAMUserPct = `sum(sum_over_time(kubecost_cluster_memory_working_set_bytes[%s:1m]%s)) by (cluster_id)
|
|
|
|
|
+ / sum(sum_over_time(kube_node_status_capacity_memory_bytes[%s:1m]%s)) by (cluster_id)`
|
|
|
|
|
+
|
|
|
|
|
+ // TODO niko/clustercost metric "kubelet_volume_stats_used_bytes" was deprecated in 1.12, then seems to have come back in 1.17
|
|
|
|
|
+ // const fmtQueryPVStorageUsePct = `(sum(kube_persistentvolumeclaim_info) by (persistentvolumeclaim, storageclass,namespace) + on (persistentvolumeclaim,namespace)
|
|
|
|
|
+ // group_right(storageclass) sum(kubelet_volume_stats_used_bytes) by (persistentvolumeclaim,namespace))`
|
|
|
|
|
+
|
|
|
|
|
+ queryUsedLocalStorage := provider.GetLocalStorageQuery(window, offset, false, true)
|
|
|
|
|
+
|
|
|
|
|
+ queryTotalLocalStorage := provider.GetLocalStorageQuery(window, offset, false, false)
|
|
|
if queryTotalLocalStorage != "" {
|
|
if queryTotalLocalStorage != "" {
|
|
|
queryTotalLocalStorage = fmt.Sprintf(" + %s", queryTotalLocalStorage)
|
|
queryTotalLocalStorage = fmt.Sprintf(" + %s", queryTotalLocalStorage)
|
|
|
}
|
|
}
|
|
@@ -193,19 +184,27 @@ func ComputeClusterCosts(client prometheus.Client, provider cloud.Provider, wind
|
|
|
queryTotalCPU := fmt.Sprintf(fmtQueryTotalCPU, window, fmtOffset, window, fmtOffset)
|
|
queryTotalCPU := fmt.Sprintf(fmtQueryTotalCPU, window, fmtOffset, window, fmtOffset)
|
|
|
queryTotalRAM := fmt.Sprintf(fmtQueryTotalRAM, window, fmtOffset, window, fmtOffset)
|
|
queryTotalRAM := fmt.Sprintf(fmtQueryTotalRAM, window, fmtOffset, window, fmtOffset)
|
|
|
queryTotalStorage := fmt.Sprintf(fmtQueryTotalStorage, window, fmtOffset, window, fmtOffset, queryTotalLocalStorage)
|
|
queryTotalStorage := fmt.Sprintf(fmtQueryTotalStorage, window, fmtOffset, window, fmtOffset, queryTotalLocalStorage)
|
|
|
- numQueries := 5
|
|
|
|
|
|
|
+ queryCPUModePct := fmt.Sprintf(fmtQueryCPUModePct, window, fmtOffset, window, fmtOffset)
|
|
|
|
|
+ queryRAMSystemPct := fmt.Sprintf(fmtQueryRAMSystemPct, window, fmtOffset, window, fmtOffset)
|
|
|
|
|
+ queryRAMUserPct := fmt.Sprintf(fmtQueryRAMUserPct, window, fmtOffset, window, fmtOffset)
|
|
|
|
|
+
|
|
|
|
|
+ numQueries := 9
|
|
|
|
|
|
|
|
klog.V(4).Infof("[Debug] queryDataCount: %s", queryDataCount)
|
|
klog.V(4).Infof("[Debug] queryDataCount: %s", queryDataCount)
|
|
|
klog.V(4).Infof("[Debug] queryTotalGPU: %s", queryTotalGPU)
|
|
klog.V(4).Infof("[Debug] queryTotalGPU: %s", queryTotalGPU)
|
|
|
klog.V(4).Infof("[Debug] queryTotalCPU: %s", queryTotalCPU)
|
|
klog.V(4).Infof("[Debug] queryTotalCPU: %s", queryTotalCPU)
|
|
|
klog.V(4).Infof("[Debug] queryTotalRAM: %s", queryTotalRAM)
|
|
klog.V(4).Infof("[Debug] queryTotalRAM: %s", queryTotalRAM)
|
|
|
klog.V(4).Infof("[Debug] queryTotalStorage: %s", queryTotalStorage)
|
|
klog.V(4).Infof("[Debug] queryTotalStorage: %s", queryTotalStorage)
|
|
|
|
|
+ klog.V(4).Infof("[Debug] queryCPUModePct: %s", queryCPUModePct)
|
|
|
|
|
+ klog.V(4).Infof("[Debug] queryRAMSystemPct: %s", queryRAMSystemPct)
|
|
|
|
|
+ klog.V(4).Infof("[Debug] queryRAMUserPct: %s", queryRAMUserPct)
|
|
|
|
|
+ klog.V(4).Infof("[Debug] queryUsedLocalStorage: %s", queryUsedLocalStorage)
|
|
|
|
|
|
|
|
// Submit queries to Prometheus asynchronously
|
|
// Submit queries to Prometheus asynchronously
|
|
|
var ec util.ErrorCollector
|
|
var ec util.ErrorCollector
|
|
|
var wg sync.WaitGroup
|
|
var wg sync.WaitGroup
|
|
|
ctx := PromQueryContext{client, &ec, &wg}
|
|
ctx := PromQueryContext{client, &ec, &wg}
|
|
|
- ctx.wg.Add(numQueries)
|
|
|
|
|
|
|
+ ctx.WaitGroup.Add(numQueries)
|
|
|
|
|
|
|
|
chDataCount := make(chan []*PromQueryResult, 1)
|
|
chDataCount := make(chan []*PromQueryResult, 1)
|
|
|
go AsyncPromQuery(queryDataCount, chDataCount, ctx)
|
|
go AsyncPromQuery(queryDataCount, chDataCount, ctx)
|
|
@@ -222,6 +221,18 @@ func ComputeClusterCosts(client prometheus.Client, provider cloud.Provider, wind
|
|
|
chTotalStorage := make(chan []*PromQueryResult, 1)
|
|
chTotalStorage := make(chan []*PromQueryResult, 1)
|
|
|
go AsyncPromQuery(queryTotalStorage, chTotalStorage, ctx)
|
|
go AsyncPromQuery(queryTotalStorage, chTotalStorage, ctx)
|
|
|
|
|
|
|
|
|
|
+ chCPUModePct := make(chan []*PromQueryResult, 1)
|
|
|
|
|
+ go AsyncPromQuery(queryCPUModePct, chCPUModePct, ctx)
|
|
|
|
|
+
|
|
|
|
|
+ chRAMSystemPct := make(chan []*PromQueryResult, 1)
|
|
|
|
|
+ go AsyncPromQuery(queryRAMSystemPct, chRAMSystemPct, ctx)
|
|
|
|
|
+
|
|
|
|
|
+ chRAMUserPct := make(chan []*PromQueryResult, 1)
|
|
|
|
|
+ go AsyncPromQuery(queryRAMUserPct, chRAMUserPct, ctx)
|
|
|
|
|
+
|
|
|
|
|
+ chUsedLocalStorage := make(chan []*PromQueryResult, 1)
|
|
|
|
|
+ go AsyncPromQuery(queryUsedLocalStorage, chUsedLocalStorage, ctx)
|
|
|
|
|
+
|
|
|
// After queries complete, retrieve results
|
|
// After queries complete, retrieve results
|
|
|
wg.Wait()
|
|
wg.Wait()
|
|
|
|
|
|
|
@@ -240,6 +251,18 @@ func ComputeClusterCosts(client prometheus.Client, provider cloud.Provider, wind
|
|
|
resultsTotalStorage := <-chTotalStorage
|
|
resultsTotalStorage := <-chTotalStorage
|
|
|
close(chTotalStorage)
|
|
close(chTotalStorage)
|
|
|
|
|
|
|
|
|
|
+ resultsCPUModePct := <-chCPUModePct
|
|
|
|
|
+ close(chCPUModePct)
|
|
|
|
|
+
|
|
|
|
|
+ resultsRAMSystemPct := <-chRAMSystemPct
|
|
|
|
|
+ close(chRAMSystemPct)
|
|
|
|
|
+
|
|
|
|
|
+ resultsRAMUserPct := <-chRAMUserPct
|
|
|
|
|
+ close(chRAMUserPct)
|
|
|
|
|
+
|
|
|
|
|
+ resultsUsedLocalStorage := <-chUsedLocalStorage
|
|
|
|
|
+ close(chUsedLocalStorage)
|
|
|
|
|
+
|
|
|
defaultClusterID := os.Getenv(clusterIDKey)
|
|
defaultClusterID := os.Getenv(clusterIDKey)
|
|
|
|
|
|
|
|
dataMinsByCluster := map[string]float64{}
|
|
dataMinsByCluster := map[string]float64{}
|
|
@@ -257,12 +280,26 @@ func ComputeClusterCosts(client prometheus.Client, provider cloud.Provider, wind
|
|
|
dataMinsByCluster[clusterID] = dataMins
|
|
dataMinsByCluster[clusterID] = dataMins
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // Determine combined discount
|
|
|
|
|
+ discount, customDiscount := 0.0, 0.0
|
|
|
|
|
+ c, err := A.Cloud.GetConfig()
|
|
|
|
|
+ if err == nil {
|
|
|
|
|
+ discount, err = ParsePercentString(c.Discount)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ discount = 0.0
|
|
|
|
|
+ }
|
|
|
|
|
+ customDiscount, err = ParsePercentString(c.NegotiatedDiscount)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ customDiscount = 0.0
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// Intermediate structure storing mapping of [clusterID][type ∈ {cpu, ram, storage, total}]=cost
|
|
// Intermediate structure storing mapping of [clusterID][type ∈ {cpu, ram, storage, total}]=cost
|
|
|
costData := make(map[string]map[string]float64)
|
|
costData := make(map[string]map[string]float64)
|
|
|
|
|
|
|
|
// Helper function to iterate over Prom query results, parsing the raw values into
|
|
// Helper function to iterate over Prom query results, parsing the raw values into
|
|
|
// the intermediate costData structure.
|
|
// the intermediate costData structure.
|
|
|
- setCostsFromResults := func(costData map[string]map[string]float64, results []*PromQueryResult, name string) {
|
|
|
|
|
|
|
+ setCostsFromResults := func(costData map[string]map[string]float64, results []*PromQueryResult, name string, discount float64, customDiscount float64) {
|
|
|
for _, result := range results {
|
|
for _, result := range results {
|
|
|
clusterID, _ := result.GetString("cluster_id")
|
|
clusterID, _ := result.GetString("cluster_id")
|
|
|
if clusterID == "" {
|
|
if clusterID == "" {
|
|
@@ -272,15 +309,86 @@ func ComputeClusterCosts(client prometheus.Client, provider cloud.Provider, wind
|
|
|
costData[clusterID] = map[string]float64{}
|
|
costData[clusterID] = map[string]float64{}
|
|
|
}
|
|
}
|
|
|
if len(result.Values) > 0 {
|
|
if len(result.Values) > 0 {
|
|
|
- costData[clusterID][name] += result.Values[0].Value
|
|
|
|
|
- costData[clusterID]["total"] += result.Values[0].Value
|
|
|
|
|
|
|
+ costData[clusterID][name] += result.Values[0].Value * (1.0 - discount) * (1.0 - customDiscount)
|
|
|
|
|
+ costData[clusterID]["total"] += result.Values[0].Value * (1.0 - discount) * (1.0 - customDiscount)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- setCostsFromResults(costData, resultsTotalGPU, "gpu")
|
|
|
|
|
- setCostsFromResults(costData, resultsTotalCPU, "cpu")
|
|
|
|
|
- setCostsFromResults(costData, resultsTotalRAM, "ram")
|
|
|
|
|
- setCostsFromResults(costData, resultsTotalStorage, "storage")
|
|
|
|
|
|
|
+ // Apply both sustained use and custom discounts to RAM and CPU
|
|
|
|
|
+ setCostsFromResults(costData, resultsTotalCPU, "cpu", discount, customDiscount)
|
|
|
|
|
+ setCostsFromResults(costData, resultsTotalRAM, "ram", discount, customDiscount)
|
|
|
|
|
+ // Apply only custom discount to GPU and storage
|
|
|
|
|
+ setCostsFromResults(costData, resultsTotalGPU, "gpu", 0.0, customDiscount)
|
|
|
|
|
+ setCostsFromResults(costData, resultsTotalStorage, "storage", 0.0, customDiscount)
|
|
|
|
|
+
|
|
|
|
|
+ cpuBreakdownMap := map[string]*ClusterCostsBreakdown{}
|
|
|
|
|
+ for _, result := range resultsCPUModePct {
|
|
|
|
|
+ clusterID, _ := result.GetString("cluster_id")
|
|
|
|
|
+ if clusterID == "" {
|
|
|
|
|
+ clusterID = defaultClusterID
|
|
|
|
|
+ }
|
|
|
|
|
+ if _, ok := cpuBreakdownMap[clusterID]; !ok {
|
|
|
|
|
+ cpuBreakdownMap[clusterID] = &ClusterCostsBreakdown{}
|
|
|
|
|
+ }
|
|
|
|
|
+ cpuBD := cpuBreakdownMap[clusterID]
|
|
|
|
|
+
|
|
|
|
|
+ mode, err := result.GetString("mode")
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ klog.V(3).Infof("[Warning] ComputeClusterCosts: unable to read CPU mode: %s", err)
|
|
|
|
|
+ mode = "other"
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ switch mode {
|
|
|
|
|
+ case "idle":
|
|
|
|
|
+ cpuBD.Idle += result.Values[0].Value
|
|
|
|
|
+ case "system":
|
|
|
|
|
+ cpuBD.System += result.Values[0].Value
|
|
|
|
|
+ case "user":
|
|
|
|
|
+ cpuBD.User += result.Values[0].Value
|
|
|
|
|
+ default:
|
|
|
|
|
+ cpuBD.Other += result.Values[0].Value
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ramBreakdownMap := map[string]*ClusterCostsBreakdown{}
|
|
|
|
|
+ for _, result := range resultsRAMSystemPct {
|
|
|
|
|
+ clusterID, _ := result.GetString("cluster_id")
|
|
|
|
|
+ if clusterID == "" {
|
|
|
|
|
+ clusterID = defaultClusterID
|
|
|
|
|
+ }
|
|
|
|
|
+ if _, ok := ramBreakdownMap[clusterID]; !ok {
|
|
|
|
|
+ ramBreakdownMap[clusterID] = &ClusterCostsBreakdown{}
|
|
|
|
|
+ }
|
|
|
|
|
+ ramBD := ramBreakdownMap[clusterID]
|
|
|
|
|
+ ramBD.System += result.Values[0].Value
|
|
|
|
|
+ }
|
|
|
|
|
+ for _, result := range resultsRAMUserPct {
|
|
|
|
|
+ clusterID, _ := result.GetString("cluster_id")
|
|
|
|
|
+ if clusterID == "" {
|
|
|
|
|
+ clusterID = defaultClusterID
|
|
|
|
|
+ }
|
|
|
|
|
+ if _, ok := ramBreakdownMap[clusterID]; !ok {
|
|
|
|
|
+ ramBreakdownMap[clusterID] = &ClusterCostsBreakdown{}
|
|
|
|
|
+ }
|
|
|
|
|
+ ramBD := ramBreakdownMap[clusterID]
|
|
|
|
|
+ ramBD.User += result.Values[0].Value
|
|
|
|
|
+ }
|
|
|
|
|
+ for _, ramBD := range ramBreakdownMap {
|
|
|
|
|
+ remaining := 1.0
|
|
|
|
|
+ remaining -= ramBD.Other
|
|
|
|
|
+ remaining -= ramBD.System
|
|
|
|
|
+ remaining -= ramBD.User
|
|
|
|
|
+ ramBD.Idle = remaining
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ pvUsedCostMap := map[string]float64{}
|
|
|
|
|
+ for _, result := range resultsUsedLocalStorage {
|
|
|
|
|
+ clusterID, _ := result.GetString("cluster_id")
|
|
|
|
|
+ if clusterID == "" {
|
|
|
|
|
+ clusterID = defaultClusterID
|
|
|
|
|
+ }
|
|
|
|
|
+ pvUsedCostMap[clusterID] += result.Values[0].Value
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// Convert intermediate structure to Costs instances
|
|
// Convert intermediate structure to Costs instances
|
|
|
costsByCluster := map[string]*ClusterCosts{}
|
|
costsByCluster := map[string]*ClusterCosts{}
|
|
@@ -295,6 +403,19 @@ func ComputeClusterCosts(client prometheus.Client, provider cloud.Provider, wind
|
|
|
klog.V(3).Infof("[Warning] Failed to parse cluster costs on %s (%s) from cumulative data: %+v", window, offset, cd)
|
|
klog.V(3).Infof("[Warning] Failed to parse cluster costs on %s (%s) from cumulative data: %+v", window, offset, cd)
|
|
|
return nil, err
|
|
return nil, err
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ if cpuBD, ok := cpuBreakdownMap[id]; ok {
|
|
|
|
|
+ costs.CPUBreakdown = cpuBD
|
|
|
|
|
+ }
|
|
|
|
|
+ if ramBD, ok := ramBreakdownMap[id]; ok {
|
|
|
|
|
+ costs.RAMBreakdown = ramBD
|
|
|
|
|
+ }
|
|
|
|
|
+ costs.StorageBreakdown = &ClusterCostsBreakdown{}
|
|
|
|
|
+ if pvUC, ok := pvUsedCostMap[id]; ok {
|
|
|
|
|
+ costs.StorageBreakdown.Idle = (costs.StorageCumulative - pvUC) / costs.StorageCumulative
|
|
|
|
|
+ costs.StorageBreakdown.User = pvUC / costs.StorageCumulative
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
costsByCluster[id] = costs
|
|
costsByCluster[id] = costs
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -334,7 +455,7 @@ func resultToTotals(qr interface{}) ([][]string, error) {
|
|
|
|
|
|
|
|
// ClusterCostsOverTime gives the full cluster costs over time
|
|
// ClusterCostsOverTime gives the full cluster costs over time
|
|
|
func ClusterCostsOverTime(cli prometheus.Client, provider cloud.Provider, startString, endString, windowString, offset string) (*Totals, error) {
|
|
func ClusterCostsOverTime(cli prometheus.Client, provider cloud.Provider, startString, endString, windowString, offset string) (*Totals, error) {
|
|
|
- localStorageQuery := provider.GetLocalStorageQuery(windowString, offset, true)
|
|
|
|
|
|
|
+ localStorageQuery := provider.GetLocalStorageQuery(windowString, offset, true, false)
|
|
|
if localStorageQuery != "" {
|
|
if localStorageQuery != "" {
|
|
|
localStorageQuery = fmt.Sprintf("+ %s", localStorageQuery)
|
|
localStorageQuery = fmt.Sprintf("+ %s", localStorageQuery)
|
|
|
}
|
|
}
|