|
|
@@ -10,10 +10,10 @@ import (
|
|
|
|
|
|
costAnalyzerCloud "github.com/kubecost/cost-model/pkg/cloud"
|
|
|
"github.com/kubecost/cost-model/pkg/clustercache"
|
|
|
+ "github.com/kubecost/cost-model/pkg/costmodel/clusters"
|
|
|
"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/thanos"
|
|
|
"github.com/kubecost/cost-model/pkg/util"
|
|
|
prometheusClient "github.com/prometheus/client_golang/api"
|
|
|
v1 "k8s.io/api/core/v1"
|
|
|
@@ -48,15 +48,17 @@ var isCron = regexp.MustCompile(`^(.+)-\d{10}$`)
|
|
|
|
|
|
type CostModel struct {
|
|
|
Cache clustercache.ClusterCache
|
|
|
+ ClusterMap clusters.ClusterMap
|
|
|
RequestGroup *singleflight.Group
|
|
|
}
|
|
|
|
|
|
-func NewCostModel(cache clustercache.ClusterCache) *CostModel {
|
|
|
+func NewCostModel(cache clustercache.ClusterCache, clusterMap clusters.ClusterMap) *CostModel {
|
|
|
// request grouping to prevent over-requesting the same data prior to caching
|
|
|
requestGroup := new(singleflight.Group)
|
|
|
|
|
|
return &CostModel{
|
|
|
Cache: cache,
|
|
|
+ ClusterMap: clusterMap,
|
|
|
RequestGroup: requestGroup,
|
|
|
}
|
|
|
}
|
|
|
@@ -223,58 +225,6 @@ const (
|
|
|
kubecostUpMinsPerHourStr = `max(count_over_time(node_cpu_hourly_cost[%s:1m])) / %f`
|
|
|
)
|
|
|
|
|
|
-type PrometheusMetadata struct {
|
|
|
- Running bool `json:"running"`
|
|
|
- KubecostDataExists bool `json:"kubecostDataExists"`
|
|
|
-}
|
|
|
-
|
|
|
-// ValidatePrometheus tells the model what data prometheus has on it.
|
|
|
-func ValidatePrometheus(cli prometheusClient.Client, isThanos bool) (*PrometheusMetadata, error) {
|
|
|
- q := "up"
|
|
|
- if isThanos {
|
|
|
- q += thanos.QueryOffset()
|
|
|
- }
|
|
|
-
|
|
|
- ctx := prom.NewContext(cli)
|
|
|
-
|
|
|
- resUp, err := ctx.QuerySync(q)
|
|
|
- if err != nil {
|
|
|
- return &PrometheusMetadata{
|
|
|
- Running: false,
|
|
|
- KubecostDataExists: false,
|
|
|
- }, err
|
|
|
- }
|
|
|
-
|
|
|
- if len(resUp) == 0 {
|
|
|
- return &PrometheusMetadata{
|
|
|
- Running: false,
|
|
|
- KubecostDataExists: false,
|
|
|
- }, fmt.Errorf("no running jobs on Prometheus at %s", ctx.QueryURL().Path)
|
|
|
- }
|
|
|
-
|
|
|
- for _, result := range resUp {
|
|
|
- job, err := result.GetString("job")
|
|
|
- if err != nil {
|
|
|
- return &PrometheusMetadata{
|
|
|
- Running: false,
|
|
|
- KubecostDataExists: false,
|
|
|
- }, fmt.Errorf("up query does not have job names")
|
|
|
- }
|
|
|
-
|
|
|
- if job == "kubecost" {
|
|
|
- return &PrometheusMetadata{
|
|
|
- Running: true,
|
|
|
- KubecostDataExists: true,
|
|
|
- }, err
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return &PrometheusMetadata{
|
|
|
- Running: true,
|
|
|
- KubecostDataExists: false,
|
|
|
- }, nil
|
|
|
-}
|
|
|
-
|
|
|
func (cm *CostModel) ComputeCostData(cli prometheusClient.Client, clientset kubernetes.Interface, cp costAnalyzerCloud.Provider, window string, offset string, filterNamespace string) (map[string]*CostData, error) {
|
|
|
queryRAMRequests := fmt.Sprintf(queryRAMRequestsStr, window, offset, window, offset)
|
|
|
queryRAMUsage := fmt.Sprintf(queryRAMUsageStr, window, offset, window, offset)
|
|
|
@@ -514,27 +464,27 @@ func (cm *CostModel) ComputeCostData(cli prometheusClient.Client, clientset kube
|
|
|
RAMReqV, ok := RAMReqMap[newKey]
|
|
|
if !ok {
|
|
|
klog.V(4).Info("no RAM requests for " + newKey)
|
|
|
- RAMReqV = []*util.Vector{&util.Vector{}}
|
|
|
+ RAMReqV = []*util.Vector{{}}
|
|
|
}
|
|
|
RAMUsedV, ok := RAMUsedMap[newKey]
|
|
|
if !ok {
|
|
|
klog.V(4).Info("no RAM usage for " + newKey)
|
|
|
- RAMUsedV = []*util.Vector{&util.Vector{}}
|
|
|
+ RAMUsedV = []*util.Vector{{}}
|
|
|
}
|
|
|
CPUReqV, ok := CPUReqMap[newKey]
|
|
|
if !ok {
|
|
|
klog.V(4).Info("no CPU requests for " + newKey)
|
|
|
- CPUReqV = []*util.Vector{&util.Vector{}}
|
|
|
+ CPUReqV = []*util.Vector{{}}
|
|
|
}
|
|
|
GPUReqV, ok := GPUReqMap[newKey]
|
|
|
if !ok {
|
|
|
klog.V(4).Info("no GPU requests for " + newKey)
|
|
|
- GPUReqV = []*util.Vector{&util.Vector{}}
|
|
|
+ GPUReqV = []*util.Vector{{}}
|
|
|
}
|
|
|
CPUUsedV, ok := CPUUsedMap[newKey]
|
|
|
if !ok {
|
|
|
klog.V(4).Info("no CPU usage for " + newKey)
|
|
|
- CPUUsedV = []*util.Vector{&util.Vector{}}
|
|
|
+ CPUUsedV = []*util.Vector{{}}
|
|
|
}
|
|
|
|
|
|
var pvReq []*PersistentVolumeClaimData
|
|
|
@@ -564,7 +514,7 @@ func (cm *CostModel) ComputeCostData(cli prometheusClient.Client, clientset kube
|
|
|
NetworkData: netReq,
|
|
|
Labels: podLabels,
|
|
|
NamespaceLabels: nsLabels,
|
|
|
- ClusterID: clusterID,
|
|
|
+ ClusterID: cm.ClusterMap.NameIDFor(clusterID),
|
|
|
}
|
|
|
costs.CPUAllocation = getContainerAllocation(costs.CPUReq, costs.CPUUsed, "CPU")
|
|
|
costs.RAMAllocation = getContainerAllocation(costs.RAMReq, costs.RAMUsed, "RAM")
|
|
|
@@ -584,27 +534,27 @@ func (cm *CostModel) ComputeCostData(cli prometheusClient.Client, clientset kube
|
|
|
RAMReqV, ok := RAMReqMap[key]
|
|
|
if !ok {
|
|
|
klog.V(4).Info("no RAM requests for " + key)
|
|
|
- RAMReqV = []*util.Vector{&util.Vector{}}
|
|
|
+ RAMReqV = []*util.Vector{{}}
|
|
|
}
|
|
|
RAMUsedV, ok := RAMUsedMap[key]
|
|
|
if !ok {
|
|
|
klog.V(4).Info("no RAM usage for " + key)
|
|
|
- RAMUsedV = []*util.Vector{&util.Vector{}}
|
|
|
+ RAMUsedV = []*util.Vector{{}}
|
|
|
}
|
|
|
CPUReqV, ok := CPUReqMap[key]
|
|
|
if !ok {
|
|
|
klog.V(4).Info("no CPU requests for " + key)
|
|
|
- CPUReqV = []*util.Vector{&util.Vector{}}
|
|
|
+ CPUReqV = []*util.Vector{{}}
|
|
|
}
|
|
|
GPUReqV, ok := GPUReqMap[key]
|
|
|
if !ok {
|
|
|
klog.V(4).Info("no GPU requests for " + key)
|
|
|
- GPUReqV = []*util.Vector{&util.Vector{}}
|
|
|
+ GPUReqV = []*util.Vector{{}}
|
|
|
}
|
|
|
CPUUsedV, ok := CPUUsedMap[key]
|
|
|
if !ok {
|
|
|
klog.V(4).Info("no CPU usage for " + key)
|
|
|
- CPUUsedV = []*util.Vector{&util.Vector{}}
|
|
|
+ CPUUsedV = []*util.Vector{{}}
|
|
|
}
|
|
|
|
|
|
node, ok := nodes[c.NodeName]
|
|
|
@@ -634,7 +584,7 @@ func (cm *CostModel) ComputeCostData(cli prometheusClient.Client, clientset kube
|
|
|
CPUUsed: CPUUsedV,
|
|
|
GPUReq: GPUReqV,
|
|
|
NamespaceLabels: namespacelabels,
|
|
|
- ClusterID: c.ClusterID,
|
|
|
+ ClusterID: cm.ClusterMap.NameIDFor(c.ClusterID),
|
|
|
}
|
|
|
costs.CPUAllocation = getContainerAllocation(costs.CPUReq, costs.CPUUsed, "CPU")
|
|
|
costs.RAMAllocation = getContainerAllocation(costs.RAMReq, costs.RAMUsed, "RAM")
|
|
|
@@ -649,7 +599,7 @@ func (cm *CostModel) ComputeCostData(cli prometheusClient.Client, clientset kube
|
|
|
}
|
|
|
// Use unmounted pvs to create a mapping of "Unmounted-<Namespace>" containers
|
|
|
// to pass along the cost data
|
|
|
- unmounted := findUnmountedPVCostData(unmountedPVs, namespaceLabelsMapping)
|
|
|
+ unmounted := findUnmountedPVCostData(cm.ClusterMap, unmountedPVs, namespaceLabelsMapping)
|
|
|
for k, costs := range unmounted {
|
|
|
klog.V(4).Infof("Unmounted PVs in Namespace/ClusterID: %s/%s", costs.Namespace, costs.ClusterID)
|
|
|
|
|
|
@@ -672,7 +622,7 @@ func (cm *CostModel) ComputeCostData(cli prometheusClient.Client, clientset kube
|
|
|
return containerNameCost, err
|
|
|
}
|
|
|
|
|
|
-func findUnmountedPVCostData(unmountedPVs map[string][]*PersistentVolumeClaimData, namespaceLabelsMapping map[string]map[string]string) map[string]*CostData {
|
|
|
+func findUnmountedPVCostData(clusterMap clusters.ClusterMap, unmountedPVs map[string][]*PersistentVolumeClaimData, namespaceLabelsMapping map[string]map[string]string) map[string]*CostData {
|
|
|
costs := make(map[string]*CostData)
|
|
|
if len(unmountedPVs) == 0 {
|
|
|
return costs
|
|
|
@@ -706,7 +656,7 @@ func findUnmountedPVCostData(unmountedPVs map[string][]*PersistentVolumeClaimDat
|
|
|
Namespace: ns,
|
|
|
NamespaceLabels: namespacelabels,
|
|
|
Labels: namespacelabels,
|
|
|
- ClusterID: clusterID,
|
|
|
+ ClusterID: clusterMap.NameIDFor(clusterID),
|
|
|
PVCData: pv,
|
|
|
}
|
|
|
} else {
|
|
|
@@ -1440,15 +1390,20 @@ func sliceToSet(s []string) map[string]bool {
|
|
|
|
|
|
func setToSlice(m map[string]bool) []string {
|
|
|
var result []string
|
|
|
- for k, _ := range m {
|
|
|
+ for k := range m {
|
|
|
result = append(result, k)
|
|
|
}
|
|
|
return result
|
|
|
}
|
|
|
|
|
|
-func costDataPassesFilters(costs *CostData, namespace string, cluster string) bool {
|
|
|
+func costDataPassesFilters(cm clusters.ClusterMap, costs *CostData, namespace string, cluster string) bool {
|
|
|
passesNamespace := namespace == "" || costs.Namespace == namespace
|
|
|
- passesCluster := cluster == "" || costs.ClusterID == cluster
|
|
|
+
|
|
|
+ passesCluster := true
|
|
|
+ if cluster != "" {
|
|
|
+ id, name := cm.SplitNameID(costs.ClusterID)
|
|
|
+ passesCluster = id == cluster || name == cluster
|
|
|
+ }
|
|
|
|
|
|
return passesNamespace && passesCluster
|
|
|
}
|
|
|
@@ -2061,10 +2016,10 @@ func (cm *CostModel) costDataRange(cli prometheusClient.Client, clientset kubern
|
|
|
NamespaceLabels: namespaceLabels,
|
|
|
PVCData: podPVs,
|
|
|
NetworkData: podNetCosts,
|
|
|
- ClusterID: c.ClusterID,
|
|
|
+ ClusterID: cm.ClusterMap.NameIDFor(c.ClusterID),
|
|
|
}
|
|
|
|
|
|
- if costDataPassesFilters(costs, filterNamespace, filterCluster) {
|
|
|
+ if costDataPassesFilters(cm.ClusterMap, costs, filterNamespace, filterCluster) {
|
|
|
containerNameCost[key] = costs
|
|
|
missingContainers[key] = costs
|
|
|
}
|
|
|
@@ -2072,11 +2027,11 @@ func (cm *CostModel) costDataRange(cli prometheusClient.Client, clientset kubern
|
|
|
|
|
|
measureTime(profileStart, profileThreshold, fmt.Sprintf("costDataRange(%fh): build CostData map", durHrs))
|
|
|
|
|
|
- unmounted := findUnmountedPVCostData(unmountedPVs, namespaceLabelsMapping)
|
|
|
+ unmounted := findUnmountedPVCostData(cm.ClusterMap, unmountedPVs, namespaceLabelsMapping)
|
|
|
for k, costs := range unmounted {
|
|
|
klog.V(4).Infof("Unmounted PVs in Namespace/ClusterID: %s/%s", costs.Namespace, costs.ClusterID)
|
|
|
|
|
|
- if costDataPassesFilters(costs, filterNamespace, filterCluster) {
|
|
|
+ if costDataPassesFilters(cm.ClusterMap, costs, filterNamespace, filterCluster) {
|
|
|
containerNameCost[k] = costs
|
|
|
}
|
|
|
}
|