| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381 |
- package costmodel
- import (
- "fmt"
- "net"
- "strconv"
- "strings"
- "time"
- "github.com/opencost/opencost/pkg/cloud/provider"
- "golang.org/x/exp/slices"
- "github.com/opencost/opencost/core/pkg/log"
- "github.com/opencost/opencost/core/pkg/opencost"
- "github.com/opencost/opencost/core/pkg/source"
- "github.com/opencost/opencost/core/pkg/util/timeutil"
- "github.com/opencost/opencost/pkg/cloud/models"
- "github.com/opencost/opencost/pkg/env"
- )
- const MAX_LOCAL_STORAGE_SIZE = 1024 * 1024 * 1024 * 1024
- // When ASSET_INCLUDE_LOCAL_DISK_COST is set to false, local storage
- // provisioned by sig-storage-local-static-provisioner is excluded
- // by checking if the volume is prefixed by "local-pv-".
- //
- // This is based on the sig-storage-local-static-provisioner implementation,
- // which creates all PVs with the "local-pv-" prefix. For reference, see:
- // https://github.com/kubernetes-sigs/sig-storage-local-static-provisioner/blob/b6f465027bd059e92c0032c81dd1e1d90e35c909/pkg/discovery/discovery.go#L410-L417
- const SIG_STORAGE_LOCAL_PROVISIONER_PREFIX = "local-pv-"
- // Costs represents cumulative and monthly cluster costs over a given duration. Costs
- // are broken down by cores, memory, and storage.
- type ClusterCosts struct {
- 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"`
- DataMinutes float64
- }
- // 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
- // the associated monthly rate data, and returns the Costs.
- 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()
- }
- // 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,
- CPUCumulative: cpu,
- GPUCumulative: gpu,
- RAMCumulative: ram,
- StorageCumulative: storage,
- TotalCumulative: cpu + gpu + ram + storage,
- 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
- return cc, nil
- }
- type Disk struct {
- Cluster string
- Name string
- ProviderID string
- StorageClass string
- VolumeName string
- ClaimName string
- ClaimNamespace string
- Cost float64
- Bytes float64
- // These two fields may not be available at all times because they rely on
- // a new set of metrics that may or may not be available. Thus, they must
- // be nilable to represent the complete absence of the data.
- //
- // In other words, nilability here lets us distinguish between
- // "metric is not available" and "metric is available but is 0".
- //
- // They end in "Ptr" to distinguish from an earlier version in order to
- // ensure that all usages are checked for nil.
- BytesUsedAvgPtr *float64
- BytesUsedMaxPtr *float64
- Local bool
- Start time.Time
- End time.Time
- Minutes float64
- Breakdown *ClusterCostsBreakdown
- }
- type DiskIdentifier struct {
- Cluster string
- Name string
- }
- func ClusterDisks(dataSource source.OpenCostDataSource, cp models.Provider, start, end time.Time) (map[DiskIdentifier]*Disk, error) {
- resolution := env.GetETLResolution()
- grp := source.NewQueryGroup()
- resChPVCost := source.WithGroup(grp, dataSource.QueryPVPricePerGiBHour(start, end))
- resChPVSize := source.WithGroup(grp, dataSource.QueryPVBytes(start, end))
- resChActiveMins := source.WithGroup(grp, dataSource.QueryPVActiveMinutes(start, end))
- resChPVStorageClass := source.WithGroup(grp, dataSource.QueryPVInfo(start, end))
- resChPVUsedAvg := source.WithGroup(grp, dataSource.QueryPVUsedAverage(start, end))
- resChPVUsedMax := source.WithGroup(grp, dataSource.QueryPVUsedMax(start, end))
- resChPVCInfo := source.WithGroup(grp, dataSource.QueryPVCInfo(start, end))
- resPVCost, _ := resChPVCost.Await()
- resPVSize, _ := resChPVSize.Await()
- resActiveMins, _ := resChActiveMins.Await()
- resPVStorageClass, _ := resChPVStorageClass.Await()
- resPVUsedAvg, _ := resChPVUsedAvg.Await()
- resPVUsedMax, _ := resChPVUsedMax.Await()
- resPVCInfo, _ := resChPVCInfo.Await()
- // Cloud providers do not always charge for a node's local disk costs (i.e.
- // ephemeral storage). Provide an option to opt out of calculating &
- // allocating local disk costs. Note, that this does not affect
- // PersistentVolume costs.
- //
- // Ref:
- // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/RootDeviceStorage.html
- // https://learn.microsoft.com/en-us/azure/virtual-machines/managed-disks-overview#temporary-disk
- // https://cloud.google.com/compute/docs/disks/local-ssd
- resLocalStorageCost := []*source.LocalStorageCostResult{}
- resLocalStorageUsedCost := []*source.LocalStorageUsedCostResult{}
- resLocalStorageUsedAvg := []*source.LocalStorageUsedAvgResult{}
- resLocalStorageUsedMax := []*source.LocalStorageUsedMaxResult{}
- resLocalStorageBytes := []*source.LocalStorageBytesResult{}
- resLocalActiveMins := []*source.LocalStorageActiveMinutesResult{}
- if env.GetAssetIncludeLocalDiskCost() {
- resChLocalStorageCost := source.WithGroup(grp, dataSource.QueryLocalStorageCost(start, end))
- resChLocalStorageUsedCost := source.WithGroup(grp, dataSource.QueryLocalStorageUsedCost(start, end))
- resChLocalStoreageUsedAvg := source.WithGroup(grp, dataSource.QueryLocalStorageUsedAvg(start, end))
- resChLocalStoreageUsedMax := source.WithGroup(grp, dataSource.QueryLocalStorageUsedMax(start, end))
- resChLocalStorageBytes := source.WithGroup(grp, dataSource.QueryLocalStorageBytes(start, end))
- resChLocalActiveMins := source.WithGroup(grp, dataSource.QueryLocalStorageActiveMinutes(start, end))
- resLocalStorageCost, _ = resChLocalStorageCost.Await()
- resLocalStorageUsedCost, _ = resChLocalStorageUsedCost.Await()
- resLocalStorageUsedAvg, _ = resChLocalStoreageUsedAvg.Await()
- resLocalStorageUsedMax, _ = resChLocalStoreageUsedMax.Await()
- resLocalStorageBytes, _ = resChLocalStorageBytes.Await()
- resLocalActiveMins, _ = resChLocalActiveMins.Await()
- }
- if grp.HasErrors() {
- return nil, grp.Error()
- }
- diskMap := buildAssetsPVCMap(resPVCInfo)
- pvCosts(diskMap, resolution, resActiveMins, resPVSize, resPVCost, resPVUsedAvg, resPVUsedMax, resPVCInfo, cp, opencost.NewClosedWindow(start, end))
- type localStorage struct {
- device string
- disk *Disk
- }
- localStorageDisks := map[DiskIdentifier]localStorage{}
- // Start with local storage bytes so that the device with the largest size which has passed the
- // query filters can be determined
- for _, result := range resLocalStorageBytes {
- cluster := result.Cluster
- if cluster == "" {
- cluster = env.GetClusterID()
- }
- name := result.Instance
- if name == "" {
- log.Warnf("ClusterDisks: local storage data missing instance")
- continue
- }
- device := result.Device
- if device == "" {
- log.Warnf("ClusterDisks: local storage data missing device")
- continue
- }
- bytes := result.Data[0].Value
- // Ignore disks that are larger than the max size
- if bytes > MAX_LOCAL_STORAGE_SIZE {
- continue
- }
- key := DiskIdentifier{cluster, name}
- // only keep the device with the most bytes per instance
- if current, ok := localStorageDisks[key]; !ok || current.disk.Bytes < bytes {
- localStorageDisks[key] = localStorage{
- device: device,
- disk: &Disk{
- Cluster: cluster,
- Name: name,
- Breakdown: &ClusterCostsBreakdown{},
- Local: true,
- StorageClass: opencost.LocalStorageClass,
- Bytes: bytes,
- },
- }
- }
- }
- for _, result := range resLocalStorageCost {
- cluster := result.Cluster
- if cluster == "" {
- cluster = env.GetClusterID()
- }
- name := result.Instance
- if name == "" {
- log.Warnf("ClusterDisks: local storage data missing instance")
- continue
- }
- device := result.Device
- if device == "" {
- log.Warnf("ClusterDisks: local storage data missing device")
- continue
- }
- cost := result.Data[0].Value
- key := DiskIdentifier{cluster, name}
- ls, ok := localStorageDisks[key]
- if !ok || ls.device != device {
- continue
- }
- ls.disk.Cost = cost
- }
- for _, result := range resLocalStorageUsedCost {
- cluster := result.Cluster
- if cluster == "" {
- cluster = env.GetClusterID()
- }
- name := result.Instance
- if name == "" {
- log.Warnf("ClusterDisks: local storage data missing instance")
- continue
- }
- device := result.Device
- if device == "" {
- log.Warnf("ClusterDisks: local storage data missing device")
- continue
- }
- cost := result.Data[0].Value
- key := DiskIdentifier{cluster, name}
- ls, ok := localStorageDisks[key]
- if !ok || ls.device != device {
- continue
- }
- ls.disk.Breakdown.System = cost / ls.disk.Cost
- }
- for _, result := range resLocalStorageUsedAvg {
- cluster := result.Cluster
- if cluster == "" {
- cluster = env.GetClusterID()
- }
- name := result.Instance
- if name == "" {
- log.Warnf("ClusterDisks: local storage data missing instance")
- continue
- }
- device := result.Device
- if device == "" {
- log.Warnf("ClusterDisks: local storage data missing device")
- continue
- }
- bytesAvg := result.Data[0].Value
- key := DiskIdentifier{cluster, name}
- ls, ok := localStorageDisks[key]
- if !ok || ls.device != device {
- continue
- }
- ls.disk.BytesUsedAvgPtr = &bytesAvg
- }
- for _, result := range resLocalStorageUsedMax {
- cluster := result.Cluster
- if cluster == "" {
- cluster = env.GetClusterID()
- }
- name := result.Instance
- if name == "" {
- log.Warnf("ClusterDisks: local storage data missing instance")
- continue
- }
- device := result.Device
- if device == "" {
- log.Warnf("ClusterDisks: local storage data missing device")
- continue
- }
- bytesMax := result.Data[0].Value
- key := DiskIdentifier{cluster, name}
- ls, ok := localStorageDisks[key]
- if !ok || ls.device != device {
- continue
- }
- ls.disk.BytesUsedMaxPtr = &bytesMax
- }
- for _, result := range resLocalActiveMins {
- cluster := result.Cluster
- if cluster == "" {
- cluster = env.GetClusterID()
- }
- name := result.Node
- if name == "" {
- log.DedupedWarningf(5, "ClusterDisks: local active mins data missing instance")
- continue
- }
- providerID := result.ProviderID
- if providerID == "" {
- log.DedupedWarningf(5, "ClusterDisks: local active mins data missing instance")
- continue
- }
- key := DiskIdentifier{cluster, name}
- ls, ok := localStorageDisks[key]
- if !ok {
- continue
- }
- ls.disk.ProviderID = provider.ParseLocalDiskID(providerID)
- if len(result.Data) == 0 {
- continue
- }
- s := time.Unix(int64(result.Data[0].Timestamp), 0)
- e := time.Unix(int64(result.Data[len(result.Data)-1].Timestamp), 0)
- mins := e.Sub(s).Minutes()
- // TODO niko/assets if mins >= threshold, interpolate for missing data?
- ls.disk.End = e
- ls.disk.Start = s
- ls.disk.Minutes = mins
- }
- // move local storage disks to main disk map
- for key, ls := range localStorageDisks {
- diskMap[key] = ls.disk
- }
- var unTracedDiskLogData []DiskIdentifier
- //Iterating through Persistent Volume given by custom metrics kubecost_pv_info and assign the storage class if known and __unknown__ if not populated.
- for _, result := range resPVStorageClass {
- cluster := result.Cluster
- if cluster == "" {
- cluster = env.GetClusterID()
- }
- name := result.PersistentVolume
- key := DiskIdentifier{cluster, name}
- if _, ok := diskMap[key]; !ok {
- if !slices.Contains(unTracedDiskLogData, key) {
- unTracedDiskLogData = append(unTracedDiskLogData, key)
- }
- continue
- }
- if len(result.Data) == 0 {
- continue
- }
- storageClass := result.StorageClass
- if storageClass == "" {
- diskMap[key].StorageClass = opencost.UnknownStorageClass
- } else {
- diskMap[key].StorageClass = storageClass
- }
- }
- // Logging the unidentified disk information outside the loop
- for _, unIdentifiedDisk := range unTracedDiskLogData {
- log.Warnf("ClusterDisks: Cluster %s has Storage Class information for unidentified disk %s or disk deleted from analysis", unIdentifiedDisk.Cluster, unIdentifiedDisk.Name)
- }
- for _, disk := range diskMap {
- // Apply all remaining RAM to Idle
- disk.Breakdown.Idle = 1.0 - (disk.Breakdown.System + disk.Breakdown.Other + disk.Breakdown.User)
- // Set provider Id to the name for reconciliation
- if disk.ProviderID == "" {
- disk.ProviderID = disk.Name
- }
- }
- if !env.GetAssetIncludeLocalDiskCost() {
- return filterOutLocalPVs(diskMap), nil
- }
- return diskMap, nil
- }
- type NodeOverhead struct {
- CpuOverheadFraction float64
- RamOverheadFraction float64
- }
- type Node struct {
- Cluster string
- Name string
- ProviderID string
- NodeType string
- CPUCost float64
- CPUCores float64
- GPUCost float64
- GPUCount float64
- RAMCost float64
- RAMBytes float64
- Discount float64
- Preemptible bool
- CPUBreakdown *ClusterCostsBreakdown
- RAMBreakdown *ClusterCostsBreakdown
- Start time.Time
- End time.Time
- Minutes float64
- Labels map[string]string
- CostPerCPUHr float64
- CostPerRAMGiBHr float64
- CostPerGPUHr float64
- Overhead *NodeOverhead
- }
- // GKE lies about the number of cores e2 nodes have. This table
- // contains a mapping from node type -> actual CPU cores
- // for those cases.
- var partialCPUMap = map[string]float64{
- "e2-micro": 0.25,
- "e2-small": 0.5,
- "e2-medium": 1.0,
- }
- type NodeIdentifier struct {
- Cluster string
- Name string
- ProviderID string
- }
- type nodeIdentifierNoProviderID struct {
- Cluster string
- Name string
- }
- type ClusterManagementIdentifier struct {
- Cluster string
- Provisioner string
- }
- type ClusterManagementCost struct {
- Cluster string
- Provisioner string
- Cost float64
- }
- func costTimesMinuteAndCount(activeDataMap map[NodeIdentifier]activeData, costMap map[NodeIdentifier]float64, resourceCountMap map[nodeIdentifierNoProviderID]float64) {
- for k, v := range activeDataMap {
- keyNon := nodeIdentifierNoProviderID{
- Cluster: k.Cluster,
- Name: k.Name,
- }
- if cost, ok := costMap[k]; ok {
- minutes := v.minutes
- count := 1.0
- if c, ok := resourceCountMap[keyNon]; ok {
- count = c
- }
- costMap[k] = cost * (minutes / 60.0) * count
- }
- }
- }
- func costTimesMinute[T comparable](activeDataMap map[T]activeData, costMap map[T]float64) {
- for k, v := range activeDataMap {
- if cost, ok := costMap[k]; ok {
- minutes := v.minutes
- costMap[k] = cost * (minutes / 60)
- }
- }
- }
- func ClusterNodes(dataSource source.OpenCostDataSource, cp models.Provider, start, end time.Time) (map[NodeIdentifier]*Node, error) {
- resolution := env.GetETLResolution()
- requiredGrp := source.NewQueryGroup()
- optionalGrp := source.NewQueryGroup()
- // return errors if these fail
- resChNodeCPUHourlyCost := source.WithGroup(requiredGrp, dataSource.QueryNodeCPUPricePerHr(start, end))
- resChNodeCPUCoresCapacity := source.WithGroup(requiredGrp, dataSource.QueryNodeCPUCoresCapacity(start, end))
- resChNodeCPUCoresAllocatable := source.WithGroup(requiredGrp, dataSource.QueryNodeCPUCoresAllocatable(start, end))
- resChNodeRAMHourlyCost := source.WithGroup(requiredGrp, dataSource.QueryNodeRAMPricePerGiBHr(start, end))
- resChNodeRAMBytesCapacity := source.WithGroup(requiredGrp, dataSource.QueryNodeRAMBytesCapacity(start, end))
- resChNodeRAMBytesAllocatable := source.WithGroup(requiredGrp, dataSource.QueryNodeRAMBytesAllocatable(start, end))
- resChNodeGPUCount := source.WithGroup(requiredGrp, dataSource.QueryNodeGPUCount(start, end))
- resChNodeGPUHourlyPrice := source.WithGroup(requiredGrp, dataSource.QueryNodeGPUPricePerHr(start, end))
- resChActiveMins := source.WithGroup(requiredGrp, dataSource.QueryNodeActiveMinutes(start, end))
- resChIsSpot := source.WithGroup(requiredGrp, dataSource.QueryNodeIsSpot(start, end))
- // Do not return errors if these fail, but log warnings
- resChNodeCPUModeTotal := source.WithGroup(optionalGrp, dataSource.QueryNodeCPUModeTotal(start, end))
- resChNodeRAMSystemPct := source.WithGroup(optionalGrp, dataSource.QueryNodeRAMSystemPercent(start, end))
- resChNodeRAMUserPct := source.WithGroup(optionalGrp, dataSource.QueryNodeRAMUserPercent(start, end))
- resChLabels := source.WithGroup(optionalGrp, dataSource.QueryNodeLabels(start, end))
- resNodeCPUHourlyCost, _ := resChNodeCPUHourlyCost.Await()
- resNodeCPUCoresCapacity, _ := resChNodeCPUCoresCapacity.Await()
- resNodeCPUCoresAllocatable, _ := resChNodeCPUCoresAllocatable.Await()
- resNodeGPUCount, _ := resChNodeGPUCount.Await()
- resNodeGPUHourlyPrice, _ := resChNodeGPUHourlyPrice.Await()
- resNodeRAMHourlyCost, _ := resChNodeRAMHourlyCost.Await()
- resNodeRAMBytesCapacity, _ := resChNodeRAMBytesCapacity.Await()
- resNodeRAMBytesAllocatable, _ := resChNodeRAMBytesAllocatable.Await()
- resIsSpot, _ := resChIsSpot.Await()
- resNodeCPUModeTotal, _ := resChNodeCPUModeTotal.Await()
- resNodeRAMSystemPct, _ := resChNodeRAMSystemPct.Await()
- resNodeRAMUserPct, _ := resChNodeRAMUserPct.Await()
- resActiveMins, _ := resChActiveMins.Await()
- resLabels, _ := resChLabels.Await()
- if optionalGrp.HasErrors() {
- for _, err := range optionalGrp.Errors() {
- log.Warnf("ClusterNodes: %s", err)
- }
- }
- if requiredGrp.HasErrors() {
- for _, err := range requiredGrp.Errors() {
- log.Errorf("ClusterNodes: %s", err)
- }
- return nil, requiredGrp.Error()
- }
- activeDataMap := buildActiveDataMap(resActiveMins, nodeKeyGen, nodeValues, resolution, opencost.NewClosedWindow(start, end))
- gpuCountMap := buildGPUCountMap(resNodeGPUCount)
- preemptibleMap := buildPreemptibleMap(resIsSpot)
- cpuCostMap, clusterAndNameToType1 := buildCPUCostMap(resNodeCPUHourlyCost, cp, preemptibleMap)
- ramCostMap, clusterAndNameToType2 := buildRAMCostMap(resNodeRAMHourlyCost, cp, preemptibleMap)
- gpuCostMap, clusterAndNameToType3 := buildGPUCostMap(resNodeGPUHourlyPrice, gpuCountMap, cp, preemptibleMap)
- clusterAndNameToTypeIntermediate := mergeTypeMaps(clusterAndNameToType1, clusterAndNameToType2)
- clusterAndNameToType := mergeTypeMaps(clusterAndNameToTypeIntermediate, clusterAndNameToType3)
- cpuCoresCapacityMap := buildCPUCoresMap(resNodeCPUCoresCapacity)
- ramBytesCapacityMap := buildRAMBytesMap(resNodeRAMBytesCapacity)
- cpuCoresAllocatableMap := buildCPUCoresMap(resNodeCPUCoresAllocatable)
- ramBytesAllocatableMap := buildRAMBytesMap(resNodeRAMBytesAllocatable)
- overheadMap := buildOverheadMap(ramBytesCapacityMap, ramBytesAllocatableMap, cpuCoresCapacityMap, cpuCoresAllocatableMap)
- ramUserPctMap := buildRAMUserPctMap(resNodeRAMUserPct)
- ramSystemPctMap := buildRAMSystemPctMap(resNodeRAMSystemPct)
- cpuBreakdownMap := buildCPUBreakdownMap(resNodeCPUModeTotal)
- labelsMap := buildLabelsMap(resLabels)
- costTimesMinuteAndCount(activeDataMap, cpuCostMap, cpuCoresCapacityMap)
- costTimesMinuteAndCount(activeDataMap, ramCostMap, ramBytesCapacityMap)
- costTimesMinute(activeDataMap, gpuCostMap) // there's no need to do a weird "nodeIdentifierNoProviderID" type match since gpuCounts have a providerID
- nodeMap := buildNodeMap(
- cpuCostMap, ramCostMap, gpuCostMap, gpuCountMap,
- cpuCoresCapacityMap, ramBytesCapacityMap, ramUserPctMap,
- ramSystemPctMap,
- cpuBreakdownMap,
- activeDataMap,
- preemptibleMap,
- labelsMap,
- clusterAndNameToType,
- overheadMap,
- )
- c, err := cp.GetConfig()
- if err != nil {
- return nil, err
- }
- discount, err := ParsePercentString(c.Discount)
- if err != nil {
- return nil, err
- }
- negotiatedDiscount, err := ParsePercentString(c.NegotiatedDiscount)
- if err != nil {
- return nil, err
- }
- for _, node := range nodeMap {
- // TODO take GKE Reserved Instances into account
- node.Discount = cp.CombinedDiscountForNode(node.NodeType, node.Preemptible, discount, negotiatedDiscount)
- // Apply all remaining resources to Idle
- node.CPUBreakdown.Idle = 1.0 - (node.CPUBreakdown.System + node.CPUBreakdown.Other + node.CPUBreakdown.User)
- node.RAMBreakdown.Idle = 1.0 - (node.RAMBreakdown.System + node.RAMBreakdown.Other + node.RAMBreakdown.User)
- }
- return nodeMap, nil
- }
- type LoadBalancerIdentifier struct {
- Cluster string
- Namespace string
- Name string
- IngressIP string
- }
- type LoadBalancer struct {
- Cluster string
- Namespace string
- Name string
- ProviderID string
- Cost float64
- Start time.Time
- End time.Time
- Minutes float64
- Private bool
- Ip string
- }
- func ClusterLoadBalancers(dataSource source.OpenCostDataSource, start, end time.Time) (map[LoadBalancerIdentifier]*LoadBalancer, error) {
- resolution := env.GetETLResolution()
- grp := source.NewQueryGroup()
- resChLBCost := source.WithGroup(grp, dataSource.QueryLBPricePerHr(start, end))
- resChActiveMins := source.WithGroup(grp, dataSource.QueryLBActiveMinutes(start, end))
- resLBCost, _ := resChLBCost.Await()
- resActiveMins, _ := resChActiveMins.Await()
- if grp.HasErrors() {
- return nil, grp.Error()
- }
- loadBalancerMap := make(map[LoadBalancerIdentifier]*LoadBalancer, len(resActiveMins))
- activeMap := buildActiveDataMap(resActiveMins, loadBalancerKeyGen, lbValues, resolution, opencost.NewClosedWindow(start, end))
- for _, result := range resLBCost {
- key, ok := loadBalancerKeyGen(result)
- if !ok {
- continue
- }
- lbPricePerHr := result.Data[0].Value
- lb := &LoadBalancer{
- Cluster: key.Cluster,
- Namespace: key.Namespace,
- Name: key.Name,
- Cost: lbPricePerHr, // default to hourly cost, overwrite if active entry exists
- Ip: key.IngressIP,
- Private: privateIPCheck(key.IngressIP),
- }
- if active, ok := activeMap[key]; ok {
- lb.Start = active.start
- lb.End = active.end
- lb.Minutes = active.minutes
- if active.minutes > 0 {
- lb.Cost = lbPricePerHr * (active.minutes / 60.0)
- } else {
- log.DedupedWarningf(20, "ClusterLoadBalancers: found zero minutes for key: %v", key)
- }
- }
- loadBalancerMap[key] = lb
- }
- return loadBalancerMap, nil
- }
- func ClusterManagement(dataSource source.OpenCostDataSource, start, end time.Time) (map[ClusterManagementIdentifier]*ClusterManagementCost, error) {
- resolution := env.GetETLResolution()
- grp := source.NewQueryGroup()
- resChCMPrice := source.WithGroup(grp, dataSource.QueryClusterManagementPricePerHr(start, end))
- resChCMDur := source.WithGroup(grp, dataSource.QueryClusterManagementDuration(start, end))
- resCMPrice, _ := resChCMPrice.Await()
- resCMDur, _ := resChCMDur.Await()
- if grp.HasErrors() {
- return nil, grp.Error()
- }
- clusterManagementPriceMap := make(map[ClusterManagementIdentifier]*ClusterManagementCost, len(resCMDur))
- activeMap := buildActiveDataMap(resCMDur, clusterManagementKeyGen, clusterManagementValues, resolution, opencost.NewClosedWindow(start, end))
- for _, result := range resCMPrice {
- key, ok := clusterManagementKeyGen(result)
- if !ok {
- continue
- }
- cmPricePerHr := result.Data[0].Value
- cm := &ClusterManagementCost{
- Cluster: key.Cluster,
- Provisioner: key.Provisioner,
- Cost: cmPricePerHr, // default to hourly cost, overwrite if active entry exists
- }
- if active, ok := activeMap[key]; ok {
- if active.minutes > 0 {
- cm.Cost = cmPricePerHr * (active.minutes / 60.0)
- } else {
- log.DedupedWarningf(20, "ClusterManagement: found zero minutes for key: %v", key)
- }
- }
- clusterManagementPriceMap[key] = cm
- }
- return clusterManagementPriceMap, nil
- }
- // Check if an ip is private.
- func privateIPCheck(ip string) bool {
- ipAddress := net.ParseIP(ip)
- return ipAddress.IsPrivate()
- }
- // ComputeClusterCosts gives the cumulative and monthly-rate cluster costs over a window of time for all clusters.
- func (a *Accesses) ComputeClusterCosts(dataSource source.OpenCostDataSource, provider models.Provider, window, offset time.Duration, withBreakdown bool) (map[string]*ClusterCosts, error) {
- if window < 10*time.Minute {
- return nil, fmt.Errorf("minimum window of 10m required; got %s", window)
- }
- // Compute number of minutes in the full interval, for use interpolating missed scrapes or scaling missing data
- start, end := timeutil.ParseTimeRange(window, offset)
- mins := end.Sub(start).Minutes()
- providerName := ""
- if clusterInfo, err := provider.ClusterInfo(); err != nil {
- providerName = clusterInfo["provider"]
- }
- grp := source.NewQueryGroup()
- queryDataCount := source.WithGroup(grp, dataSource.QueryDataCount(start, end))
- queryTotalGPU := source.WithGroup(grp, dataSource.QueryTotalGPU(start, end))
- queryTotalCPU := source.WithGroup(grp, dataSource.QueryTotalCPU(start, end))
- queryTotalRAM := source.WithGroup(grp, dataSource.QueryTotalRAM(start, end))
- queryTotalStorage := source.WithGroup(grp, dataSource.QueryTotalStorage(start, end))
- queryTotalLocalStorage := source.WithGroup(grp, dataSource.QueryLocalStorageBytesByProvider(providerName, start, end))
- var queryCPUModePct *source.QueryGroupFuture[source.NodeCPUModePercentResult]
- var queryRAMSystemPct *source.QueryGroupFuture[source.NodeRAMSystemPercentResult]
- var queryRAMUserPct *source.QueryGroupFuture[source.NodeRAMUserPercentResult]
- var queryUsedLocalStorage *source.QueryGroupFuture[source.LocalStorageUsedByProviderResult]
- if withBreakdown {
- queryCPUModePct = source.WithGroup(grp, dataSource.QueryNodeCPUModePercent(start, end))
- queryRAMSystemPct = source.WithGroup(grp, dataSource.QueryNodeRAMSystemPercent(start, end))
- queryRAMUserPct = source.WithGroup(grp, dataSource.QueryNodeRAMUserPercent(start, end))
- queryUsedLocalStorage = source.WithGroup(grp, dataSource.QueryLocalStorageUsedByProvider(providerName, start, end))
- }
- resDataCount, _ := queryDataCount.Await()
- resTotalGPU, _ := queryTotalGPU.Await()
- resTotalCPU, _ := queryTotalCPU.Await()
- resTotalRAM, _ := queryTotalRAM.Await()
- resTotalStorage, _ := queryTotalStorage.Await()
- if grp.HasErrors() {
- return nil, grp.Error()
- }
- defaultClusterID := env.GetClusterID()
- dataMinsByCluster := map[string]float64{}
- for _, result := range resDataCount {
- clusterID := result.Cluster
- if clusterID == "" {
- clusterID = defaultClusterID
- }
- dataMins := mins
- if len(result.Data) > 0 {
- dataMins = result.Data[0].Value
- } else {
- log.Warnf("Cluster cost data count returned no results for cluster %s", clusterID)
- }
- dataMinsByCluster[clusterID] = dataMins
- }
- // Determine combined discount
- discount, customDiscount := 0.0, 0.0
- c, err := a.CloudProvider.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
- costData := make(map[string]map[string]float64)
- // Helper function to iterate over Prom query results, parsing the raw values into
- // the intermediate costData structure.
- setCostsFromResults := func(costData map[string]map[string]float64, results []*source.TotalResult, name string, discount float64, customDiscount float64) {
- for _, result := range results {
- clusterID := result.Cluster
- if clusterID == "" {
- clusterID = defaultClusterID
- }
- if _, ok := costData[clusterID]; !ok {
- costData[clusterID] = map[string]float64{}
- }
- if len(result.Data) > 0 {
- costData[clusterID][name] += result.Data[0].Value * (1.0 - discount) * (1.0 - customDiscount)
- costData[clusterID]["total"] += result.Data[0].Value * (1.0 - discount) * (1.0 - customDiscount)
- }
- }
- }
- // Apply both sustained use and custom discounts to RAM and CPU
- setCostsFromResults(costData, resTotalCPU, "cpu", discount, customDiscount)
- setCostsFromResults(costData, resTotalRAM, "ram", discount, customDiscount)
- // Apply only custom discount to GPU and storage
- setCostsFromResults(costData, resTotalGPU, "gpu", 0.0, customDiscount)
- setCostsFromResults(costData, resTotalStorage, "storage", 0.0, customDiscount)
- resTotalLocalStorage, err := queryTotalLocalStorage.Await()
- if err != nil {
- return nil, err
- }
- if len(resTotalLocalStorage) > 0 {
- setCostsFromResults(costData, resTotalLocalStorage, "localstorage", 0.0, customDiscount)
- }
- cpuBreakdownMap := map[string]*ClusterCostsBreakdown{}
- ramBreakdownMap := map[string]*ClusterCostsBreakdown{}
- pvUsedCostMap := map[string]float64{}
- if withBreakdown {
- resCPUModePct, _ := queryCPUModePct.Await()
- resRAMSystemPct, _ := queryRAMSystemPct.Await()
- resRAMUserPct, _ := queryRAMUserPct.Await()
- if grp.HasErrors() {
- return nil, grp.Error()
- }
- for _, result := range resCPUModePct {
- clusterID := result.Cluster
- if clusterID == "" {
- clusterID = defaultClusterID
- }
- if _, ok := cpuBreakdownMap[clusterID]; !ok {
- cpuBreakdownMap[clusterID] = &ClusterCostsBreakdown{}
- }
- cpuBD := cpuBreakdownMap[clusterID]
- mode := result.Mode
- if mode == "" {
- log.Warnf("ComputeClusterCosts: unable to read CPU mode: %s", err)
- mode = "other"
- }
- switch mode {
- case "idle":
- cpuBD.Idle += result.Data[0].Value
- case "system":
- cpuBD.System += result.Data[0].Value
- case "user":
- cpuBD.User += result.Data[0].Value
- default:
- cpuBD.Other += result.Data[0].Value
- }
- }
- for _, result := range resRAMSystemPct {
- clusterID := result.Cluster
- if clusterID == "" {
- clusterID = defaultClusterID
- }
- if _, ok := ramBreakdownMap[clusterID]; !ok {
- ramBreakdownMap[clusterID] = &ClusterCostsBreakdown{}
- }
- ramBD := ramBreakdownMap[clusterID]
- ramBD.System += result.Data[0].Value
- }
- for _, result := range resRAMUserPct {
- clusterID := result.Cluster
- if clusterID == "" {
- clusterID = defaultClusterID
- }
- if _, ok := ramBreakdownMap[clusterID]; !ok {
- ramBreakdownMap[clusterID] = &ClusterCostsBreakdown{}
- }
- ramBD := ramBreakdownMap[clusterID]
- ramBD.User += result.Data[0].Value
- }
- for _, ramBD := range ramBreakdownMap {
- remaining := 1.0
- remaining -= ramBD.Other
- remaining -= ramBD.System
- remaining -= ramBD.User
- ramBD.Idle = remaining
- }
- resUsedLocalStorage, err := queryUsedLocalStorage.Await()
- if err != nil {
- return nil, err
- }
- for _, result := range resUsedLocalStorage {
- clusterID := result.Cluster
- if clusterID == "" {
- clusterID = defaultClusterID
- }
- pvUsedCostMap[clusterID] += result.Data[0].Value
- }
- }
- if grp.HasErrors() {
- for _, err := range grp.Errors() {
- log.Errorf("ComputeClusterCosts: %s", err)
- }
- return nil, grp.Error()
- }
- // Convert intermediate structure to Costs instances
- costsByCluster := map[string]*ClusterCosts{}
- for id, cd := range costData {
- dataMins, ok := dataMinsByCluster[id]
- if !ok {
- dataMins = mins
- log.Warnf("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/timeutil.MinsPerHour)
- if err != nil {
- log.Warnf("Failed to parse cluster costs on %s (%s) from cumulative data: %+v", window, offset, cd)
- 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
- }
- costs.DataMinutes = dataMins
- costsByCluster[id] = costs
- }
- return costsByCluster, nil
- }
- type Totals struct {
- TotalCost [][]string `json:"totalcost"`
- CPUCost [][]string `json:"cpucost"`
- MemCost [][]string `json:"memcost"`
- StorageCost [][]string `json:"storageCost"`
- }
- func resultToTotals(qrs []*source.ClusterResult) ([][]string, error) {
- if len(qrs) == 0 {
- return [][]string{}, fmt.Errorf("not enough data available in the selected time range")
- }
- result := qrs[0]
- totals := [][]string{}
- for _, value := range result.Data {
- d0 := fmt.Sprintf("%f", value.Timestamp)
- d1 := fmt.Sprintf("%f", value.Value)
- toAppend := []string{
- d0,
- d1,
- }
- totals = append(totals, toAppend)
- }
- return totals, nil
- }
- // ClusterCostsOverTime gives the full cluster costs over time
- func ClusterCostsOverTime(dataSource source.OpenCostDataSource, provider models.Provider, start, end time.Time, window, offset time.Duration) (*Totals, error) {
- providerName := ""
- if clusterInfo, err := provider.ClusterInfo(); err != nil {
- providerName = clusterInfo["provider"]
- }
- grp := source.NewQueryGroup()
- qCores := source.WithGroup(grp, dataSource.QueryClusterCores(start, end, window))
- qRAM := source.WithGroup(grp, dataSource.QueryClusterRAM(start, end, window))
- qStorage := source.WithGroup(grp, dataSource.QueryClusterStorageByProvider(providerName, start, end, window))
- qTotal := source.WithGroup(grp, dataSource.QueryClusterTotalByProvider(providerName, start, end, window))
- resultClusterCores, _ := qCores.Await()
- resultClusterRAM, _ := qRAM.Await()
- resultStorage, _ := qStorage.Await()
- resultTotal, _ := qTotal.Await()
- if grp.HasErrors() {
- return nil, grp.Error()
- }
- coreTotal, err := resultToTotals(resultClusterCores)
- if err != nil {
- log.Infof("[Warning] ClusterCostsOverTime: no cpu data: %s", err)
- return nil, err
- }
- ramTotal, err := resultToTotals(resultClusterRAM)
- if err != nil {
- log.Infof("[Warning] ClusterCostsOverTime: no ram data: %s", err)
- return nil, err
- }
- storageTotal, err := resultToTotals(resultStorage)
- if err != nil {
- log.Infof("[Warning] ClusterCostsOverTime: no storage data: %s", err)
- }
- clusterTotal, err := resultToTotals(resultTotal)
- if err != nil {
- // If clusterTotal query failed, it's likely because there are no PVs, which
- // causes the qTotal query to return no data. Instead, query only node costs.
- // If that fails, return an error because something is actually wrong.
- qNodes := source.WithGroup(grp, dataSource.QueryClusterNodesByProvider(providerName, start, end, window))
- resultNodes, err := qNodes.Await()
- if err != nil {
- return nil, err
- }
- clusterTotal, err = resultToTotals(resultNodes)
- if err != nil {
- log.Infof("[Warning] ClusterCostsOverTime: no node data: %s", err)
- return nil, err
- }
- }
- return &Totals{
- TotalCost: clusterTotal,
- CPUCost: coreTotal,
- MemCost: ramTotal,
- StorageCost: storageTotal,
- }, nil
- }
- func pvCosts(
- diskMap map[DiskIdentifier]*Disk,
- resolution time.Duration,
- resActiveMins []*source.PVActiveMinutesResult,
- resPVSize []*source.PVBytesResult,
- resPVCost []*source.PVPricePerGiBHourResult,
- resPVUsedAvg []*source.PVUsedAvgResult,
- resPVUsedMax []*source.PVUsedMaxResult,
- resPVCInfo []*source.PVCInfoResult,
- cp models.Provider,
- window opencost.Window,
- ) {
- for _, result := range resActiveMins {
- cluster := result.Cluster
- if cluster == "" {
- cluster = env.GetClusterID()
- }
- name := result.PersistentVolume
- if name == "" {
- log.Warnf("ClusterDisks: active mins missing pv name")
- continue
- }
- if len(result.Data) == 0 {
- continue
- }
- key := DiskIdentifier{cluster, name}
- if _, ok := diskMap[key]; !ok {
- diskMap[key] = &Disk{
- Cluster: cluster,
- Name: name,
- Breakdown: &ClusterCostsBreakdown{},
- }
- }
- s, e := calculateStartAndEnd(result.Data, resolution, window)
- mins := e.Sub(s).Minutes()
- diskMap[key].End = e
- diskMap[key].Start = s
- diskMap[key].Minutes = mins
- }
- for _, result := range resPVSize {
- cluster := result.Cluster
- if cluster == "" {
- cluster = env.GetClusterID()
- }
- name := result.PersistentVolume
- if name == "" {
- log.Warnf("ClusterDisks: PV size data missing persistentvolume")
- continue
- }
- // TODO niko/assets storage class
- bytes := result.Data[0].Value
- key := DiskIdentifier{cluster, name}
- if _, ok := diskMap[key]; !ok {
- diskMap[key] = &Disk{
- Cluster: cluster,
- Name: name,
- Breakdown: &ClusterCostsBreakdown{},
- }
- }
- diskMap[key].Bytes = bytes
- }
- customPricingEnabled := provider.CustomPricesEnabled(cp)
- customPricingConfig, err := cp.GetConfig()
- if err != nil {
- log.Warnf("ClusterDisks: failed to load custom pricing: %s", err)
- }
- for _, result := range resPVCost {
- cluster := result.Cluster
- if cluster == "" {
- cluster = env.GetClusterID()
- }
- name := result.PersistentVolume
- if name == "" {
- log.Warnf("ClusterDisks: PV cost data missing persistentvolume")
- continue
- }
- // TODO niko/assets storage class
- var cost float64
- if customPricingEnabled && customPricingConfig != nil {
- customPVCostStr := customPricingConfig.Storage
- customPVCost, err := strconv.ParseFloat(customPVCostStr, 64)
- if err != nil {
- log.Warnf("ClusterDisks: error parsing custom PV price: %s", customPVCostStr)
- }
- cost = customPVCost
- } else {
- cost = result.Data[0].Value
- }
- key := DiskIdentifier{cluster, name}
- if _, ok := diskMap[key]; !ok {
- diskMap[key] = &Disk{
- Cluster: cluster,
- Name: name,
- Breakdown: &ClusterCostsBreakdown{},
- }
- }
- diskMap[key].Cost = cost * (diskMap[key].Bytes / 1024 / 1024 / 1024) * (diskMap[key].Minutes / 60)
- providerID := result.ProviderID // just put the providerID set up here, it's the simplest query.
- if providerID != "" {
- diskMap[key].ProviderID = provider.ParsePVID(providerID)
- }
- }
- for _, result := range resPVUsedAvg {
- cluster := result.Cluster
- if cluster == "" {
- cluster = env.GetClusterID()
- }
- claimName := result.PersistentVolumeClaim
- if claimName == "" {
- log.Debugf("ClusterDisks: pv usage data missing persistentvolumeclaim")
- continue
- }
- claimNamespace := result.Namespace
- if claimNamespace == "" {
- log.Debugf("ClusterDisks: pv usage data missing namespace")
- continue
- }
- var volumeName string
- for _, thatRes := range resPVCInfo {
- thatCluster := thatRes.Cluster
- if thatCluster == "" {
- thatCluster = env.GetClusterID()
- }
- thatVolumeName := thatRes.VolumeName
- if thatVolumeName == "" {
- log.Debugf("ClusterDisks: pv claim data missing volumename")
- continue
- }
- thatClaimName := thatRes.PersistentVolumeClaim
- if thatClaimName == "" {
- log.Debugf("ClusterDisks: pv claim data missing persistentvolumeclaim")
- continue
- }
- thatClaimNamespace := thatRes.Namespace
- if thatClaimNamespace == "" {
- log.Debugf("ClusterDisks: pv claim data missing namespace")
- continue
- }
- if cluster == thatCluster && claimName == thatClaimName && claimNamespace == thatClaimNamespace {
- volumeName = thatVolumeName
- }
- }
- usage := result.Data[0].Value
- key := DiskIdentifier{cluster, volumeName}
- if _, ok := diskMap[key]; !ok {
- diskMap[key] = &Disk{
- Cluster: cluster,
- Name: volumeName,
- Breakdown: &ClusterCostsBreakdown{},
- }
- }
- diskMap[key].BytesUsedAvgPtr = &usage
- }
- for _, result := range resPVUsedMax {
- cluster := result.Cluster
- if cluster == "" {
- cluster = env.GetClusterID()
- }
- claimName := result.PersistentVolumeClaim
- if claimName == "" {
- log.Debugf("ClusterDisks: pv usage data missing persistentvolumeclaim")
- continue
- }
- claimNamespace := result.Namespace
- if claimNamespace == "" {
- log.Debugf("ClusterDisks: pv usage data missing namespace")
- continue
- }
- var volumeName string
- for _, thatRes := range resPVCInfo {
- thatCluster := thatRes.Cluster
- if thatCluster == "" {
- thatCluster = env.GetClusterID()
- }
- thatVolumeName := thatRes.VolumeName
- if thatVolumeName == "" {
- log.Debugf("ClusterDisks: pv claim data missing volumename")
- continue
- }
- thatClaimName := thatRes.PersistentVolumeClaim
- if thatClaimName == "" {
- log.Debugf("ClusterDisks: pv claim data missing persistentvolumeclaim")
- continue
- }
- thatClaimNamespace := thatRes.Namespace
- if thatClaimNamespace == "" {
- log.Debugf("ClusterDisks: pv claim data missing namespace")
- continue
- }
- if cluster == thatCluster && claimName == thatClaimName && claimNamespace == thatClaimNamespace {
- volumeName = thatVolumeName
- }
- }
- usage := result.Data[0].Value
- key := DiskIdentifier{cluster, volumeName}
- if _, ok := diskMap[key]; !ok {
- diskMap[key] = &Disk{
- Cluster: cluster,
- Name: volumeName,
- Breakdown: &ClusterCostsBreakdown{},
- }
- }
- diskMap[key].BytesUsedMaxPtr = &usage
- }
- }
- // filterOutLocalPVs removes local Persistent Volumes (PVs) from the given disk map.
- // Local PVs are identified by the prefix "local-pv-" in their names, which is the
- // convention used by sig-storage-local-static-provisioner.
- //
- // Parameters:
- // - diskMap: A map of DiskIdentifier to Disk pointers, representing all PVs.
- //
- // Returns:
- // - A new map of DiskIdentifier to Disk pointers, containing only non-local PVs.
- func filterOutLocalPVs(diskMap map[DiskIdentifier]*Disk) map[DiskIdentifier]*Disk {
- nonLocalPVDiskMap := map[DiskIdentifier]*Disk{}
- for key, val := range diskMap {
- if !strings.HasPrefix(key.Name, SIG_STORAGE_LOCAL_PROVISIONER_PREFIX) {
- nonLocalPVDiskMap[key] = val
- }
- }
- return nonLocalPVDiskMap
- }
|