Просмотр исходного кода

Allocations: Add CPUCoreLimitAverage/RAMBytesLimitAverage (#3421)

Signed-off-by: Bianca Burtoiu <bianca.burtoiu@ibm.com>
Bianca Burtoiu 6 месяцев назад
Родитель
Сommit
955e58275a

+ 26 - 4
core/pkg/opencost/allocation.go

@@ -100,10 +100,12 @@ type Allocation struct {
 	// UnmountedPVCost is used to track how much of the cost in PVs is for an
 	// unmounted PV. It is not additive of PVCost() and need not be sent in API
 	// responses.
-	UnmountedPVCost             float64        `json:"-"`             //@bingen:field[ignore]
-	deprecatedGPURequestAverage float64        `json:"-"`             //@bingen:field[version=22]
-	deprecatedGPUUsageAverage   float64        `json:"-"`             //@bingen:field[version=22]
-	GPUAllocation               *GPUAllocation `json:"GPUAllocation"` //@bingen:field[version=23]
+	UnmountedPVCost             float64        `json:"-"`                   //@bingen:field[ignore]
+	deprecatedGPURequestAverage float64        `json:"-"`                   //@bingen:field[version=22]
+	deprecatedGPUUsageAverage   float64        `json:"-"`                   //@bingen:field[version=22]
+	GPUAllocation               *GPUAllocation `json:"GPUAllocation"`       //@bingen:field[version=23]
+	CPUCoreLimitAverage         float64        `json:"cpuCoreLimitAverage"` //@bingen:field[version=24]
+	RAMBytesLimitAverage        float64        `json:"ramByteLimitAverage"` //@bingen:field[version=24]
 }
 
 type GPUAllocation struct {
@@ -774,6 +776,8 @@ func (a *Allocation) Clone() *Allocation {
 		LoadBalancers:                  a.LoadBalancers.Clone(),
 		UnmountedPVCost:                a.UnmountedPVCost,
 		GPUAllocation:                  a.GPUAllocation.Clone(),
+		CPUCoreLimitAverage:            a.CPUCoreLimitAverage,
+		RAMBytesLimitAverage:           a.RAMBytesLimitAverage,
 	}
 }
 
@@ -1293,12 +1297,18 @@ func (a *Allocation) add(that *Allocation) {
 	cpuReqCoreMins := a.CPUCoreRequestAverage * a.Minutes()
 	cpuReqCoreMins += that.CPUCoreRequestAverage * that.Minutes()
 
+	cpuLimCoreMins := a.CPUCoreLimitAverage * a.Minutes()
+	cpuLimCoreMins += that.CPUCoreLimitAverage * that.Minutes()
+
 	cpuUseCoreMins := a.CPUCoreUsageAverage * a.Minutes()
 	cpuUseCoreMins += that.CPUCoreUsageAverage * that.Minutes()
 
 	ramReqByteMins := a.RAMBytesRequestAverage * a.Minutes()
 	ramReqByteMins += that.RAMBytesRequestAverage * that.Minutes()
 
+	ramLimByteMins := a.RAMBytesLimitAverage * a.Minutes()
+	ramLimByteMins += that.RAMBytesLimitAverage * that.Minutes()
+
 	ramUseByteMins := a.RAMBytesUsageAverage * a.Minutes()
 	ramUseByteMins += that.RAMBytesUsageAverage * that.Minutes()
 
@@ -1346,8 +1356,10 @@ func (a *Allocation) add(that *Allocation) {
 	// TODO:TEST write a unit test that fails if this is done incorrectly
 	if a.Minutes() > 0 {
 		a.CPUCoreRequestAverage = cpuReqCoreMins / a.Minutes()
+		a.CPUCoreLimitAverage = cpuLimCoreMins / a.Minutes()
 		a.CPUCoreUsageAverage = cpuUseCoreMins / a.Minutes()
 		a.RAMBytesRequestAverage = ramReqByteMins / a.Minutes()
+		a.RAMBytesLimitAverage = ramLimByteMins / a.Minutes()
 		a.RAMBytesUsageAverage = ramUseByteMins / a.Minutes()
 
 		if a.GPUAllocation != nil {
@@ -1367,8 +1379,10 @@ func (a *Allocation) add(that *Allocation) {
 		}
 	} else {
 		a.CPUCoreRequestAverage = 0.0
+		a.CPUCoreLimitAverage = 0.0
 		a.CPUCoreUsageAverage = 0.0
 		a.RAMBytesRequestAverage = 0.0
+		a.RAMBytesLimitAverage = 0.0
 		a.RAMBytesUsageAverage = 0.0
 
 		if a.GPUAllocation != nil {
@@ -2695,6 +2709,10 @@ func (a *Allocation) SanitizeNaN() {
 		log.DedupedWarningf(5, "Allocation: Unexpected NaN found for CPUCoreRequestAverage: name:%s, window:%s, properties:%s", a.Name, a.Window.String(), a.Properties.String())
 		a.CPUCoreRequestAverage = 0
 	}
+	if math.IsNaN(a.CPUCoreLimitAverage) {
+		log.DedupedWarningf(5, "Allocation: Unexpected NaN found for CPUCoreLimitAverage: name:%s, window:%s, properties:%s", a.Name, a.Window.String(), a.Properties.String())
+		a.CPUCoreLimitAverage = 0
+	}
 	if math.IsNaN(a.CPUCoreHours) {
 		log.DedupedWarningf(5, "Allocation: Unexpected NaN found for CPUCoreHours: name:%s, window:%s, properties:%s", a.Name, a.Window.String(), a.Properties.String())
 		a.CPUCoreHours = 0
@@ -2772,6 +2790,10 @@ func (a *Allocation) SanitizeNaN() {
 		log.DedupedWarningf(5, "Allocation: Unexpected NaN found for RAMBytesRequestAverage name:%s, window:%s, properties:%s", a.Name, a.Window.String(), a.Properties.String())
 		a.RAMBytesRequestAverage = 0
 	}
+	if math.IsNaN(a.RAMBytesLimitAverage) {
+		log.DedupedWarningf(5, "Allocation: Unexpected NaN found for RAMBytesLimitAverage name:%s, window:%s, properties:%s", a.Name, a.Window.String(), a.Properties.String())
+		a.RAMBytesLimitAverage = 0
+	}
 	if math.IsNaN(a.RAMBytesUsageAverage) {
 		log.DedupedWarningf(5, "Allocation: Unexpected NaN found for RAMBytesUsageAverage name:%s, window:%s, properties:%s", a.Name, a.Window.String(), a.Properties.String())
 		a.RAMBytesUsageAverage = 0

+ 4 - 0
core/pkg/opencost/allocation_json.go

@@ -19,6 +19,7 @@ type AllocationJSON struct {
 	Minutes                        *float64                        `json:"minutes"`
 	CPUCores                       *float64                        `json:"cpuCores"`
 	CPUCoreRequestAverage          *float64                        `json:"cpuCoreRequestAverage"`
+	CPUCoreLimitAverage            *float64                        `json:"cpuCoreLimitAverage"`
 	CPUCoreUsageAverage            *float64                        `json:"cpuCoreUsageAverage"`
 	CPUCoreHours                   *float64                        `json:"cpuCoreHours"`
 	CPUCost                        *float64                        `json:"cpuCost"`
@@ -49,6 +50,7 @@ type AllocationJSON struct {
 	PVCostAdjustment               *float64                        `json:"pvCostAdjustment"`
 	RAMBytes                       *float64                        `json:"ramBytes"`
 	RAMByteRequestAverage          *float64                        `json:"ramByteRequestAverage"`
+	RAMByteLimitAverage            *float64                        `json:"ramByteLimitAverage"`
 	RAMByteUsageAverage            *float64                        `json:"ramByteUsageAverage"`
 	RAMByteHours                   *float64                        `json:"ramByteHours"`
 	RAMCost                        *float64                        `json:"ramCost"`
@@ -78,6 +80,7 @@ func (aj *AllocationJSON) BuildFromAllocation(a *Allocation) {
 	aj.Minutes = formatFloat64ForResponse(a.Minutes())
 	aj.CPUCores = formatFloat64ForResponse(a.CPUCores())
 	aj.CPUCoreRequestAverage = formatFloat64ForResponse(a.CPUCoreRequestAverage)
+	aj.CPUCoreLimitAverage = formatFloat64ForResponse(a.CPUCoreLimitAverage)
 	aj.CPUCoreUsageAverage = formatFloat64ForResponse(a.CPUCoreUsageAverage)
 	aj.CPUCoreHours = formatFloat64ForResponse(a.CPUCoreHours)
 	aj.CPUCost = formatFloat64ForResponse(a.CPUCost)
@@ -106,6 +109,7 @@ func (aj *AllocationJSON) BuildFromAllocation(a *Allocation) {
 	aj.PVCostAdjustment = formatFloat64ForResponse(a.PVCostAdjustment)
 	aj.RAMBytes = formatFloat64ForResponse(a.RAMBytes())
 	aj.RAMByteRequestAverage = formatFloat64ForResponse(a.RAMBytesRequestAverage)
+	aj.RAMByteLimitAverage = formatFloat64ForResponse(a.RAMBytesLimitAverage)
 	aj.RAMByteUsageAverage = formatFloat64ForResponse(a.RAMBytesUsageAverage)
 	aj.RAMByteHours = formatFloat64ForResponse(a.RAMByteHours)
 	aj.RAMCost = formatFloat64ForResponse(a.RAMCost)

+ 1 - 1
core/pkg/opencost/bingen.go

@@ -44,7 +44,7 @@ package opencost
 // @bingen:end
 
 // Allocation Version Set: Includes Allocation pipeline specific resources
-// @bingen:set[name=Allocation,version=23]
+// @bingen:set[name=Allocation,version=24]
 // @bingen:generate[migrate]:Allocation
 // @bingen:generate[stringtable]:AllocationSet
 // @bingen:generate:AllocationSetRange

+ 25 - 6
core/pkg/opencost/opencost_codecs.go

@@ -13,12 +13,11 @@ package opencost
 
 import (
 	"fmt"
+	util "github.com/opencost/opencost/core/pkg/util"
 	"reflect"
 	"strings"
 	"sync"
 	"time"
-
-	util "github.com/opencost/opencost/core/pkg/util"
 )
 
 const (
@@ -34,9 +33,6 @@ const (
 )
 
 const (
-	// NetworkInsightCodecVersion is used for any resources listed in the NetworkInsight version set
-	NetworkInsightCodecVersion uint8 = 1
-
 	// DefaultCodecVersion is used for any resources listed in the Default version set
 	DefaultCodecVersion uint8 = 18
 
@@ -44,10 +40,13 @@ const (
 	AssetsCodecVersion uint8 = 21
 
 	// AllocationCodecVersion is used for any resources listed in the Allocation version set
-	AllocationCodecVersion uint8 = 23
+	AllocationCodecVersion uint8 = 24
 
 	// CloudCostCodecVersion is used for any resources listed in the CloudCost version set
 	CloudCostCodecVersion uint8 = 3
+
+	// NetworkInsightCodecVersion is used for any resources listed in the NetworkInsight version set
+	NetworkInsightCodecVersion uint8 = 1
 )
 
 //--------------------------------------------------------------------------
@@ -478,6 +477,8 @@ func (target *Allocation) MarshalBinaryWithContext(ctx *EncodingContext) (err er
 		// --- [end][write][struct](GPUAllocation) ---
 
 	}
+	buff.WriteFloat64(target.CPUCoreLimitAverage)  // write float64
+	buff.WriteFloat64(target.RAMBytesLimitAverage) // write float64
 	return nil
 }
 
@@ -829,6 +830,24 @@ func (target *Allocation) UnmarshalBinaryWithContext(ctx *DecodingContext) (err
 
 	}
 
+	// field version check
+	if uint8(24) <= version {
+		kkk := buff.ReadFloat64() // read float64
+		target.CPUCoreLimitAverage = kkk
+
+	} else {
+		target.CPUCoreLimitAverage = float64(0) // default
+	}
+
+	// field version check
+	if uint8(24) <= version {
+		lll := buff.ReadFloat64() // read float64
+		target.RAMBytesLimitAverage = lll
+
+	} else {
+		target.RAMBytesLimitAverage = float64(0) // default
+	}
+
 	// execute migration func if version delta detected
 	if version != AllocationCodecVersion {
 		migrateAllocation(target, version, AllocationCodecVersion)

+ 24 - 0
core/pkg/opencost/summaryallocation.go

@@ -27,6 +27,7 @@ type SummaryAllocation struct {
 	Start                  time.Time             `json:"start"`
 	End                    time.Time             `json:"end"`
 	CPUCoreRequestAverage  float64               `json:"cpuCoreRequestAverage"`
+	CPUCoreLimitAverage    float64               `json:"cpuCoreLimitAverage"`
 	CPUCoreUsageAverage    float64               `json:"cpuCoreUsageAverage"`
 	CPUCost                float64               `json:"cpuCost"`
 	CPUCostIdle            float64               `json:"cpuCostIdle"`
@@ -38,6 +39,7 @@ type SummaryAllocation struct {
 	LoadBalancerCost       float64               `json:"loadBalancerCost"`
 	PVCost                 float64               `json:"pvCost"`
 	RAMBytesRequestAverage float64               `json:"ramByteRequestAverage"`
+	RAMBytesLimitAverage   float64               `json:"ramByteLimitAverage"`
 	RAMBytesUsageAverage   float64               `json:"ramByteUsageAverage"`
 	RAMCost                float64               `json:"ramCost"`
 	RAMCostIdle            float64               `json:"ramCostIdle"`
@@ -69,6 +71,7 @@ func NewSummaryAllocation(alloc *Allocation, reconcile, reconcileNetwork bool) *
 		Start:                  alloc.Start,
 		End:                    alloc.End,
 		CPUCoreRequestAverage:  alloc.CPUCoreRequestAverage,
+		CPUCoreLimitAverage:    alloc.CPUCoreLimitAverage,
 		CPUCoreUsageAverage:    alloc.CPUCoreUsageAverage,
 		CPUCost:                alloc.CPUCost + alloc.CPUCostAdjustment,
 		CPUCostIdle:            alloc.CPUCostIdle,
@@ -80,6 +83,7 @@ func NewSummaryAllocation(alloc *Allocation, reconcile, reconcileNetwork bool) *
 		LoadBalancerCost:       alloc.LoadBalancerCost + alloc.LoadBalancerCostAdjustment,
 		PVCost:                 alloc.PVCost() + alloc.PVCostAdjustment,
 		RAMBytesRequestAverage: alloc.RAMBytesRequestAverage,
+		RAMBytesLimitAverage:   alloc.RAMBytesRequestAverage,
 		RAMBytesUsageAverage:   alloc.RAMBytesUsageAverage,
 		RAMCost:                alloc.RAMCost + alloc.RAMCostAdjustment,
 		RAMCostIdle:            alloc.RAMCostIdle,
@@ -128,12 +132,18 @@ func (sa *SummaryAllocation) Add(that *SummaryAllocation) error {
 	cpuReqCoreMins := sa.CPUCoreRequestAverage * sa.Minutes()
 	cpuReqCoreMins += that.CPUCoreRequestAverage * that.Minutes()
 
+	cpuLimCoreMins := sa.CPUCoreLimitAverage * sa.Minutes()
+	cpuLimCoreMins += that.CPUCoreLimitAverage * that.Minutes()
+
 	cpuUseCoreMins := sa.CPUCoreUsageAverage * sa.Minutes()
 	cpuUseCoreMins += that.CPUCoreUsageAverage * that.Minutes()
 
 	ramReqByteMins := sa.RAMBytesRequestAverage * sa.Minutes()
 	ramReqByteMins += that.RAMBytesRequestAverage * that.Minutes()
 
+	ramLimByteMins := sa.RAMBytesLimitAverage * sa.Minutes()
+	ramLimByteMins += that.RAMBytesLimitAverage * that.Minutes()
+
 	ramUseByteMins := sa.RAMBytesUsageAverage * sa.Minutes()
 	ramUseByteMins += that.RAMBytesUsageAverage * that.Minutes()
 
@@ -180,8 +190,10 @@ func (sa *SummaryAllocation) Add(that *SummaryAllocation) error {
 	// Convert cumulative request and usage back into rates
 	if sa.Minutes() > 0 {
 		sa.CPUCoreRequestAverage = cpuReqCoreMins / sa.Minutes()
+		sa.CPUCoreLimitAverage = cpuLimCoreMins / sa.Minutes()
 		sa.CPUCoreUsageAverage = cpuUseCoreMins / sa.Minutes()
 		sa.RAMBytesRequestAverage = ramReqByteMins / sa.Minutes()
+		sa.RAMBytesLimitAverage = ramLimByteMins / sa.Minutes()
 		sa.RAMBytesUsageAverage = ramUseByteMins / sa.Minutes()
 
 		var gpuReqAvgVal, gpuUsageAvgVal *float64
@@ -199,8 +211,10 @@ func (sa *SummaryAllocation) Add(that *SummaryAllocation) error {
 		sa.GPUUsageAverage = gpuUsageAvgVal
 	} else {
 		sa.CPUCoreRequestAverage = 0.0
+		sa.CPUCoreLimitAverage = 0.0
 		sa.CPUCoreUsageAverage = 0.0
 		sa.RAMBytesRequestAverage = 0.0
+		sa.RAMBytesLimitAverage = 0.0
 		sa.RAMBytesUsageAverage = 0.0
 		sa.GPURequestAverage = nil
 		sa.GPUUsageAverage = nil
@@ -228,6 +242,7 @@ func (sa *SummaryAllocation) Clone() *SummaryAllocation {
 		Start:                  sa.Start,
 		End:                    sa.End,
 		CPUCoreRequestAverage:  sa.CPUCoreRequestAverage,
+		CPUCoreLimitAverage:    sa.CPUCoreLimitAverage,
 		CPUCoreUsageAverage:    sa.CPUCoreUsageAverage,
 		CPUCost:                sa.CPUCost,
 		GPURequestAverage:      sa.GPURequestAverage,
@@ -237,6 +252,7 @@ func (sa *SummaryAllocation) Clone() *SummaryAllocation {
 		LoadBalancerCost:       sa.LoadBalancerCost,
 		PVCost:                 sa.PVCost,
 		RAMBytesRequestAverage: sa.RAMBytesRequestAverage,
+		RAMBytesLimitAverage:   sa.RAMBytesLimitAverage,
 		RAMBytesUsageAverage:   sa.RAMBytesUsageAverage,
 		RAMCost:                sa.RAMCost,
 		SharedCost:             sa.SharedCost,
@@ -285,6 +301,10 @@ func (sa *SummaryAllocation) Equal(that *SummaryAllocation) bool {
 		return false
 	}
 
+	if sa.CPUCoreLimitAverage != that.CPUCoreLimitAverage {
+		return false
+	}
+
 	if sa.CPUCoreUsageAverage != that.CPUCoreUsageAverage {
 		return false
 	}
@@ -321,6 +341,10 @@ func (sa *SummaryAllocation) Equal(that *SummaryAllocation) bool {
 		return false
 	}
 
+	if sa.RAMBytesLimitAverage != that.RAMBytesLimitAverage {
+		return false
+	}
+
 	if sa.RAMBytesUsageAverage != that.RAMBytesUsageAverage {
 		return false
 	}

+ 4 - 0
core/pkg/opencost/summaryallocation_json.go

@@ -13,6 +13,7 @@ type SummaryAllocationResponse struct {
 	Start                  time.Time `json:"start"`
 	End                    time.Time `json:"end"`
 	CPUCoreRequestAverage  *float64  `json:"cpuCoreRequestAverage"`
+	CPUCoreLimitAverage    *float64  `json:"cpuCoreLimitAverage"`
 	CPUCoreUsageAverage    *float64  `json:"cpuCoreUsageAverage"`
 	CPUCost                *float64  `json:"cpuCost"`
 	CPUCostIdle            *float64  `json:"cpuCostIdle"`
@@ -24,6 +25,7 @@ type SummaryAllocationResponse struct {
 	LoadBalancerCost       *float64  `json:"loadBalancerCost"`
 	PVCost                 *float64  `json:"pvCost"`
 	RAMBytesRequestAverage *float64  `json:"ramByteRequestAverage"`
+	RAMBytesLimitAverage   *float64  `json:"ramBytesLimitAverage"`
 	RAMBytesUsageAverage   *float64  `json:"ramByteUsageAverage"`
 	RAMCost                *float64  `json:"ramCost"`
 	RAMCostIdle            *float64  `json:"ramCostIdle"`
@@ -55,6 +57,7 @@ func (sa *SummaryAllocation) ToResponse() *SummaryAllocationResponse {
 		Start:                  sa.Start,
 		End:                    sa.End,
 		CPUCoreRequestAverage:  formatutil.Float64ToResponse(sa.CPUCoreRequestAverage),
+		CPUCoreLimitAverage:    formatutil.Float64ToResponse(sa.CPUCoreLimitAverage),
 		CPUCoreUsageAverage:    formatutil.Float64ToResponse(sa.CPUCoreUsageAverage),
 		CPUCost:                formatutil.Float64ToResponse(sa.CPUCost),
 		CPUCostIdle:            formatutil.Float64ToResponse(sa.CPUCostIdle),
@@ -66,6 +69,7 @@ func (sa *SummaryAllocation) ToResponse() *SummaryAllocationResponse {
 		LoadBalancerCost:       formatutil.Float64ToResponse(sa.LoadBalancerCost),
 		PVCost:                 formatutil.Float64ToResponse(sa.PVCost),
 		RAMBytesRequestAverage: formatutil.Float64ToResponse(sa.RAMBytesRequestAverage),
+		RAMBytesLimitAverage:   formatutil.Float64ToResponse(sa.RAMBytesLimitAverage),
 		RAMBytesUsageAverage:   formatutil.Float64ToResponse(sa.RAMBytesUsageAverage),
 		RAMCost:                formatutil.Float64ToResponse(sa.RAMCost),
 		RAMCostIdle:            formatutil.Float64ToResponse(sa.RAMCostIdle),

+ 2 - 0
core/pkg/source/datasource.go

@@ -49,6 +49,7 @@ type MetricsQuerier interface {
 	// RAM
 	QueryRAMBytesAllocated(start, end time.Time) *Future[RAMBytesAllocatedResult]
 	QueryRAMRequests(start, end time.Time) *Future[RAMRequestsResult]
+	QueryRAMLimits(start, end time.Time) *Future[RAMLimitsResult]
 	QueryRAMUsageAvg(start, end time.Time) *Future[RAMUsageAvgResult]
 	QueryRAMUsageMax(start, end time.Time) *Future[RAMUsageMaxResult]
 	QueryNodeRAMPricePerGiBHr(start, end time.Time) *Future[NodeRAMPricePerGiBHrResult]
@@ -56,6 +57,7 @@ type MetricsQuerier interface {
 	// CPU
 	QueryCPUCoresAllocated(start, end time.Time) *Future[CPUCoresAllocatedResult]
 	QueryCPURequests(start, end time.Time) *Future[CPURequestsResult]
+	QueryCPULimits(start, end time.Time) *Future[CPULimitsResult]
 	QueryCPUUsageAvg(start, end time.Time) *Future[CPUUsageAvgResult]
 	QueryCPUUsageMax(start, end time.Time) *Future[CPUUsageMaxResult]
 	QueryNodeCPUPricePerHr(start, end time.Time) *Future[NodeCPUPricePerHrResult]

+ 12 - 0
core/pkg/source/decoders.go

@@ -568,6 +568,12 @@ func DecodeRAMRequestsResult(result *QueryResult) *RAMRequestsResult {
 	return DecodeContainerMetricResult(result)
 }
 
+type RAMLimitsResult = ContainerMetricResult
+
+func DecodeRAMLimitsResult(result *QueryResult) *RAMLimitsResult {
+	return DecodeContainerMetricResult(result)
+}
+
 type RAMUsageAvgResult = ContainerMetricResult
 
 func DecodeRAMUsageAvgResult(result *QueryResult) *RAMUsageAvgResult {
@@ -618,6 +624,12 @@ func DecodeCPURequestsResult(result *QueryResult) *CPURequestsResult {
 	return DecodeContainerMetricResult(result)
 }
 
+type CPULimitsResult = ContainerMetricResult
+
+func DecodeCPULimitsResult(result *QueryResult) *CPULimitsResult {
+	return DecodeContainerMetricResult(result)
+}
+
 type CPUUsageAvgResult = ContainerMetricResult
 
 func DecodeCPUUsageAvgResult(result *QueryResult) *CPUUsageAvgResult {

+ 64 - 0
modules/collector-source/pkg/collector/collector.go

@@ -39,10 +39,12 @@ func NewOpenCostMetricStore() metric.MetricStore {
 	memStore.Register(NewPodActiveMinutesMetricCollector())
 	memStore.Register(NewRAMBytesAllocatedMetricCollector())
 	memStore.Register(NewRAMRequestsMetricCollector())
+	memStore.Register(NewRAMLimitsMetricCollector())
 	memStore.Register(NewRAMUsageAverageMetricCollector())
 	memStore.Register(NewRAMUsageMaxMetricCollector())
 	memStore.Register(NewCPUCoresAllocatedMetricCollector())
 	memStore.Register(NewCPURequestsMetricCollector())
+	memStore.Register(NewCPULimitsMetricCollector())
 	memStore.Register(NewCPUUsageAverageMetricCollector())
 	memStore.Register(NewCPUUsageMaxMetricCollector())
 	memStore.Register(NewGPUsRequestedMetricCollector())
@@ -711,6 +713,37 @@ func NewRAMRequestsMetricCollector() *metric.MetricCollector {
 	)
 }
 
+// avg(
+//	avg_over_time(
+//		kube_pod_container_resource_limits{
+//			resource="memory",
+//			unit="byte",
+//			container!="",
+//			container!="POD",
+//			node!="",
+//			<some_custom_filter>
+//		}[1h]
+//	)
+//) by (container, pod, namespace, node, cluster_id)
+
+func NewRAMLimitsMetricCollector() *metric.MetricCollector {
+	return metric.NewMetricCollector(
+		metric.RAMLimitsID,
+		metric.KubePodContainerResourceLimits,
+		[]string{
+			source.NodeLabel,
+			source.InstanceLabel,
+			source.NamespaceLabel,
+			source.PodLabel,
+			source.ContainerLabel,
+		},
+		aggregator.AverageOverTime,
+		func(labels map[string]string) bool {
+			return labels[source.ResourceLabel] == "memory" && labels[source.UnitLabel] == "byte" && labels[source.ContainerLabel] != "POD" && labels[source.ContainerLabel] != "" && labels[source.NodeLabel] != ""
+		},
+	)
+}
+
 // avg(
 // 		avg_over_time(
 // 			container_memory_working_set_bytes{
@@ -832,6 +865,37 @@ func NewCPURequestsMetricCollector() *metric.MetricCollector {
 	)
 }
 
+//	avg(
+//		avg_over_time(
+//			kube_pod_container_resource_limits{
+//				resource="cpu",
+//				unit="core",
+//				container!="",
+//				container!="POD",
+//				node!="",
+//				<some_custom_filter>
+//			}[1h]
+//		)
+//	) by (container, pod, namespace, node, cluster_id)
+
+func NewCPULimitsMetricCollector() *metric.MetricCollector {
+	return metric.NewMetricCollector(
+		metric.CPULimitsID,
+		metric.KubePodContainerResourceLimits,
+		[]string{
+			source.NodeLabel,
+			source.InstanceLabel,
+			source.NamespaceLabel,
+			source.PodLabel,
+			source.ContainerLabel,
+		},
+		aggregator.AverageOverTime,
+		func(labels map[string]string) bool {
+			return labels[source.ResourceLabel] == "cpu" && labels[source.UnitLabel] == "core" && labels[source.ContainerLabel] != "POD" && labels[source.ContainerLabel] != "" && labels[source.NodeLabel] != ""
+		},
+	)
+}
+
 //	avg(
 //		rate(
 //			container_cpu_usage_seconds_total{

+ 8 - 0
modules/collector-source/pkg/collector/metricsquerier.go

@@ -324,6 +324,10 @@ func (c *collectorMetricsQuerier) QueryRAMRequests(start, end time.Time) *source
 	return queryCollector(c, start, end, metric.RAMRequestsID, source.DecodeRAMRequestsResult)
 }
 
+func (c *collectorMetricsQuerier) QueryRAMLimits(start, end time.Time) *source.Future[source.RAMLimitsResult] {
+	return queryCollector(c, start, end, metric.RAMLimitsID, source.DecodeRAMLimitsResult)
+}
+
 func (c *collectorMetricsQuerier) QueryRAMUsageAvg(start, end time.Time) *source.Future[source.RAMUsageAvgResult] {
 	return queryCollector(c, start, end, metric.RAMUsageAverageID, source.DecodeRAMUsageAvgResult)
 }
@@ -344,6 +348,10 @@ func (c *collectorMetricsQuerier) QueryCPURequests(start, end time.Time) *source
 	return queryCollector(c, start, end, metric.CPURequestsID, source.DecodeCPURequestsResult)
 }
 
+func (c *collectorMetricsQuerier) QueryCPULimits(start, end time.Time) *source.Future[source.CPULimitsResult] {
+	return queryCollector(c, start, end, metric.CPULimitsID, source.DecodeCPULimitsResult)
+}
+
 func (c *collectorMetricsQuerier) QueryCPUUsageAvg(start, end time.Time) *source.Future[source.CPUUsageAvgResult] {
 	return queryCollector(c, start, end, metric.CPUUsageAverageID, source.DecodeCPUUsageAvgResult)
 }

+ 3 - 1
modules/collector-source/pkg/metric/collector.go

@@ -42,10 +42,12 @@ const (
 	PodActiveMinutesID              MetricCollectorID = "PodActiveMinutes"
 	RAMBytesAllocatedID             MetricCollectorID = "RAMBytesAllocated"
 	RAMRequestsID                   MetricCollectorID = "RAMRequests"
+	RAMLimitsID                     MetricCollectorID = "RAMLimits"
 	RAMUsageAverageID               MetricCollectorID = "RAMUsageAverage"
 	RAMUsageMaxID                   MetricCollectorID = "RAMUsageMax"
 	CPUCoresAllocatedID             MetricCollectorID = "CPUCoresAllocated"
 	CPURequestsID                   MetricCollectorID = "CPURequestsID"
+	CPULimitsID                     MetricCollectorID = "CPULimitsID"
 	CPUUsageAverageID               MetricCollectorID = "CPUUsageAverage"
 	CPUUsageMaxID                   MetricCollectorID = "CPUUsageMax"
 	GPUsRequestedID                 MetricCollectorID = "GPUsRequested"
@@ -148,4 +150,4 @@ func (mi *MetricCollector) Get() []*aggregator.MetricResult {
 
 func (mi *MetricCollector) Labels() []string {
 	return mi.labels
-}
+}

+ 2 - 1
modules/collector-source/pkg/metric/metrics.go

@@ -12,6 +12,7 @@ const (
 	KubePodOwner                                          = "kube_pod_owner"
 	KubePodContainerStatusRunning                         = "kube_pod_container_status_running"
 	KubePodContainerResourceRequests                      = "kube_pod_container_resource_requests"
+	KubePodContainerResourceLimits                        = "kube_pod_container_resource_limits"
 	KubePersistentVolumeClaimInfo                         = "kube_persistentvolumeclaim_info"
 	KubePersistentVolumeClaimResourceRequestsStorageBytes = "kube_persistentvolumeclaim_resource_requests_storage_bytes"
 	KubecostPVInfo                                        = "kubecost_pv_info"
@@ -58,4 +59,4 @@ const (
 	ContainerMemoryWorkingSetBytes     = "container_memory_working_set_bytes"
 	ContainerFSUsageBytes              = "container_fs_usage_bytes"
 	KubeletVolumeStatsUsedBytes        = "kubelet_volume_stats_used_bytes"
-)
+)

+ 26 - 0
modules/collector-source/pkg/scrape/clustercache.go

@@ -293,6 +293,32 @@ func (ccs *ClusterCacheScraper) scrapePods(pods []*clustercache.Pod) []metric.Up
 					})
 				}
 			}
+
+			// Limits
+			if container.Resources.Limits != nil {
+				// sorting keys here for testing purposes
+				keys := maps.Keys(container.Resources.Limits)
+				slices.Sort(keys)
+				for _, resourceName := range keys {
+					quantity := container.Resources.Limits[resourceName]
+					resource, unit, value := toResourceUnitValue(resourceName, quantity)
+
+					// failed to parse the resource type
+					if resource == "" {
+						log.DedupedWarningf(5, "Failed to parse resource units and quantity for resource: %s", resourceName)
+						continue
+					}
+
+					resourceLimitInfo := maps.Clone(containerInfo)
+					resourceLimitInfo[source.ResourceLabel] = resource
+					resourceLimitInfo[source.UnitLabel] = unit
+					scrapeResults = append(scrapeResults, metric.Update{
+						Name:   metric.KubePodContainerResourceLimits,
+						Labels: resourceLimitInfo,
+						Value:  value,
+					})
+				}
+			}
 		}
 	}
 

+ 34 - 0
modules/collector-source/pkg/scrape/clustercache_test.go

@@ -326,6 +326,10 @@ func Test_kubernetesScraper_scrapePods(t *testing.T) {
 												v1.ResourceCPU:    resource.MustParse("500m"),
 												v1.ResourceMemory: resource.MustParse("512"),
 											},
+											Limits: map[v1.ResourceName]resource.Quantity{
+												v1.ResourceCPU:    resource.MustParse("1"),
+												v1.ResourceMemory: resource.MustParse("1024"),
+											},
 										},
 									},
 								},
@@ -448,6 +452,36 @@ func Test_kubernetesScraper_scrapePods(t *testing.T) {
 					Value:          512,
 					AdditionalInfo: nil,
 				},
+				{
+					Name: metric.KubePodContainerResourceLimits,
+					Labels: map[string]string{
+						source.PodLabel:       "pod1",
+						source.NamespaceLabel: "namespace1",
+						source.UIDLabel:       "uuid1",
+						source.NodeLabel:      "node1",
+						source.InstanceLabel:  "node1",
+						source.ContainerLabel: "container1",
+						source.ResourceLabel:  "cpu",
+						source.UnitLabel:      "core",
+					},
+					Value:          1,
+					AdditionalInfo: nil,
+				},
+				{
+					Name: metric.KubePodContainerResourceLimits,
+					Labels: map[string]string{
+						source.PodLabel:       "pod1",
+						source.NamespaceLabel: "namespace1",
+						source.UIDLabel:       "uuid1",
+						source.NodeLabel:      "node1",
+						source.InstanceLabel:  "node1",
+						source.ContainerLabel: "container1",
+						source.ResourceLabel:  "memory",
+						source.UnitLabel:      "byte",
+					},
+					Value:          1024,
+					AdditionalInfo: nil,
+				},
 			},
 		},
 	}

+ 36 - 0
modules/prometheus-source/pkg/prom/metricsquerier.go

@@ -587,6 +587,24 @@ func (pds *PrometheusMetricsQuerier) QueryRAMRequests(start, end time.Time) *sou
 	return source.NewFuture(source.DecodeRAMRequestsResult, ctx.QueryAtTime(queryRAMRequests, end))
 }
 
+func (pds *PrometheusMetricsQuerier) QueryRAMLimits(start, end time.Time) *source.Future[source.RAMLimitsResult] {
+	const queryName = "QueryRAMLimits"
+	const queryFmtRAMLimits = `avg(avg_over_time(kube_pod_container_resource_limits{resource="memory", unit="byte", container!="", container!="POD", node!="", %s}[%s])) by (container, pod, namespace, node, %s)`
+
+	cfg := pds.promConfig
+
+	durStr := timeutil.DurationString(end.Sub(start))
+	if durStr == "" {
+		panic(fmt.Sprintf("failed to parse duration string passed to %s", queryName))
+	}
+
+	queryRAMLimits := fmt.Sprintf(queryFmtRAMLimits, cfg.ClusterFilter, durStr, cfg.ClusterLabel)
+	log.Debugf(PrometheusMetricsQueryLogFormat, queryName, end.Unix(), queryRAMLimits)
+
+	ctx := pds.promContexts.NewNamedContext(AllocationContextName)
+	return source.NewFuture(source.DecodeRAMLimitsResult, ctx.QueryAtTime(queryRAMLimits, end))
+}
+
 func (pds *PrometheusMetricsQuerier) QueryRAMUsageAvg(start, end time.Time) *source.Future[source.RAMUsageAvgResult] {
 	const queryName = "QueryRAMUsageAvg"
 	const queryFmtRAMUsageAvg = `avg(avg_over_time(container_memory_working_set_bytes{container!="", container_name!="POD", container!="POD", %s}[%s])) by (container_name, container, pod_name, pod, namespace, node, instance, uid, %s)`
@@ -659,6 +677,24 @@ func (pds *PrometheusMetricsQuerier) QueryCPURequests(start, end time.Time) *sou
 	return source.NewFuture(source.DecodeCPURequestsResult, ctx.QueryAtTime(queryCPURequests, end))
 }
 
+func (pds *PrometheusMetricsQuerier) QueryCPULimits(start, end time.Time) *source.Future[source.CPULimitsResult] {
+	const queryName = "QueryCPULimits"
+	const queryFmtCPULimits = `avg(avg_over_time(kube_pod_container_resource_limits{resource="cpu", unit="core", container!="", container!="POD", node!="", %s}[%s])) by (container, pod, namespace, node, %s)`
+
+	cfg := pds.promConfig
+
+	durStr := timeutil.DurationString(end.Sub(start))
+	if durStr == "" {
+		panic(fmt.Sprintf("failed to parse duration string passed to %s", queryName))
+	}
+
+	queryCPULimits := fmt.Sprintf(queryFmtCPULimits, cfg.ClusterFilter, durStr, cfg.ClusterLabel)
+	log.Debugf(PrometheusMetricsQueryLogFormat, queryName, end.Unix(), queryCPULimits)
+
+	ctx := pds.promContexts.NewNamedContext(AllocationContextName)
+	return source.NewFuture(source.DecodeCPULimitsResult, ctx.QueryAtTime(queryCPULimits, end))
+}
+
 func (pds *PrometheusMetricsQuerier) QueryCPUUsageAvg(start, end time.Time) *source.Future[source.CPUUsageAvgResult] {
 	const queryName = "QueryCPUUsageAvg"
 	const queryFmtCPUUsageAvg = `avg(rate(container_cpu_usage_seconds_total{container!="", container_name!="POD", container!="POD", %s}[%s])) by (container_name, container, pod_name, pod, namespace, node, instance, uid, %s)`

+ 2 - 0
modules/prometheus-source/pkg/prom/metricsquerier_test.go

@@ -123,11 +123,13 @@ func TestQueryLogs(t *testing.T) {
 		"QueryPodsUID":                      func(s, e time.Time) { querier.QueryPodsUID(s, e) },
 		"QueryRAMBytesAllocated":            func(s, e time.Time) { querier.QueryRAMBytesAllocated(s, e) },
 		"QueryRAMRequests":                  func(s, e time.Time) { querier.QueryRAMRequests(s, e) },
+		"QueryRAMLimits":                    func(s, e time.Time) { querier.QueryRAMLimits(s, e) },
 		"QueryRAMUsageAvg":                  func(s, e time.Time) { querier.QueryRAMUsageAvg(s, e) },
 		"QueryRAMUsageMax":                  func(s, e time.Time) { querier.QueryRAMUsageMax(s, e) },
 		"QueryNodeRAMPricePerGiBHr":         func(s, e time.Time) { querier.QueryNodeRAMPricePerGiBHr(s, e) },
 		"QueryCPUCoresAllocated":            func(s, e time.Time) { querier.QueryCPUCoresAllocated(s, e) },
 		"QueryCPURequests":                  func(s, e time.Time) { querier.QueryCPURequests(s, e) },
+		"QueryCPULimits":                    func(s, e time.Time) { querier.QueryCPULimits(s, e) },
 		"QueryCPUUsageAvg":                  func(s, e time.Time) { querier.QueryCPUUsageAvg(s, e) },
 		"QueryCPUUsageMax":                  func(s, e time.Time) { querier.QueryCPUUsageMax(s, e) },
 		"QueryNodeCPUPricePerHr":            func(s, e time.Time) { querier.QueryNodeCPUPricePerHr(s, e) },

+ 6 - 0
pkg/costmodel/allocation.go

@@ -274,11 +274,13 @@ func (cm *CostModel) computeAllocation(start, end time.Time) (*opencost.Allocati
 
 	resChRAMBytesAllocated := source.WithGroup(grp, ds.QueryRAMBytesAllocated(start, end))
 	resChRAMRequests := source.WithGroup(grp, ds.QueryRAMRequests(start, end))
+	resChRAMLimits := source.WithGroup(grp, ds.QueryRAMLimits(start, end))
 	resChRAMUsageAvg := source.WithGroup(grp, ds.QueryRAMUsageAvg(start, end))
 	resChRAMUsageMax := source.WithGroup(grp, ds.QueryRAMUsageMax(start, end))
 
 	resChCPUCoresAllocated := source.WithGroup(grp, ds.QueryCPUCoresAllocated(start, end))
 	resChCPURequests := source.WithGroup(grp, ds.QueryCPURequests(start, end))
+	resChCPULimits := source.WithGroup(grp, ds.QueryCPULimits(start, end))
 	resChCPUUsageAvg := source.WithGroup(grp, ds.QueryCPUUsageAvg(start, end))
 	resChCPUUsageMax := source.WithGroup(grp, ds.QueryCPUUsageMax(start, end))
 	resCPUUsageMax, _ := resChCPUUsageMax.Await()
@@ -349,9 +351,11 @@ func (cm *CostModel) computeAllocation(start, end time.Time) (*opencost.Allocati
 
 	resCPUCoresAllocated, _ := resChCPUCoresAllocated.Await()
 	resCPURequests, _ := resChCPURequests.Await()
+	resCPULimits, _ := resChCPULimits.Await()
 	resCPUUsageAvg, _ := resChCPUUsageAvg.Await()
 	resRAMBytesAllocated, _ := resChRAMBytesAllocated.Await()
 	resRAMRequests, _ := resChRAMRequests.Await()
+	resRAMLimits, _ := resChRAMLimits.Await()
 	resRAMUsageAvg, _ := resChRAMUsageAvg.Await()
 	resRAMUsageMax, _ := resChRAMUsageMax.Await()
 	resGPUsRequested, _ := resChGPUsRequested.Await()
@@ -418,10 +422,12 @@ func (cm *CostModel) computeAllocation(start, end time.Time) (*opencost.Allocati
 	// or equal to request.
 	applyCPUCoresAllocated(podMap, resCPUCoresAllocated, podUIDKeyMap)
 	applyCPUCoresRequested(podMap, resCPURequests, podUIDKeyMap)
+	applyCPUCoresLimits(podMap, resCPULimits, podUIDKeyMap)
 	applyCPUCoresUsedAvg(podMap, resCPUUsageAvg, podUIDKeyMap)
 	applyCPUCoresUsedMax(podMap, resCPUUsageMax, podUIDKeyMap)
 	applyRAMBytesAllocated(podMap, resRAMBytesAllocated, podUIDKeyMap)
 	applyRAMBytesRequested(podMap, resRAMRequests, podUIDKeyMap)
+	applyRAMBytesLimits(podMap, resRAMLimits, podUIDKeyMap)
 	applyRAMBytesUsedAvg(podMap, resRAMUsageAvg, podUIDKeyMap)
 	applyRAMBytesUsedMax(podMap, resRAMUsageMax, podUIDKeyMap)
 	applyGPUUsageAvg(podMap, resGPUsUsageAvg, podUIDKeyMap)

+ 82 - 0
pkg/costmodel/allocation_helpers.go

@@ -280,6 +280,47 @@ func applyCPUCoresRequested(podMap map[podKey]*pod, resCPUCoresRequested []*sour
 	}
 }
 
+func applyCPUCoresLimits(podMap map[podKey]*pod, resCPUCoresLimits []*source.CPULimitsResult, podUIDKeyMap map[podKey][]podKey) {
+	for _, res := range resCPUCoresLimits {
+		key, err := newResultPodKey(res.Cluster, res.Namespace, res.Pod)
+		if err != nil {
+			log.DedupedWarningf(10, "CostModel.ComputeAllocation: CPU limit result missing field: %s", err)
+			continue
+		}
+
+		container := res.Container
+		if container == "" {
+			log.DedupedWarningf(10, "CostModel.ComputeAllocation: CPU limit query result missing 'container': %s", key)
+			continue
+		}
+
+		var pods []*pod
+		if thisPod, ok := podMap[key]; !ok {
+			if uidKeys, ok := podUIDKeyMap[key]; ok {
+				for _, uidKey := range uidKeys {
+					thisPod, ok = podMap[uidKey]
+					if ok {
+						pods = append(pods, thisPod)
+					}
+				}
+			} else {
+				continue
+			}
+		} else {
+			pods = []*pod{thisPod}
+		}
+
+		for _, thisPod := range pods {
+
+			if _, ok := thisPod.Allocations[container]; !ok {
+				thisPod.appendContainer(container)
+			}
+
+			thisPod.Allocations[container].CPUCoreLimitAverage = res.Data[0].Value
+		}
+	}
+}
+
 func applyCPUCoresUsedAvg(podMap map[podKey]*pod, resCPUCoresUsedAvg []*source.CPUUsageAvgResult, podUIDKeyMap map[podKey][]podKey) {
 	for _, res := range resCPUCoresUsedAvg {
 		key, err := newResultPodKey(res.Cluster, res.Namespace, res.Pod)
@@ -478,6 +519,47 @@ func applyRAMBytesRequested(podMap map[podKey]*pod, resRAMBytesRequested []*sour
 	}
 }
 
+func applyRAMBytesLimits(podMap map[podKey]*pod, resRAMBytesLimits []*source.RAMLimitsResult, podUIDKeyMap map[podKey][]podKey) {
+	for _, res := range resRAMBytesLimits {
+		key, err := newResultPodKey(res.Cluster, res.Namespace, res.Pod)
+		if err != nil {
+			log.DedupedWarningf(10, "CostModel.ComputeAllocation: RAM limit result missing field: %s", err)
+			continue
+		}
+
+		container := res.Container
+		if container == "" {
+			log.DedupedWarningf(10, "CostModel.ComputeAllocation: RAM limit query result missing 'container': %s", key)
+			continue
+		}
+
+		var pods []*pod
+		if thisPod, ok := podMap[key]; !ok {
+			if uidKeys, ok := podUIDKeyMap[key]; ok {
+				for _, uidKey := range uidKeys {
+					thisPod, ok = podMap[uidKey]
+					if ok {
+						pods = append(pods, thisPod)
+					}
+				}
+			} else {
+				continue
+			}
+		} else {
+			pods = []*pod{thisPod}
+		}
+
+		for _, pod := range pods {
+
+			if _, ok := pod.Allocations[container]; !ok {
+				pod.appendContainer(container)
+			}
+
+			pod.Allocations[container].RAMBytesLimitAverage = res.Data[0].Value
+		}
+	}
+}
+
 func applyRAMBytesUsedAvg(podMap map[podKey]*pod, resRAMBytesUsedAvg []*source.RAMUsageAvgResult, podUIDKeyMap map[podKey][]podKey) {
 	for _, res := range resRAMBytesUsedAvg {
 		key, err := newResultPodKey(res.Cluster, res.Namespace, res.Pod)