| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- package costmodel
- import (
- "fmt"
- "time"
- "github.com/opencost/opencost/core/pkg/log"
- "github.com/opencost/opencost/core/pkg/opencost"
- )
- // clampTimeToRange does not permit timestamps to exceed a given start, end
- // range, inclusive of start and end times. For examples:
- //
- // If time is within (start, end) inclusive, return that time:
- //
- // > S----T-------------E => T
- //
- // If time is before start, return start:
- //
- // > T S------------------E => S
- //
- // If time is after end, return end:
- //
- // > S------------------E T => E
- //
- // Note: if this function encounters a "zero" time (either time.Zero or Unix
- // timestamp 0) the time returned will be the given start time.
- func clampTimeToRange(t time.Time, start, end time.Time) time.Time {
- if t.Before(start) {
- return start
- }
- if t.After(end) {
- return end
- }
- return t
- }
- func (cm *CostModel) ComputeAssets(start, end time.Time) (*opencost.AssetSet, error) {
- assetSet := opencost.NewAssetSet(start, end)
- nodeMap, err := cm.ClusterNodes(start, end)
- if err != nil {
- return nil, fmt.Errorf("error computing node assets for %s: %w", opencost.NewClosedWindow(start, end), err)
- }
- lbMap, err := cm.ClusterLoadBalancers(start, end)
- if err != nil {
- return nil, fmt.Errorf("error computing load balancer assets for %s: %w", opencost.NewClosedWindow(start, end), err)
- }
- diskMap, err := cm.ClusterDisks(start, end)
- if err != nil {
- return nil, fmt.Errorf("error computing disk assets for %s: %w", opencost.NewClosedWindow(start, end), err)
- }
- clusterManagement, err := cm.ClusterManagement(start, end)
- if err != nil {
- return nil, fmt.Errorf("error computing cluster management assets for %s: %w", opencost.NewClosedWindow(start, end), err)
- }
- for _, d := range diskMap {
- // Clamp the start and end fields to the start and end of the window.
- // In the case that start and end are missing (e.g. due to the "active
- // minutes" metric being absent), both times will be set to the start
- // of the window -- representing zero "runtime" within the window.
- s := clampTimeToRange(d.Start, start, end)
- e := clampTimeToRange(d.End, start, end)
- hours := e.Sub(s).Hours()
- disk := opencost.NewDisk(d.Name, d.Cluster, d.ProviderID, s, e, opencost.NewWindow(&start, &end))
- cm.PropertiesFromCluster(disk.Properties)
- disk.Cost = d.Cost
- disk.ByteHours = d.Bytes * hours
- if d.BytesUsedAvgPtr != nil {
- byteHours := *d.BytesUsedAvgPtr * hours
- disk.ByteHoursUsed = &byteHours
- }
- if d.BytesUsedMaxPtr != nil {
- usageMax := *d.BytesUsedMaxPtr
- disk.ByteUsageMax = &usageMax
- }
- if d.Local {
- disk.Local = 1.0
- }
- disk.Breakdown = &opencost.Breakdown{
- Idle: d.Breakdown.Idle,
- System: d.Breakdown.System,
- User: d.Breakdown.User,
- Other: d.Breakdown.Other,
- }
- disk.StorageClass = d.StorageClass
- disk.VolumeName = d.VolumeName
- disk.ClaimName = d.ClaimName
- disk.ClaimNamespace = d.ClaimNamespace
- assetSet.Insert(disk, nil)
- }
- for _, lb := range lbMap {
- // Clamp the start and end fields to the start and end of the window.
- // In the case that start and end are missing (e.g. due to the "active
- // minutes" metric being absent), both times will be set to the start
- // of the window -- representing zero "runtime" within the window.
- s := clampTimeToRange(lb.Start, start, end)
- e := clampTimeToRange(lb.End, start, end)
- loadBalancer := opencost.NewLoadBalancer(lb.Name, lb.Cluster, lb.ProviderID, s, e, opencost.NewWindow(&start, &end), lb.Private, lb.Ip)
- cm.PropertiesFromCluster(loadBalancer.Properties)
- loadBalancer.Cost = lb.Cost
- assetSet.Insert(loadBalancer, nil)
- }
- for _, cman := range clusterManagement {
- cmAsset := opencost.NewClusterManagement(cman.Provisioner, cman.Cluster, opencost.NewClosedWindow(start, end))
- cm.PropertiesFromCluster(cmAsset.Properties)
- cmAsset.Cost = cman.Cost
- assetSet.Insert(cmAsset, nil)
- }
- for _, n := range nodeMap {
- // check label, to see if node from fargate, if so ignore.
- if n.Labels != nil {
- if value, ok := n.Labels["label_eks_amazonaws_com_compute_type"]; ok && value == "fargate" {
- continue
- }
- }
- // Clamp the start and end fields to the start and end of the window.
- // In the case that start and end are missing (e.g. due to the "active
- // minutes" metric being absent), both times will be set to the start
- // of the window -- representing zero "runtime" within the window.
- s := clampTimeToRange(n.Start, start, end)
- e := clampTimeToRange(n.End, start, end)
- hours := e.Sub(s).Hours()
- node := opencost.NewNode(n.Name, n.Cluster, n.ProviderID, s, e, opencost.NewWindow(&start, &end))
- cm.PropertiesFromCluster(node.Properties)
- node.NodeType = n.NodeType
- node.CPUCoreHours = n.CPUCores * hours
- node.RAMByteHours = n.RAMBytes * hours
- node.GPUHours = n.GPUCount * hours
- node.CPUBreakdown = &opencost.Breakdown{
- Idle: n.CPUBreakdown.Idle,
- System: n.CPUBreakdown.System,
- User: n.CPUBreakdown.User,
- Other: n.CPUBreakdown.Other,
- }
- node.RAMBreakdown = &opencost.Breakdown{
- Idle: n.RAMBreakdown.Idle,
- System: n.RAMBreakdown.System,
- User: n.RAMBreakdown.User,
- Other: n.RAMBreakdown.Other,
- }
- node.CPUCost = n.CPUCost
- node.GPUCost = n.GPUCost
- node.GPUCount = n.GPUCount
- node.RAMCost = n.RAMCost
- if n.Overhead != nil {
- node.Overhead = &opencost.NodeOverhead{
- RamOverheadFraction: n.Overhead.RamOverheadFraction,
- CpuOverheadFraction: n.Overhead.CpuOverheadFraction,
- OverheadCostFraction: ((n.Overhead.CpuOverheadFraction * n.CPUCost) +
- (n.Overhead.RamOverheadFraction * n.RAMCost)) / node.TotalCost(),
- }
- } else {
- node.Overhead = &opencost.NodeOverhead{}
- }
- node.Discount = n.Discount
- if n.Preemptible {
- node.Preemptible = 1.0
- }
- node.SetLabels(opencost.AssetLabels(n.Labels))
- assetSet.Insert(node, nil)
- }
- return assetSet, nil
- }
- func (cm *CostModel) ClusterDisks(start, end time.Time) (map[DiskIdentifier]*Disk, error) {
- return ClusterDisks(cm.DataSource, cm.Provider, start, end)
- }
- func (cm *CostModel) ClusterLoadBalancers(start, end time.Time) (map[LoadBalancerIdentifier]*LoadBalancer, error) {
- return ClusterLoadBalancers(cm.DataSource, start, end)
- }
- func (cm *CostModel) ClusterNodes(start, end time.Time) (map[NodeIdentifier]*Node, error) {
- return ClusterNodes(cm.DataSource, cm.Provider, start, end)
- }
- func (cm *CostModel) ClusterManagement(start, end time.Time) (map[ClusterManagementIdentifier]*ClusterManagementCost, error) {
- return ClusterManagement(cm.DataSource, start, end)
- }
- // propertiesFromCluster populates static cluster properties to individual asset properties
- func (cm *CostModel) PropertiesFromCluster(props *opencost.AssetProperties) {
- // If properties does not have cluster value, do nothing
- if props.Cluster == "" {
- return
- }
- clusterMap := cm.ClusterMap.AsMap()
- ci, ok := clusterMap[props.Cluster]
- if !ok {
- log.Debugf("CostMode.PropertiesFromCluster: cluster '%s' was not found in ClusterMap", props.Cluster)
- return
- }
- props.Project = ci.Project
- props.Account = ci.Account
- props.Provider = ci.Provider
- }
|