|
|
@@ -118,12 +118,17 @@ type Disk struct {
|
|
|
Breakdown *ClusterCostsBreakdown
|
|
|
}
|
|
|
|
|
|
-func ClusterDisks(client prometheus.Client, provider cloud.Provider, duration, offset time.Duration) (map[string]*Disk, error) {
|
|
|
- durationStr := fmt.Sprintf("%dm", int64(duration.Minutes()))
|
|
|
- offsetStr := fmt.Sprintf(" offset %dm", int64(offset.Minutes()))
|
|
|
- if offset < time.Minute {
|
|
|
- offsetStr = ""
|
|
|
- }
|
|
|
+type DiskIdentifier struct {
|
|
|
+ Cluster string
|
|
|
+ Name string
|
|
|
+}
|
|
|
+
|
|
|
+func ClusterDisks(client prometheus.Client, provider cloud.Provider, start, end time.Time) (map[DiskIdentifier]*Disk, error) {
|
|
|
+ // Query for the duration between start and end
|
|
|
+ durStr := timeutil.DurationString(end.Sub(start))
|
|
|
+
|
|
|
+ // Start from the time "end", querying backwards
|
|
|
+ t := end
|
|
|
|
|
|
// minsPerResolution determines accuracy and resource use for the following
|
|
|
// queries. Smaller values (higher resolution) result in better accuracy,
|
|
|
@@ -140,22 +145,22 @@ func ClusterDisks(client prometheus.Client, provider cloud.Provider, duration, o
|
|
|
costPerGBHr := 0.04 / 730.0
|
|
|
|
|
|
ctx := prom.NewNamedContext(client, prom.ClusterContextName)
|
|
|
- queryPVCost := fmt.Sprintf(`avg(avg_over_time(pv_hourly_cost[%s]%s)) by (%s, persistentvolume,provider_id)`, durationStr, offsetStr, env.GetPromClusterLabel())
|
|
|
- queryPVSize := fmt.Sprintf(`avg(avg_over_time(kube_persistentvolume_capacity_bytes[%s]%s)) by (%s, persistentvolume)`, durationStr, offsetStr, env.GetPromClusterLabel())
|
|
|
- queryActiveMins := fmt.Sprintf(`count(pv_hourly_cost) by (%s, persistentvolume)[%s:%dm]%s`, env.GetPromClusterLabel(), durationStr, minsPerResolution, offsetStr)
|
|
|
-
|
|
|
- queryLocalStorageCost := fmt.Sprintf(`sum_over_time(sum(container_fs_limit_bytes{device!="tmpfs", id="/"}) by (instance, %s)[%s:%dm]%s) / 1024 / 1024 / 1024 * %f * %f`, env.GetPromClusterLabel(), durationStr, minsPerResolution, offsetStr, hourlyToCumulative, costPerGBHr)
|
|
|
- queryLocalStorageUsedCost := fmt.Sprintf(`sum_over_time(sum(container_fs_usage_bytes{device!="tmpfs", id="/"}) by (instance, %s)[%s:%dm]%s) / 1024 / 1024 / 1024 * %f * %f`, env.GetPromClusterLabel(), durationStr, minsPerResolution, offsetStr, hourlyToCumulative, costPerGBHr)
|
|
|
- queryLocalStorageBytes := fmt.Sprintf(`avg_over_time(sum(container_fs_limit_bytes{device!="tmpfs", id="/"}) by (instance, %s)[%s:%dm]%s)`, env.GetPromClusterLabel(), durationStr, minsPerResolution, offsetStr)
|
|
|
- queryLocalActiveMins := fmt.Sprintf(`count(node_total_hourly_cost) by (%s, node)[%s:%dm]%s`, env.GetPromClusterLabel(), durationStr, minsPerResolution, offsetStr)
|
|
|
-
|
|
|
- resChPVCost := ctx.Query(queryPVCost)
|
|
|
- resChPVSize := ctx.Query(queryPVSize)
|
|
|
- resChActiveMins := ctx.Query(queryActiveMins)
|
|
|
- resChLocalStorageCost := ctx.Query(queryLocalStorageCost)
|
|
|
- resChLocalStorageUsedCost := ctx.Query(queryLocalStorageUsedCost)
|
|
|
- resChLocalStorageBytes := ctx.Query(queryLocalStorageBytes)
|
|
|
- resChLocalActiveMins := ctx.Query(queryLocalActiveMins)
|
|
|
+ queryPVCost := fmt.Sprintf(`avg(avg_over_time(pv_hourly_cost[%s])) by (%s, persistentvolume,provider_id)`, durStr, env.GetPromClusterLabel())
|
|
|
+ queryPVSize := fmt.Sprintf(`avg(avg_over_time(kube_persistentvolume_capacity_bytes[%s])) by (%s, persistentvolume)`, durStr, env.GetPromClusterLabel())
|
|
|
+ queryActiveMins := fmt.Sprintf(`count(pv_hourly_cost) by (%s, persistentvolume)[%s:%dm]`, env.GetPromClusterLabel(), durStr, minsPerResolution)
|
|
|
+
|
|
|
+ queryLocalStorageCost := fmt.Sprintf(`sum_over_time(sum(container_fs_limit_bytes{device!="tmpfs", id="/"}) by (instance, %s)[%s:%dm]) / 1024 / 1024 / 1024 * %f * %f`, env.GetPromClusterLabel(), durStr, minsPerResolution, hourlyToCumulative, costPerGBHr)
|
|
|
+ queryLocalStorageUsedCost := fmt.Sprintf(`sum_over_time(sum(container_fs_usage_bytes{device!="tmpfs", id="/"}) by (instance, %s)[%s:%dm]) / 1024 / 1024 / 1024 * %f * %f`, env.GetPromClusterLabel(), durStr, minsPerResolution, hourlyToCumulative, costPerGBHr)
|
|
|
+ queryLocalStorageBytes := fmt.Sprintf(`avg_over_time(sum(container_fs_limit_bytes{device!="tmpfs", id="/"}) by (instance, %s)[%s:%dm])`, env.GetPromClusterLabel(), durStr, minsPerResolution)
|
|
|
+ queryLocalActiveMins := fmt.Sprintf(`count(node_total_hourly_cost) by (%s, node)[%s:%dm]`, env.GetPromClusterLabel(), durStr, minsPerResolution)
|
|
|
+
|
|
|
+ resChPVCost := ctx.QueryAtTime(queryPVCost, t)
|
|
|
+ resChPVSize := ctx.QueryAtTime(queryPVSize, t)
|
|
|
+ resChActiveMins := ctx.QueryAtTime(queryActiveMins, t)
|
|
|
+ resChLocalStorageCost := ctx.QueryAtTime(queryLocalStorageCost, t)
|
|
|
+ resChLocalStorageUsedCost := ctx.QueryAtTime(queryLocalStorageUsedCost, t)
|
|
|
+ resChLocalStorageBytes := ctx.QueryAtTime(queryLocalStorageBytes, t)
|
|
|
+ resChLocalActiveMins := ctx.QueryAtTime(queryLocalActiveMins, t)
|
|
|
|
|
|
resPVCost, _ := resChPVCost.Await()
|
|
|
resPVSize, _ := resChPVSize.Await()
|
|
|
@@ -168,7 +173,7 @@ func ClusterDisks(client prometheus.Client, provider cloud.Provider, duration, o
|
|
|
return nil, ctx.ErrorCollection()
|
|
|
}
|
|
|
|
|
|
- diskMap := map[string]*Disk{}
|
|
|
+ diskMap := map[DiskIdentifier]*Disk{}
|
|
|
|
|
|
pvCosts(diskMap, resolution, resActiveMins, resPVSize, resPVCost, provider)
|
|
|
|
|
|
@@ -185,7 +190,7 @@ func ClusterDisks(client prometheus.Client, provider cloud.Provider, duration, o
|
|
|
}
|
|
|
|
|
|
cost := result.Values[0].Value
|
|
|
- key := fmt.Sprintf("%s/%s", cluster, name)
|
|
|
+ key := DiskIdentifier{cluster, name}
|
|
|
if _, ok := diskMap[key]; !ok {
|
|
|
diskMap[key] = &Disk{
|
|
|
Cluster: cluster,
|
|
|
@@ -210,7 +215,7 @@ func ClusterDisks(client prometheus.Client, provider cloud.Provider, duration, o
|
|
|
}
|
|
|
|
|
|
cost := result.Values[0].Value
|
|
|
- key := fmt.Sprintf("%s/%s", cluster, name)
|
|
|
+ key := DiskIdentifier{cluster, name}
|
|
|
if _, ok := diskMap[key]; !ok {
|
|
|
diskMap[key] = &Disk{
|
|
|
Cluster: cluster,
|
|
|
@@ -235,7 +240,7 @@ func ClusterDisks(client prometheus.Client, provider cloud.Provider, duration, o
|
|
|
}
|
|
|
|
|
|
bytes := result.Values[0].Value
|
|
|
- key := fmt.Sprintf("%s/%s", cluster, name)
|
|
|
+ key := DiskIdentifier{cluster, name}
|
|
|
if _, ok := diskMap[key]; !ok {
|
|
|
diskMap[key] = &Disk{
|
|
|
Cluster: cluster,
|
|
|
@@ -263,7 +268,7 @@ func ClusterDisks(client prometheus.Client, provider cloud.Provider, duration, o
|
|
|
continue
|
|
|
}
|
|
|
|
|
|
- key := fmt.Sprintf("%s/%s", cluster, name)
|
|
|
+ key := DiskIdentifier{cluster, name}
|
|
|
if _, ok := diskMap[key]; !ok {
|
|
|
log.DedupedWarningf(5, "ClusterDisks: local active mins for unidentified disk or disk deleted from analysis")
|
|
|
continue
|
|
|
@@ -274,7 +279,7 @@ func ClusterDisks(client prometheus.Client, provider cloud.Provider, duration, o
|
|
|
}
|
|
|
|
|
|
s := time.Unix(int64(result.Values[0].Timestamp), 0)
|
|
|
- e := time.Unix(int64(result.Values[len(result.Values)-1].Timestamp), 0).Add(resolution)
|
|
|
+ e := time.Unix(int64(result.Values[len(result.Values)-1].Timestamp), 0)
|
|
|
mins := e.Sub(s).Minutes()
|
|
|
|
|
|
// TODO niko/assets if mins >= threshold, interpolate for missing data?
|
|
|
@@ -369,12 +374,12 @@ func costTimesMinute(activeDataMap map[NodeIdentifier]activeData, costMap map[No
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-func ClusterNodes(cp cloud.Provider, client prometheus.Client, duration, offset time.Duration) (map[NodeIdentifier]*Node, error) {
|
|
|
- durationStr := fmt.Sprintf("%dm", int64(duration.Minutes()))
|
|
|
- offsetStr := fmt.Sprintf(" offset %dm", int64(offset.Minutes()))
|
|
|
- if offset < time.Minute {
|
|
|
- offsetStr = ""
|
|
|
- }
|
|
|
+func ClusterNodes(cp cloud.Provider, client prometheus.Client, start, end time.Time) (map[NodeIdentifier]*Node, error) {
|
|
|
+ // Query for the duration between start and end
|
|
|
+ durStr := timeutil.DurationString(end.Sub(start))
|
|
|
+
|
|
|
+ // Start from the time "end", querying backwards
|
|
|
+ t := end
|
|
|
|
|
|
// minsPerResolution determines accuracy and resource use for the following
|
|
|
// queries. Smaller values (higher resolution) result in better accuracy,
|
|
|
@@ -385,34 +390,34 @@ func ClusterNodes(cp cloud.Provider, client prometheus.Client, duration, offset
|
|
|
requiredCtx := prom.NewNamedContext(client, prom.ClusterContextName)
|
|
|
optionalCtx := prom.NewNamedContext(client, prom.ClusterOptionalContextName)
|
|
|
|
|
|
- queryNodeCPUHourlyCost := fmt.Sprintf(`avg(avg_over_time(node_cpu_hourly_cost[%s]%s)) by (%s, node, instance_type, provider_id)`, durationStr, offsetStr, env.GetPromClusterLabel())
|
|
|
- queryNodeCPUCores := fmt.Sprintf(`avg(avg_over_time(kube_node_status_capacity_cpu_cores[%s]%s)) by (%s, node)`, durationStr, offsetStr, env.GetPromClusterLabel())
|
|
|
- queryNodeRAMHourlyCost := fmt.Sprintf(`avg(avg_over_time(node_ram_hourly_cost[%s]%s)) by (%s, node, instance_type, provider_id) / 1024 / 1024 / 1024`, durationStr, offsetStr, env.GetPromClusterLabel())
|
|
|
- queryNodeRAMBytes := fmt.Sprintf(`avg(avg_over_time(kube_node_status_capacity_memory_bytes[%s]%s)) by (%s, node)`, durationStr, offsetStr, env.GetPromClusterLabel())
|
|
|
- queryNodeGPUCount := fmt.Sprintf(`avg(avg_over_time(node_gpu_count[%s]%s)) by (%s, node, provider_id)`, durationStr, offsetStr, env.GetPromClusterLabel())
|
|
|
- queryNodeGPUHourlyCost := fmt.Sprintf(`avg(avg_over_time(node_gpu_hourly_cost[%s]%s)) by (%s, node, instance_type, provider_id)`, durationStr, offsetStr, env.GetPromClusterLabel())
|
|
|
- queryNodeCPUModeTotal := fmt.Sprintf(`sum(rate(node_cpu_seconds_total[%s:%dm]%s)) by (kubernetes_node, %s, mode)`, durationStr, minsPerResolution, offsetStr, env.GetPromClusterLabel())
|
|
|
- queryNodeRAMSystemPct := fmt.Sprintf(`sum(sum_over_time(container_memory_working_set_bytes{container_name!="POD",container_name!="",namespace="kube-system"}[%s:%dm]%s)) by (instance, %s) / avg(label_replace(sum(sum_over_time(kube_node_status_capacity_memory_bytes[%s:%dm]%s)) by (node, %s), "instance", "$1", "node", "(.*)")) by (instance, %s)`, durationStr, minsPerResolution, offsetStr, env.GetPromClusterLabel(), durationStr, minsPerResolution, offsetStr, env.GetPromClusterLabel(), env.GetPromClusterLabel())
|
|
|
- queryNodeRAMUserPct := fmt.Sprintf(`sum(sum_over_time(container_memory_working_set_bytes{container_name!="POD",container_name!="",namespace!="kube-system"}[%s:%dm]%s)) by (instance, %s) / avg(label_replace(sum(sum_over_time(kube_node_status_capacity_memory_bytes[%s:%dm]%s)) by (node, %s), "instance", "$1", "node", "(.*)")) by (instance, %s)`, durationStr, minsPerResolution, offsetStr, env.GetPromClusterLabel(), durationStr, minsPerResolution, offsetStr, env.GetPromClusterLabel(), env.GetPromClusterLabel())
|
|
|
- queryActiveMins := fmt.Sprintf(`avg(node_total_hourly_cost) by (node, %s, provider_id)[%s:%dm]%s`, env.GetPromClusterLabel(), durationStr, minsPerResolution, offsetStr)
|
|
|
- queryIsSpot := fmt.Sprintf(`avg_over_time(kubecost_node_is_spot[%s:%dm]%s)`, durationStr, minsPerResolution, offsetStr)
|
|
|
- queryLabels := fmt.Sprintf(`count_over_time(kube_node_labels[%s:%dm]%s)`, durationStr, minsPerResolution, offsetStr)
|
|
|
+ queryNodeCPUHourlyCost := fmt.Sprintf(`avg(avg_over_time(node_cpu_hourly_cost[%s])) by (%s, node, instance_type, provider_id)`, durStr, env.GetPromClusterLabel())
|
|
|
+ queryNodeCPUCores := fmt.Sprintf(`avg(avg_over_time(kube_node_status_capacity_cpu_cores[%s])) by (%s, node)`, durStr, env.GetPromClusterLabel())
|
|
|
+ queryNodeRAMHourlyCost := fmt.Sprintf(`avg(avg_over_time(node_ram_hourly_cost[%s])) by (%s, node, instance_type, provider_id) / 1024 / 1024 / 1024`, durStr, env.GetPromClusterLabel())
|
|
|
+ queryNodeRAMBytes := fmt.Sprintf(`avg(avg_over_time(kube_node_status_capacity_memory_bytes[%s])) by (%s, node)`, durStr, env.GetPromClusterLabel())
|
|
|
+ queryNodeGPUCount := fmt.Sprintf(`avg(avg_over_time(node_gpu_count[%s])) by (%s, node, provider_id)`, durStr, env.GetPromClusterLabel())
|
|
|
+ queryNodeGPUHourlyCost := fmt.Sprintf(`avg(avg_over_time(node_gpu_hourly_cost[%s])) by (%s, node, instance_type, provider_id)`, durStr, env.GetPromClusterLabel())
|
|
|
+ queryNodeCPUModeTotal := fmt.Sprintf(`sum(rate(node_cpu_seconds_total[%s:%dm])) by (kubernetes_node, %s, mode)`, durStr, minsPerResolution, env.GetPromClusterLabel())
|
|
|
+ queryNodeRAMSystemPct := fmt.Sprintf(`sum(sum_over_time(container_memory_working_set_bytes{container_name!="POD",container_name!="",namespace="kube-system"}[%s:%dm])) by (instance, %s) / avg(label_replace(sum(sum_over_time(kube_node_status_capacity_memory_bytes[%s:%dm])) by (node, %s), "instance", "$1", "node", "(.*)")) by (instance, %s)`, durStr, minsPerResolution, env.GetPromClusterLabel(), durStr, minsPerResolution, env.GetPromClusterLabel(), env.GetPromClusterLabel())
|
|
|
+ queryNodeRAMUserPct := fmt.Sprintf(`sum(sum_over_time(container_memory_working_set_bytes{container_name!="POD",container_name!="",namespace!="kube-system"}[%s:%dm])) by (instance, %s) / avg(label_replace(sum(sum_over_time(kube_node_status_capacity_memory_bytes[%s:%dm])) by (node, %s), "instance", "$1", "node", "(.*)")) by (instance, %s)`, durStr, minsPerResolution, env.GetPromClusterLabel(), durStr, minsPerResolution, env.GetPromClusterLabel(), env.GetPromClusterLabel())
|
|
|
+ queryActiveMins := fmt.Sprintf(`avg(node_total_hourly_cost) by (node, %s, provider_id)[%s:%dm]`, env.GetPromClusterLabel(), durStr, minsPerResolution)
|
|
|
+ queryIsSpot := fmt.Sprintf(`avg_over_time(kubecost_node_is_spot[%s:%dm])`, durStr, minsPerResolution)
|
|
|
+ queryLabels := fmt.Sprintf(`count_over_time(kube_node_labels[%s:%dm])`, durStr, minsPerResolution)
|
|
|
|
|
|
// Return errors if these fail
|
|
|
- resChNodeCPUHourlyCost := requiredCtx.Query(queryNodeCPUHourlyCost)
|
|
|
- resChNodeCPUCores := requiredCtx.Query(queryNodeCPUCores)
|
|
|
- resChNodeRAMHourlyCost := requiredCtx.Query(queryNodeRAMHourlyCost)
|
|
|
- resChNodeRAMBytes := requiredCtx.Query(queryNodeRAMBytes)
|
|
|
- resChNodeGPUCount := requiredCtx.Query(queryNodeGPUCount)
|
|
|
- resChNodeGPUHourlyCost := requiredCtx.Query(queryNodeGPUHourlyCost)
|
|
|
- resChActiveMins := requiredCtx.Query(queryActiveMins)
|
|
|
- resChIsSpot := requiredCtx.Query(queryIsSpot)
|
|
|
+ resChNodeCPUHourlyCost := requiredCtx.QueryAtTime(queryNodeCPUHourlyCost, t)
|
|
|
+ resChNodeCPUCores := requiredCtx.QueryAtTime(queryNodeCPUCores, t)
|
|
|
+ resChNodeRAMHourlyCost := requiredCtx.QueryAtTime(queryNodeRAMHourlyCost, t)
|
|
|
+ resChNodeRAMBytes := requiredCtx.QueryAtTime(queryNodeRAMBytes, t)
|
|
|
+ resChNodeGPUCount := requiredCtx.QueryAtTime(queryNodeGPUCount, t)
|
|
|
+ resChNodeGPUHourlyCost := requiredCtx.QueryAtTime(queryNodeGPUHourlyCost, t)
|
|
|
+ resChActiveMins := requiredCtx.QueryAtTime(queryActiveMins, t)
|
|
|
+ resChIsSpot := requiredCtx.QueryAtTime(queryIsSpot, t)
|
|
|
|
|
|
// Do not return errors if these fail, but log warnings
|
|
|
- resChNodeCPUModeTotal := optionalCtx.Query(queryNodeCPUModeTotal)
|
|
|
- resChNodeRAMSystemPct := optionalCtx.Query(queryNodeRAMSystemPct)
|
|
|
- resChNodeRAMUserPct := optionalCtx.Query(queryNodeRAMUserPct)
|
|
|
- resChLabels := optionalCtx.Query(queryLabels)
|
|
|
+ resChNodeCPUModeTotal := optionalCtx.QueryAtTime(queryNodeCPUModeTotal, t)
|
|
|
+ resChNodeRAMSystemPct := optionalCtx.QueryAtTime(queryNodeRAMSystemPct, t)
|
|
|
+ resChNodeRAMUserPct := optionalCtx.QueryAtTime(queryNodeRAMUserPct, t)
|
|
|
+ resChLabels := optionalCtx.QueryAtTime(queryLabels, t)
|
|
|
|
|
|
resNodeCPUHourlyCost, _ := resChNodeCPUHourlyCost.Await()
|
|
|
resNodeCPUCores, _ := resChNodeCPUCores.Await()
|
|
|
@@ -475,6 +480,7 @@ func ClusterNodes(cp cloud.Provider, client prometheus.Client, duration, offset
|
|
|
preemptibleMap,
|
|
|
labelsMap,
|
|
|
clusterAndNameToType,
|
|
|
+ resolution,
|
|
|
)
|
|
|
|
|
|
c, err := cp.GetConfig()
|
|
|
@@ -504,38 +510,42 @@ func ClusterNodes(cp cloud.Provider, client prometheus.Client, duration, offset
|
|
|
return nodeMap, nil
|
|
|
}
|
|
|
|
|
|
+type LoadBalancerIdentifier struct {
|
|
|
+ Cluster string
|
|
|
+ Namespace string
|
|
|
+ Name string
|
|
|
+}
|
|
|
+
|
|
|
type LoadBalancer struct {
|
|
|
Cluster string
|
|
|
+ Namespace string
|
|
|
Name string
|
|
|
ProviderID string
|
|
|
Cost float64
|
|
|
Start time.Time
|
|
|
+ End time.Time
|
|
|
Minutes float64
|
|
|
}
|
|
|
|
|
|
-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 {
|
|
|
- offsetStr = ""
|
|
|
- }
|
|
|
+func ClusterLoadBalancers(client prometheus.Client, start, end time.Time) (map[LoadBalancerIdentifier]*LoadBalancer, error) {
|
|
|
+ // Query for the duration between start and end
|
|
|
+ durStr := timeutil.DurationString(end.Sub(start))
|
|
|
+
|
|
|
+ // Start from the time "end", querying backwards
|
|
|
+ t := end
|
|
|
|
|
|
// minsPerResolution determines accuracy and resource use for the following
|
|
|
// queries. Smaller values (higher resolution) result in better accuracy,
|
|
|
// but more expensive queries, and vice-a-versa.
|
|
|
- minsPerResolution := 5
|
|
|
-
|
|
|
- // hourlyToCumulative is a scaling factor that, when multiplied by an hourly
|
|
|
- // value, converts it to a cumulative value; i.e.
|
|
|
- // [$/hr] * [min/res]*[hr/min] = [$/res]
|
|
|
- hourlyToCumulative := float64(minsPerResolution) * (1.0 / 60.0)
|
|
|
+ minsPerResolution := 1
|
|
|
|
|
|
ctx := prom.NewNamedContext(client, prom.ClusterContextName)
|
|
|
- queryLBCost := fmt.Sprintf(`sum_over_time((avg(kubecost_load_balancer_cost) by (namespace, service_name, %s, ingress_ip))[%s:%dm]%s) * %f`, env.GetPromClusterLabel(), durationStr, minsPerResolution, offsetStr, hourlyToCumulative)
|
|
|
- queryActiveMins := fmt.Sprintf(`count(kubecost_load_balancer_cost) by (namespace, service_name, %s, ingress_ip)[%s:%dm]%s`, env.GetPromClusterLabel(), durationStr, minsPerResolution, offsetStr)
|
|
|
|
|
|
- resChLBCost := ctx.Query(queryLBCost)
|
|
|
- resChActiveMins := ctx.Query(queryActiveMins)
|
|
|
+ queryLBCost := fmt.Sprintf(`avg(avg_over_time(kubecost_load_balancer_cost[%s])) by (namespace, service_name, %s, ingress_ip)`, durStr, env.GetPromClusterLabel())
|
|
|
+ queryActiveMins := fmt.Sprintf(`avg(kubecost_load_balancer_cost) by (namespace, service_name, %s, ingress_ip)[%s:%dm]`, env.GetPromClusterLabel(), durStr, minsPerResolution)
|
|
|
+
|
|
|
+ resChLBCost := ctx.QueryAtTime(queryLBCost, t)
|
|
|
+ resChActiveMins := ctx.QueryAtTime(queryActiveMins, t)
|
|
|
|
|
|
resLBCost, _ := resChLBCost.Await()
|
|
|
resActiveMins, _ := resChActiveMins.Await()
|
|
|
@@ -544,9 +554,9 @@ func ClusterLoadBalancers(client prometheus.Client, duration, offset time.Durati
|
|
|
return nil, ctx.ErrorCollection()
|
|
|
}
|
|
|
|
|
|
- loadBalancerMap := map[string]*LoadBalancer{}
|
|
|
+ loadBalancerMap := make(map[LoadBalancerIdentifier]*LoadBalancer, len(resActiveMins))
|
|
|
|
|
|
- for _, result := range resLBCost {
|
|
|
+ for _, result := range resActiveMins {
|
|
|
cluster, err := result.GetString(env.GetPromClusterLabel())
|
|
|
if err != nil {
|
|
|
cluster = env.GetClusterID()
|
|
|
@@ -556,7 +566,7 @@ func ClusterLoadBalancers(client prometheus.Client, duration, offset time.Durati
|
|
|
log.Warningf("ClusterLoadBalancers: LB cost data missing namespace")
|
|
|
continue
|
|
|
}
|
|
|
- serviceName, err := result.GetString("service_name")
|
|
|
+ name, err := result.GetString("service_name")
|
|
|
if err != nil {
|
|
|
log.Warningf("ClusterLoadBalancers: LB cost data missing service_name")
|
|
|
continue
|
|
|
@@ -566,25 +576,43 @@ func ClusterLoadBalancers(client prometheus.Client, duration, offset time.Durati
|
|
|
log.DedupedWarningf(5, "ClusterLoadBalancers: LB cost data missing ingress_ip")
|
|
|
providerID = ""
|
|
|
}
|
|
|
- lbCost := result.Values[0].Value
|
|
|
|
|
|
- key := fmt.Sprintf("%s/%s/%s", cluster, namespace, serviceName)
|
|
|
+ key := LoadBalancerIdentifier{
|
|
|
+ Cluster: cluster,
|
|
|
+ Namespace: namespace,
|
|
|
+ Name: name,
|
|
|
+ }
|
|
|
+
|
|
|
+ // Skip if there are no data
|
|
|
+ if len(result.Values) == 0 {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ // Add load balancer to the set of load balancers
|
|
|
if _, ok := loadBalancerMap[key]; !ok {
|
|
|
loadBalancerMap[key] = &LoadBalancer{
|
|
|
Cluster: cluster,
|
|
|
- Name: namespace + "/" + serviceName,
|
|
|
+ Namespace: namespace,
|
|
|
+ Name: fmt.Sprintf("%s/%s", namespace, name), // TODO:ETL this is kept for backwards-compatibility, but not good
|
|
|
ProviderID: cloud.ParseLBID(providerID),
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // Append start, end, and minutes. This should come before all other data.
|
|
|
+ s := time.Unix(int64(result.Values[0].Timestamp), 0)
|
|
|
+ e := time.Unix(int64(result.Values[len(result.Values)-1].Timestamp), 0)
|
|
|
+ loadBalancerMap[key].Start = s
|
|
|
+ loadBalancerMap[key].End = e
|
|
|
+ loadBalancerMap[key].Minutes = e.Sub(s).Minutes()
|
|
|
+
|
|
|
// Fill in Provider ID if it is available and missing in the loadBalancerMap
|
|
|
// Prevents there from being a duplicate LoadBalancers on the same day
|
|
|
if providerID != "" && loadBalancerMap[key].ProviderID == "" {
|
|
|
loadBalancerMap[key].ProviderID = providerID
|
|
|
}
|
|
|
- loadBalancerMap[key].Cost += lbCost
|
|
|
}
|
|
|
|
|
|
- for _, result := range resActiveMins {
|
|
|
+ for _, result := range resLBCost {
|
|
|
cluster, err := result.GetString(env.GetPromClusterLabel())
|
|
|
if err != nil {
|
|
|
cluster = env.GetClusterID()
|
|
|
@@ -594,28 +622,28 @@ func ClusterLoadBalancers(client prometheus.Client, duration, offset time.Durati
|
|
|
log.Warningf("ClusterLoadBalancers: LB cost data missing namespace")
|
|
|
continue
|
|
|
}
|
|
|
- serviceName, err := result.GetString("service_name")
|
|
|
+ name, err := result.GetString("service_name")
|
|
|
if err != nil {
|
|
|
log.Warningf("ClusterLoadBalancers: LB cost data missing service_name")
|
|
|
continue
|
|
|
}
|
|
|
- key := fmt.Sprintf("%s/%s/%s", cluster, namespace, serviceName)
|
|
|
|
|
|
- if len(result.Values) == 0 {
|
|
|
- continue
|
|
|
+ key := LoadBalancerIdentifier{
|
|
|
+ Cluster: cluster,
|
|
|
+ Namespace: namespace,
|
|
|
+ Name: name,
|
|
|
}
|
|
|
|
|
|
+ // Apply cost as price-per-hour * hours
|
|
|
if lb, ok := loadBalancerMap[key]; ok {
|
|
|
- s := time.Unix(int64(result.Values[0].Timestamp), 0)
|
|
|
- e := time.Unix(int64(result.Values[len(result.Values)-1].Timestamp), 0)
|
|
|
- mins := e.Sub(s).Minutes()
|
|
|
-
|
|
|
- lb.Start = s
|
|
|
- lb.Minutes = mins
|
|
|
+ lbPricePerHr := result.Values[0].Value
|
|
|
+ hrs := lb.Minutes / 60.0
|
|
|
+ lb.Cost += lbPricePerHr * hrs
|
|
|
} else {
|
|
|
log.DedupedWarningf(20, "ClusterLoadBalancers: found minutes for key that does not exist: %s", key)
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
return loadBalancerMap, nil
|
|
|
}
|
|
|
|
|
|
@@ -1079,7 +1107,7 @@ func ClusterCostsOverTime(cli prometheus.Client, provider cloud.Provider, startS
|
|
|
}, nil
|
|
|
}
|
|
|
|
|
|
-func pvCosts(diskMap map[string]*Disk, resolution time.Duration, resActiveMins, resPVSize, resPVCost []*prom.QueryResult, cp cloud.Provider) {
|
|
|
+func pvCosts(diskMap map[DiskIdentifier]*Disk, resolution time.Duration, resActiveMins, resPVSize, resPVCost []*prom.QueryResult, cp cloud.Provider) {
|
|
|
for _, result := range resActiveMins {
|
|
|
cluster, err := result.GetString(env.GetPromClusterLabel())
|
|
|
if err != nil {
|
|
|
@@ -1096,7 +1124,7 @@ func pvCosts(diskMap map[string]*Disk, resolution time.Duration, resActiveMins,
|
|
|
continue
|
|
|
}
|
|
|
|
|
|
- key := fmt.Sprintf("%s/%s", cluster, name)
|
|
|
+ key := DiskIdentifier{cluster, name}
|
|
|
if _, ok := diskMap[key]; !ok {
|
|
|
diskMap[key] = &Disk{
|
|
|
Cluster: cluster,
|
|
|
@@ -1105,7 +1133,7 @@ func pvCosts(diskMap map[string]*Disk, resolution time.Duration, resActiveMins,
|
|
|
}
|
|
|
}
|
|
|
s := time.Unix(int64(result.Values[0].Timestamp), 0)
|
|
|
- e := time.Unix(int64(result.Values[len(result.Values)-1].Timestamp), 0).Add(resolution)
|
|
|
+ e := time.Unix(int64(result.Values[len(result.Values)-1].Timestamp), 0)
|
|
|
mins := e.Sub(s).Minutes()
|
|
|
|
|
|
// TODO niko/assets if mins >= threshold, interpolate for missing data?
|
|
|
@@ -1130,7 +1158,7 @@ func pvCosts(diskMap map[string]*Disk, resolution time.Duration, resActiveMins,
|
|
|
// TODO niko/assets storage class
|
|
|
|
|
|
bytes := result.Values[0].Value
|
|
|
- key := fmt.Sprintf("%s/%s", cluster, name)
|
|
|
+ key := DiskIdentifier{cluster, name}
|
|
|
if _, ok := diskMap[key]; !ok {
|
|
|
diskMap[key] = &Disk{
|
|
|
Cluster: cluster,
|
|
|
@@ -1162,9 +1190,7 @@ func pvCosts(diskMap map[string]*Disk, resolution time.Duration, resActiveMins,
|
|
|
// TODO niko/assets storage class
|
|
|
|
|
|
var cost float64
|
|
|
-
|
|
|
if customPricingEnabled && customPricingConfig != nil {
|
|
|
-
|
|
|
customPVCostStr := customPricingConfig.Storage
|
|
|
|
|
|
customPVCost, err := strconv.ParseFloat(customPVCostStr, 64)
|
|
|
@@ -1173,14 +1199,11 @@ func pvCosts(diskMap map[string]*Disk, resolution time.Duration, resActiveMins,
|
|
|
}
|
|
|
|
|
|
cost = customPVCost
|
|
|
-
|
|
|
} else {
|
|
|
-
|
|
|
cost = result.Values[0].Value
|
|
|
-
|
|
|
}
|
|
|
|
|
|
- key := fmt.Sprintf("%s/%s", cluster, name)
|
|
|
+ key := DiskIdentifier{cluster, name}
|
|
|
if _, ok := diskMap[key]; !ok {
|
|
|
diskMap[key] = &Disk{
|
|
|
Cluster: cluster,
|