|
|
@@ -2,13 +2,13 @@ package costmodel
|
|
|
|
|
|
import (
|
|
|
"fmt"
|
|
|
+ "github.com/kubecost/cost-model/pkg/util/timeutil"
|
|
|
"time"
|
|
|
|
|
|
"github.com/kubecost/cost-model/pkg/cloud"
|
|
|
"github.com/kubecost/cost-model/pkg/env"
|
|
|
"github.com/kubecost/cost-model/pkg/log"
|
|
|
"github.com/kubecost/cost-model/pkg/prom"
|
|
|
- "github.com/kubecost/cost-model/pkg/util"
|
|
|
|
|
|
prometheus "github.com/prometheus/client_golang/api"
|
|
|
"k8s.io/klog"
|
|
|
@@ -70,15 +70,12 @@ type ClusterCostsBreakdown struct {
|
|
|
|
|
|
// NewClusterCostsFromCumulative takes cumulative cost data over a given time range, computes
|
|
|
// the associated monthly rate data, and returns the Costs.
|
|
|
-func NewClusterCostsFromCumulative(cpu, gpu, ram, storage float64, window, offset string, dataHours float64) (*ClusterCosts, error) {
|
|
|
- start, end, err := util.ParseTimeRange(window, offset)
|
|
|
- if err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
+func NewClusterCostsFromCumulative(cpu, gpu, ram, storage float64, window, offset time.Duration, dataHours float64) (*ClusterCosts, error) {
|
|
|
+ start, end := timeutil.ParseTimeRange(window, offset)
|
|
|
|
|
|
// 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()
|
|
|
+ dataHours = end.Sub(start).Hours()
|
|
|
}
|
|
|
|
|
|
// Do not allow zero-length windows to prevent divide-by-zero issues
|
|
|
@@ -87,17 +84,17 @@ func NewClusterCostsFromCumulative(cpu, gpu, ram, storage float64, window, offse
|
|
|
}
|
|
|
|
|
|
cc := &ClusterCosts{
|
|
|
- Start: start,
|
|
|
- End: end,
|
|
|
+ Start: &start,
|
|
|
+ End: &end,
|
|
|
CPUCumulative: cpu,
|
|
|
GPUCumulative: gpu,
|
|
|
RAMCumulative: ram,
|
|
|
StorageCumulative: storage,
|
|
|
TotalCumulative: cpu + gpu + ram + storage,
|
|
|
- CPUMonthly: cpu / dataHours * (util.HoursPerMonth),
|
|
|
- GPUMonthly: gpu / dataHours * (util.HoursPerMonth),
|
|
|
- RAMMonthly: ram / dataHours * (util.HoursPerMonth),
|
|
|
- StorageMonthly: storage / dataHours * (util.HoursPerMonth),
|
|
|
+ CPUMonthly: cpu / dataHours * (timeutil.HoursPerMonth),
|
|
|
+ GPUMonthly: gpu / dataHours * (timeutil.HoursPerMonth),
|
|
|
+ RAMMonthly: ram / dataHours * (timeutil.HoursPerMonth),
|
|
|
+ StorageMonthly: storage / dataHours * (timeutil.HoursPerMonth),
|
|
|
}
|
|
|
cc.TotalMonthly = cc.CPUMonthly + cc.GPUMonthly + cc.RAMMonthly + cc.StorageMonthly
|
|
|
|
|
|
@@ -195,7 +192,7 @@ func ClusterDisks(client prometheus.Client, provider cloud.Provider, duration, o
|
|
|
diskMap[key].Cost += cost
|
|
|
providerID, _ := result.GetString("provider_id") // just put the providerID set up here, it's the simplest query.
|
|
|
if providerID != "" {
|
|
|
- diskMap[key].ProviderID = provider.ParsePVID(providerID)
|
|
|
+ diskMap[key].ProviderID = cloud.ParsePVID(providerID)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -522,18 +519,18 @@ func ClusterNodes(cp cloud.Provider, client prometheus.Client, duration, offset
|
|
|
return nil, requiredCtx.ErrorCollection()
|
|
|
}
|
|
|
|
|
|
- activeDataMap := buildActiveDataMap(resActiveMins, resolution, cp.ParseID)
|
|
|
+ activeDataMap := buildActiveDataMap(resActiveMins, resolution)
|
|
|
|
|
|
- gpuCountMap := buildGPUCountMap(resNodeGPUCount, cp.ParseID)
|
|
|
+ gpuCountMap := buildGPUCountMap(resNodeGPUCount)
|
|
|
|
|
|
- cpuCostMap, clusterAndNameToType1 := buildCPUCostMap(resNodeCPUHourlyCost, cp.ParseID)
|
|
|
- ramCostMap, clusterAndNameToType2 := buildRAMCostMap(resNodeRAMHourlyCost, cp.ParseID)
|
|
|
- gpuCostMap, clusterAndNameToType3 := buildGPUCostMap(resNodeGPUHourlyCost, gpuCountMap, cp.ParseID)
|
|
|
+ cpuCostMap, clusterAndNameToType1 := buildCPUCostMap(resNodeCPUHourlyCost)
|
|
|
+ ramCostMap, clusterAndNameToType2 := buildRAMCostMap(resNodeRAMHourlyCost)
|
|
|
+ gpuCostMap, clusterAndNameToType3 := buildGPUCostMap(resNodeGPUHourlyCost, gpuCountMap)
|
|
|
|
|
|
clusterAndNameToTypeIntermediate := mergeTypeMaps(clusterAndNameToType1, clusterAndNameToType2)
|
|
|
clusterAndNameToType := mergeTypeMaps(clusterAndNameToTypeIntermediate, clusterAndNameToType3)
|
|
|
|
|
|
- cpuCoresMap := buildCPUCoresMap(resNodeCPUCores, clusterAndNameToType)
|
|
|
+ cpuCoresMap := buildCPUCoresMap(resNodeCPUCores)
|
|
|
|
|
|
ramBytesMap := buildRAMBytesMap(resNodeRAMBytes)
|
|
|
|
|
|
@@ -541,7 +538,7 @@ func ClusterNodes(cp cloud.Provider, client prometheus.Client, duration, offset
|
|
|
ramSystemPctMap := buildRAMSystemPctMap(resNodeRAMSystemPct)
|
|
|
|
|
|
cpuBreakdownMap := buildCPUBreakdownMap(resNodeCPUModeTotal)
|
|
|
- preemptibleMap := buildPreemptibleMap(resIsSpot, cp.ParseID)
|
|
|
+ preemptibleMap := buildPreemptibleMap(resIsSpot)
|
|
|
labelsMap := buildLabelsMap(resLabels)
|
|
|
|
|
|
costTimesMinuteAndCount(activeDataMap, cpuCostMap, cpuCoresMap)
|
|
|
@@ -595,7 +592,7 @@ type LoadBalancer struct {
|
|
|
Minutes float64
|
|
|
}
|
|
|
|
|
|
-func ClusterLoadBalancers(cp cloud.Provider, client prometheus.Client, duration, offset time.Duration) (map[string]*LoadBalancer, error) {
|
|
|
+func ClusterLoadBalancers(client prometheus.Client, duration, offset time.Duration) (map[string]*LoadBalancer, error) {
|
|
|
durationStr := fmt.Sprintf("%dm", int64(duration.Minutes()))
|
|
|
offsetStr := fmt.Sprintf(" offset %dm", int64(offset.Minutes()))
|
|
|
if offset < time.Minute {
|
|
|
@@ -655,7 +652,7 @@ func ClusterLoadBalancers(cp cloud.Provider, client prometheus.Client, duration,
|
|
|
loadBalancerMap[key] = &LoadBalancer{
|
|
|
Cluster: cluster,
|
|
|
Name: namespace + "/" + serviceName,
|
|
|
- ProviderID: cp.ParseLBID(providerID),
|
|
|
+ ProviderID: cloud.ParseLBID(providerID),
|
|
|
}
|
|
|
}
|
|
|
// Fill in Provider ID if it is available and missing in the loadBalancerMap
|
|
|
@@ -702,13 +699,11 @@ func ClusterLoadBalancers(cp cloud.Provider, client prometheus.Client, duration,
|
|
|
}
|
|
|
|
|
|
// ComputeClusterCosts gives the cumulative and monthly-rate cluster costs over a window of time for all clusters.
|
|
|
-func (a *Accesses) ComputeClusterCosts(client prometheus.Client, provider cloud.Provider, window, offset string, withBreakdown bool) (map[string]*ClusterCosts, error) {
|
|
|
+func (a *Accesses) ComputeClusterCosts(client prometheus.Client, provider cloud.Provider, window, offset time.Duration, withBreakdown bool) (map[string]*ClusterCosts, error) {
|
|
|
// Compute number of minutes in the full interval, for use interpolating missed scrapes or scaling missing data
|
|
|
- start, end, err := util.ParseTimeRange(window, offset)
|
|
|
- if err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
- mins := end.Sub(*start).Minutes()
|
|
|
+ start, end := timeutil.ParseTimeRange(window, offset)
|
|
|
+
|
|
|
+ mins := end.Sub(start).Minutes()
|
|
|
|
|
|
// minsPerResolution determines accuracy and resource use for the following
|
|
|
// queries. Smaller values (higher resolution) result in better accuracy,
|
|
|
@@ -777,10 +772,7 @@ func (a *Accesses) ComputeClusterCosts(client prometheus.Client, provider cloud.
|
|
|
queryTotalLocalStorage = fmt.Sprintf(" + %s", queryTotalLocalStorage)
|
|
|
}
|
|
|
|
|
|
- fmtOffset := ""
|
|
|
- if offset != "" {
|
|
|
- fmtOffset = fmt.Sprintf("offset %s", offset)
|
|
|
- }
|
|
|
+ fmtOffset := timeutil.DurationToPromOffsetString(offset)
|
|
|
|
|
|
queryDataCount := fmt.Sprintf(fmtQueryDataCount, env.GetPromClusterLabel(), window, minsPerResolution, fmtOffset, minsPerResolution)
|
|
|
queryTotalGPU := fmt.Sprintf(fmtQueryTotalGPU, window, minsPerResolution, fmtOffset, hourlyToCumulative, env.GetPromClusterLabel())
|
|
|
@@ -1003,7 +995,7 @@ func (a *Accesses) ComputeClusterCosts(client prometheus.Client, provider cloud.
|
|
|
dataMins = mins
|
|
|
klog.V(3).Infof("[Warning] cluster cost data count not found for cluster %s", id)
|
|
|
}
|
|
|
- costs, err := NewClusterCostsFromCumulative(cd["cpu"], cd["gpu"], cd["ram"], cd["storage"]+cd["localstorage"], window, offset, dataMins/util.MinsPerHour)
|
|
|
+ costs, err := NewClusterCostsFromCumulative(cd["cpu"], cd["gpu"], cd["ram"], cd["storage"]+cd["localstorage"], window, offset, dataMins/timeutil.MinsPerHour)
|
|
|
if err != nil {
|
|
|
klog.V(3).Infof("[Warning] Failed to parse cluster costs on %s (%s) from cumulative data: %+v", window, offset, cd)
|
|
|
return nil, err
|
|
|
@@ -1054,8 +1046,8 @@ func resultToTotals(qrs []*prom.QueryResult) ([][]string, error) {
|
|
|
}
|
|
|
|
|
|
// ClusterCostsOverTime gives the full cluster costs over time
|
|
|
-func ClusterCostsOverTime(cli prometheus.Client, provider cloud.Provider, startString, endString, windowString, offset string) (*Totals, error) {
|
|
|
- localStorageQuery := provider.GetLocalStorageQuery(windowString, offset, true, false)
|
|
|
+func ClusterCostsOverTime(cli prometheus.Client, provider cloud.Provider, startString, endString string, window, offset time.Duration) (*Totals, error) {
|
|
|
+ localStorageQuery := provider.GetLocalStorageQuery(window, offset, true, false)
|
|
|
if localStorageQuery != "" {
|
|
|
localStorageQuery = fmt.Sprintf("+ %s", localStorageQuery)
|
|
|
}
|
|
|
@@ -1064,28 +1056,27 @@ func ClusterCostsOverTime(cli prometheus.Client, provider cloud.Provider, startS
|
|
|
|
|
|
start, err := time.Parse(layout, startString)
|
|
|
if err != nil {
|
|
|
- klog.V(1).Infof("Error parsing time " + startString + ". Error: " + err.Error())
|
|
|
+ klog.V(1).Infof("Error parsing time %s. Error: %s", startString, err.Error())
|
|
|
return nil, err
|
|
|
}
|
|
|
end, err := time.Parse(layout, endString)
|
|
|
if err != nil {
|
|
|
- klog.V(1).Infof("Error parsing time " + endString + ". Error: " + err.Error())
|
|
|
+ klog.V(1).Infof("Error parsing time %s. Error: %s", endString, err.Error())
|
|
|
return nil, err
|
|
|
}
|
|
|
- window, err := time.ParseDuration(windowString)
|
|
|
- if err != nil {
|
|
|
- klog.V(1).Infof("Error parsing time " + windowString + ". Error: " + err.Error())
|
|
|
+ fmtWindow := timeutil.DurationString(window)
|
|
|
+
|
|
|
+ if fmtWindow == "" {
|
|
|
+ err := fmt.Errorf("window value invalid or missing")
|
|
|
+ klog.V(1).Infof("Error parsing time %v. Error: %s", window, err.Error())
|
|
|
return nil, err
|
|
|
}
|
|
|
|
|
|
- // turn offsets of the format "[0-9+]h" into the format "offset [0-9+]h" for use in query templatess
|
|
|
- if offset != "" {
|
|
|
- offset = fmt.Sprintf("offset %s", offset)
|
|
|
- }
|
|
|
+ fmtOffset := timeutil.DurationToPromOffsetString(offset)
|
|
|
|
|
|
- qCores := fmt.Sprintf(queryClusterCores, windowString, offset, env.GetPromClusterLabel(), windowString, offset, env.GetPromClusterLabel(), windowString, offset, env.GetPromClusterLabel(), env.GetPromClusterLabel())
|
|
|
- qRAM := fmt.Sprintf(queryClusterRAM, windowString, offset, env.GetPromClusterLabel(), windowString, offset, env.GetPromClusterLabel(), env.GetPromClusterLabel())
|
|
|
- qStorage := fmt.Sprintf(queryStorage, windowString, offset, env.GetPromClusterLabel(), windowString, offset, env.GetPromClusterLabel(), env.GetPromClusterLabel(), localStorageQuery)
|
|
|
+ qCores := fmt.Sprintf(queryClusterCores, fmtWindow, fmtOffset, env.GetPromClusterLabel(), fmtWindow, fmtOffset, env.GetPromClusterLabel(), fmtWindow, fmtOffset, env.GetPromClusterLabel(), env.GetPromClusterLabel())
|
|
|
+ qRAM := fmt.Sprintf(queryClusterRAM, fmtWindow, fmtOffset, env.GetPromClusterLabel(), fmtWindow, fmtOffset, env.GetPromClusterLabel(), env.GetPromClusterLabel())
|
|
|
+ qStorage := fmt.Sprintf(queryStorage, fmtWindow, fmtOffset, env.GetPromClusterLabel(), fmtWindow, fmtOffset, env.GetPromClusterLabel(), env.GetPromClusterLabel(), localStorageQuery)
|
|
|
qTotal := fmt.Sprintf(queryTotal, env.GetPromClusterLabel(), env.GetPromClusterLabel(), env.GetPromClusterLabel(), env.GetPromClusterLabel(), localStorageQuery)
|
|
|
|
|
|
ctx := prom.NewContext(cli)
|