diff --git a/vertical-pod-autoscaler/pkg/recommender/logic/estimator.go b/vertical-pod-autoscaler/pkg/recommender/logic/estimator.go index 079de558f663..a444d469a8b5 100644 --- a/vertical-pod-autoscaler/pkg/recommender/logic/estimator.go +++ b/vertical-pod-autoscaler/pkg/recommender/logic/estimator.go @@ -23,8 +23,6 @@ import ( "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/model" ) -// TODO: Split the estimator to have a separate estimator object for CPU and memory. - // ResourceEstimator is a function from AggregateContainerState to // model.Resources, e.g. a prediction of resources needed by a group of // containers. @@ -32,74 +30,146 @@ type ResourceEstimator interface { GetResourceEstimation(s *model.AggregateContainerState) model.Resources } -// Implementation of ResourceEstimator that returns constant amount of -// resources. This can be used as by a fake recommender for test purposes. -type constEstimator struct { - resources model.Resources +// CPUEstimator predicts CPU resources needed by a container +type CPUEstimator interface { + GetCPUEstimation(s *model.AggregateContainerState) model.ResourceAmount +} + +// MemoryEstimator predicts memory resources needed by a container +type MemoryEstimator interface { + GetMemoryEstimation(s *model.AggregateContainerState) model.ResourceAmount +} + +// combinedEstimator is a ResourceEstimator that combines two estimators: one for CPU and one for memory. +type combinedEstimator struct { + cpuEstimator CPUEstimator + memoryEstimator MemoryEstimator } -// Simple implementation of the ResourceEstimator interface. It returns specific -// percentiles of CPU usage distribution and memory peaks distribution. -type percentileEstimator struct { - cpuPercentile float64 - memoryPercentile float64 +type percentileCPUEstimator struct { + percentile float64 } -type marginEstimator struct { +type percentileMemoryEstimator struct { + percentile float64 +} + +// margins + +type cpuMarginEstimator struct { marginFraction float64 - baseEstimator ResourceEstimator + baseEstimator CPUEstimator } -type minResourcesEstimator struct { - minResources model.Resources - baseEstimator ResourceEstimator +type memoryMarginEstimator struct { + marginFraction float64 + baseEstimator MemoryEstimator } -type confidenceMultiplier struct { +type cpuConfidenceMultiplier struct { multiplier float64 exponent float64 - baseEstimator ResourceEstimator + baseEstimator CPUEstimator +} + +type memoryConfidenceMultiplier struct { + multiplier float64 + exponent float64 + baseEstimator MemoryEstimator +} + +type cpuMinResourceEstimator struct { + minResource model.ResourceAmount + baseEstimator CPUEstimator +} + +type memoryMinResourceEstimator struct { + minResource model.ResourceAmount + baseEstimator MemoryEstimator } -// NewConstEstimator returns a new constEstimator with given resources. -func NewConstEstimator(resources model.Resources) ResourceEstimator { - return &constEstimator{resources} +// NewCombinedEstimator returns a new combinedEstimator that uses provided estimators. +func NewCombinedEstimator(cpuEstimator CPUEstimator, memoryEstimator MemoryEstimator) ResourceEstimator { + return &combinedEstimator{cpuEstimator, memoryEstimator} } -// NewPercentileEstimator returns a new percentileEstimator that uses provided percentiles. -func NewPercentileEstimator(cpuPercentile float64, memoryPercentile float64) ResourceEstimator { - return &percentileEstimator{cpuPercentile, memoryPercentile} +// NewPercentileCPUEstimator returns a new percentileCPUEstimator that uses provided percentile. +func NewPercentileCPUEstimator(percentile float64) CPUEstimator { + return &percentileCPUEstimator{percentile} } -// WithMargin returns a given ResourceEstimator with margin applied. -// The returned resources are equal to the original resources plus (originalResource * marginFraction) -func WithMargin(marginFraction float64, baseEstimator ResourceEstimator) ResourceEstimator { - return &marginEstimator{marginFraction, baseEstimator} +// NewPercentileMemoryEstimator returns a new percentileMemoryEstimator that uses provided percentile. +func NewPercentileMemoryEstimator(percentile float64) MemoryEstimator { + return &percentileMemoryEstimator{percentile} } -// WithMinResources returns a given ResourceEstimator with minResources applied. -// The returned resources are equal to the max(original resources, minResources) -func WithMinResources(minResources model.Resources, baseEstimator ResourceEstimator) ResourceEstimator { - return &minResourcesEstimator{minResources, baseEstimator} +// NewMemoryEstimator returns a new percentileMemoryEstimator that uses provided percentile. +func NewMemoryEstimator(percentile float64) MemoryEstimator { + return &percentileMemoryEstimator{percentile} } -// WithConfidenceMultiplier returns a given ResourceEstimator with confidenceMultiplier applied. -func WithConfidenceMultiplier(multiplier, exponent float64, baseEstimator ResourceEstimator) ResourceEstimator { - return &confidenceMultiplier{multiplier, exponent, baseEstimator} +// GetCPUEstimation returns the CPU estimation for the given AggregateContainerState. +func (e *cpuMarginEstimator) GetCPUEstimation(s *model.AggregateContainerState) model.ResourceAmount { + base := e.baseEstimator.GetCPUEstimation(s) + margin := model.ScaleResource(base, e.marginFraction) + return base + margin } -// Returns a constant amount of resources. -func (e *constEstimator) GetResourceEstimation(s *model.AggregateContainerState) model.Resources { - return e.resources +// GetMemoryEstimation returns the memory estimation for the given AggregateContainerState. +func (e *memoryMarginEstimator) GetMemoryEstimation(s *model.AggregateContainerState) model.ResourceAmount { + base := e.baseEstimator.GetMemoryEstimation(s) + margin := model.ScaleResource(base, e.marginFraction) + return base + margin } -// Returns specific percentiles of CPU and memory peaks distributions. -func (e *percentileEstimator) GetResourceEstimation(s *model.AggregateContainerState) model.Resources { +// WithCPUMargin returns a CPUEstimator that adds a margin to the base estimator. +func WithCPUMargin(marginFraction float64, baseEstimator CPUEstimator) CPUEstimator { + return &cpuMarginEstimator{marginFraction: marginFraction, baseEstimator: baseEstimator} +} + +// WithMemoryMargin returns a MemoryEstimator that adds a margin to the base estimator. +func WithMemoryMargin(marginFraction float64, baseEstimator MemoryEstimator) MemoryEstimator { + return &memoryMarginEstimator{marginFraction: marginFraction, baseEstimator: baseEstimator} +} + +// WithCPUConfidenceMultiplier return a CPUEstimator estimator +func WithCPUConfidenceMultiplier(multiplier, exponent float64, baseEstimator CPUEstimator) CPUEstimator { + return &cpuConfidenceMultiplier{ + multiplier: multiplier, + exponent: exponent, + baseEstimator: baseEstimator, + } +} + +// WithMemoryConfidenceMultiplier returns a MemoryEstimator that scales the +func WithMemoryConfidenceMultiplier(multiplier, exponent float64, baseEstimator MemoryEstimator) MemoryEstimator { + return &memoryConfidenceMultiplier{ + multiplier: multiplier, + exponent: exponent, + baseEstimator: baseEstimator, + } +} + +func (e *percentileCPUEstimator) GetCPUEstimation(s *model.AggregateContainerState) model.ResourceAmount { + return model.CPUAmountFromCores(s.AggregateCPUUsage.Percentile(e.percentile)) +} + +func (e *percentileMemoryEstimator) GetMemoryEstimation(s *model.AggregateContainerState) model.ResourceAmount { + return model.MemoryAmountFromBytes(s.AggregateMemoryPeaks.Percentile(e.percentile)) +} + +// Returns resources computed by the underlying estimators, scaled based on the +// confidence metric, which depends on the amount of available historical data. +// Each resource is transformed as follows: +// +// scaledResource = originalResource * (1 + 1/confidence)^exponent. +// +// This can be used to widen or narrow the gap between the lower and upper bound +// estimators depending on how much input data is available to the estimators. +func (c *combinedEstimator) GetResourceEstimation(s *model.AggregateContainerState) model.Resources { return model.Resources{ - model.ResourceCPU: model.CPUAmountFromCores( - s.AggregateCPUUsage.Percentile(e.cpuPercentile)), - model.ResourceMemory: model.MemoryAmountFromBytes( - s.AggregateMemoryPeaks.Percentile(e.memoryPercentile)), + model.ResourceCPU: c.cpuEstimator.GetCPUEstimation(s), + model.ResourceMemory: c.memoryEstimator.GetMemoryEstimation(s), } } @@ -118,43 +188,58 @@ func getConfidence(s *model.AggregateContainerState) float64 { return math.Min(lifespanInDays, samplesAmount) } -// Returns resources computed by the underlying estimator, scaled based on the -// confidence metric, which depends on the amount of available historical data. -// Each resource is transformed as follows: -// -// scaledResource = originalResource * (1 + 1/confidence)^exponent. -// -// This can be used to widen or narrow the gap between the lower and upper bound -// estimators depending on how much input data is available to the estimators. -func (e *confidenceMultiplier) GetResourceEstimation(s *model.AggregateContainerState) model.Resources { +func (e *cpuConfidenceMultiplier) GetCPUEstimation(s *model.AggregateContainerState) model.ResourceAmount { confidence := getConfidence(s) - originalResources := e.baseEstimator.GetResourceEstimation(s) - scaledResources := make(model.Resources) - for resource, resourceAmount := range originalResources { - scaledResources[resource] = model.ScaleResource( - resourceAmount, math.Pow(1.+e.multiplier/confidence, e.exponent)) - } - return scaledResources + base := e.baseEstimator.GetCPUEstimation(s) + return model.ScaleResource(base, math.Pow(1.+e.multiplier/confidence, e.exponent)) } -func (e *marginEstimator) GetResourceEstimation(s *model.AggregateContainerState) model.Resources { - originalResources := e.baseEstimator.GetResourceEstimation(s) - newResources := make(model.Resources) - for resource, resourceAmount := range originalResources { - margin := model.ScaleResource(resourceAmount, e.marginFraction) - newResources[resource] = originalResources[resource] + margin - } - return newResources +func (e *memoryConfidenceMultiplier) GetMemoryEstimation(s *model.AggregateContainerState) model.ResourceAmount { + confidence := getConfidence(s) + base := e.baseEstimator.GetMemoryEstimation(s) + return model.ScaleResource(base, math.Pow(1.+e.multiplier/confidence, e.exponent)) } -func (e *minResourcesEstimator) GetResourceEstimation(s *model.AggregateContainerState) model.Resources { - originalResources := e.baseEstimator.GetResourceEstimation(s) - newResources := make(model.Resources) - for resource, resourceAmount := range originalResources { - if resourceAmount < e.minResources[resource] { - resourceAmount = e.minResources[resource] - } - newResources[resource] = resourceAmount - } - return newResources +// WithCPUMinResource returns a CPUEstimator that returns at least minResource +func WithCPUMinResource(minResource model.ResourceAmount, baseEstimator CPUEstimator) CPUEstimator { + return &cpuMinResourceEstimator{minResource, baseEstimator} +} + +// WithMemoryMinResource returns a MemoryEstimator that returns at least minResource +func WithMemoryMinResource(minResource model.ResourceAmount, baseEstimator MemoryEstimator) MemoryEstimator { + return &memoryMinResourceEstimator{minResource, baseEstimator} +} + +func (e *cpuMinResourceEstimator) GetCPUEstimation(s *model.AggregateContainerState) model.ResourceAmount { + return model.ResourceAmountMax(e.baseEstimator.GetCPUEstimation(s), e.minResource) +} + +func (e *memoryMinResourceEstimator) GetMemoryEstimation(s *model.AggregateContainerState) model.ResourceAmount { + return model.ResourceAmountMax(e.baseEstimator.GetMemoryEstimation(s), e.minResource) +} + +// NewConstMemoryEstimator returns a Memory estimator that always returns the same value +func NewConstMemoryEstimator(memory model.ResourceAmount) MemoryEstimator { + return &constMemoryEstimator{memory} +} + +type constCPUEstimator struct { + value model.ResourceAmount +} + +type constMemoryEstimator struct { + value model.ResourceAmount +} + +func (e *constCPUEstimator) GetCPUEstimation(_ *model.AggregateContainerState) model.ResourceAmount { + return e.value +} + +func (e *constMemoryEstimator) GetMemoryEstimation(_ *model.AggregateContainerState) model.ResourceAmount { + return e.value +} + +// NewConstCPUEstimator returns a CPU estimator that always returns the same value +func NewConstCPUEstimator(cpu model.ResourceAmount) CPUEstimator { + return &constCPUEstimator{cpu} } diff --git a/vertical-pod-autoscaler/pkg/recommender/logic/estimator_test.go b/vertical-pod-autoscaler/pkg/recommender/logic/estimator_test.go index 4e83e448ad7a..0f80f8254360 100644 --- a/vertical-pod-autoscaler/pkg/recommender/logic/estimator_test.go +++ b/vertical-pod-autoscaler/pkg/recommender/logic/estimator_test.go @@ -26,11 +26,7 @@ import ( ) var ( - anyTime = time.Unix(0, 0) - testRequest = model.Resources{ - model.ResourceCPU: model.CPUAmountFromCores(3.14), - model.ResourceMemory: model.MemoryAmountFromBytes(3.14e9), - } + anyTime = time.Unix(0, 0) ) // Verifies that the PercentileEstimator returns requested percentiles of CPU @@ -50,9 +46,11 @@ func TestPercentileEstimator(t *testing.T) { // Create an estimator. CPUPercentile := 0.2 MemoryPercentile := 0.5 - estimator := NewPercentileEstimator(CPUPercentile, MemoryPercentile) + cpuEstimator := NewPercentileCPUEstimator(CPUPercentile) + memoryEstimator := NewPercentileMemoryEstimator(MemoryPercentile) + combinedEstimator := NewCombinedEstimator(cpuEstimator, memoryEstimator) - resourceEstimation := estimator.GetResourceEstimation( + resourceEstimation := combinedEstimator.GetResourceEstimation( &model.AggregateContainerState{ AggregateCPUUsage: cpuHistogram, AggregateMemoryPeaks: memoryPeaksHistogram, @@ -67,69 +65,51 @@ func TestPercentileEstimator(t *testing.T) { // returned by the base estimator according to the formula, using the calculated // confidence. func TestConfidenceMultiplier(t *testing.T) { - baseEstimator := NewConstEstimator(model.Resources{ - model.ResourceCPU: model.CPUAmountFromCores(3.14), - model.ResourceMemory: model.MemoryAmountFromBytes(3.14e9), - }) - testedEstimator := &confidenceMultiplier{ - multiplier: 0.1, - exponent: 2.0, - baseEstimator: baseEstimator, - } + baseCPUEstimator := NewConstCPUEstimator(model.CPUAmountFromCores(3.14)) + baseMemoryEstimator := NewConstMemoryEstimator(model.MemoryAmountFromBytes(3.14e9)) + testedCPU1 := WithCPUConfidenceMultiplier(1.0, 1.0, baseCPUEstimator) + testedMemory1 := WithMemoryConfidenceMultiplier(1.0, 1.0, baseMemoryEstimator) + testedEstimator1 := NewCombinedEstimator(testedCPU1, testedMemory1) - s := model.NewAggregateContainerState() - // Add 9 CPU samples at the frequency of 1/(2 mins). - timestamp := anyTime - for i := 1; i <= 9; i++ { - s.AddSample(&model.ContainerUsageSample{ - MeasureStart: timestamp, - Usage: model.CPUAmountFromCores(1.0), - Request: testRequest[model.ResourceCPU], - Resource: model.ResourceCPU, - }) - timestamp = timestamp.Add(time.Minute * 2) - } + testedCPU2 := WithCPUConfidenceMultiplier(1.0, -1.0, baseCPUEstimator) + testedMemory2 := WithMemoryConfidenceMultiplier(1.0, -1.0, baseMemoryEstimator) + testedEstimator2 := NewCombinedEstimator(testedCPU2, testedMemory2) - // Expected confidence = 9/(60*24) = 0.00625. - assert.Equal(t, 0.00625, getConfidence(s)) - // Expected CPU estimation = 3.14 * (1 + 1/confidence)^exponent = - // 3.14 * (1 + 0.1/0.00625)^2 = 907.46. - resourceEstimation := testedEstimator.GetResourceEstimation(s) - assert.Equal(t, 907.46, model.CoresFromCPUAmount(resourceEstimation[model.ResourceCPU])) + s := model.NewAggregateContainerState() + // Expect testedEstimator1 to return the maximum possible resource amount. + assert.Equal(t, model.ResourceAmount(1e14), + testedEstimator1.GetResourceEstimation(s)[model.ResourceCPU]) + // Expect testedEstimator2 to return zero. + assert.Equal(t, model.ResourceAmount(0), + testedEstimator2.GetResourceEstimation(s)[model.ResourceCPU]) } // Verifies that the confidenceMultiplier works for the case of no // history. This corresponds to the multiplier of +INF or 0 (depending on the // sign of the exponent). func TestConfidenceMultiplierNoHistory(t *testing.T) { - baseEstimator := NewConstEstimator(model.Resources{ - model.ResourceCPU: model.CPUAmountFromCores(3.14), - model.ResourceMemory: model.MemoryAmountFromBytes(3.14e9), - }) - testedEstimator1 := &confidenceMultiplier{1.0, 1.0, baseEstimator} - testedEstimator2 := &confidenceMultiplier{1.0, -1.0, baseEstimator} + baseCPUEstimator := NewConstCPUEstimator(model.CPUAmountFromCores(3.14)) + baseMemoryEstimator := NewConstMemoryEstimator(model.MemoryAmountFromBytes(3.14e9)) + testedCPU := WithCPUConfidenceMultiplier(0.1, 2.0, baseCPUEstimator) + testedMemory := WithMemoryConfidenceMultiplier(0.1, 2.0, baseMemoryEstimator) + testedEstimator := NewCombinedEstimator(testedCPU, testedMemory) s := model.NewAggregateContainerState() - // Expect testedEstimator1 to return the maximum possible resource amount. + // Expect testedEstimator to return the maximum possible resource amount. assert.Equal(t, model.ResourceAmount(1e14), - testedEstimator1.GetResourceEstimation(s)[model.ResourceCPU]) - // Expect testedEstimator2 to return zero. - assert.Equal(t, model.ResourceAmount(0), - testedEstimator2.GetResourceEstimation(s)[model.ResourceCPU]) + testedEstimator.GetResourceEstimation(s)[model.ResourceCPU]) } // Verifies that the MarginEstimator adds margin to the originally // estimated resources. func TestMarginEstimator(t *testing.T) { + // Use 10% margin on top of the recommended resources. marginFraction := 0.1 - baseEstimator := NewConstEstimator(model.Resources{ - model.ResourceCPU: model.CPUAmountFromCores(3.14), - model.ResourceMemory: model.MemoryAmountFromBytes(3.14e9), - }) - testedEstimator := &marginEstimator{ - marginFraction: marginFraction, - baseEstimator: baseEstimator, - } + baseCPUEstimator := NewConstCPUEstimator(model.CPUAmountFromCores(3.14)) + baseMemoryEstimator := NewConstMemoryEstimator(model.MemoryAmountFromBytes(3.14e9)) + testedCPU := WithCPUMargin(marginFraction, baseCPUEstimator) + testedMemory := WithMemoryMargin(marginFraction, baseMemoryEstimator) + testedEstimator := NewCombinedEstimator(testedCPU, testedMemory) s := model.NewAggregateContainerState() resourceEstimation := testedEstimator.GetResourceEstimation(s) assert.Equal(t, 3.14*1.1, model.CoresFromCPUAmount(resourceEstimation[model.ResourceCPU])) @@ -138,24 +118,17 @@ func TestMarginEstimator(t *testing.T) { // Verifies that the MinResourcesEstimator returns at least MinResources. func TestMinResourcesEstimator(t *testing.T) { - - minResources := model.Resources{ - model.ResourceCPU: model.CPUAmountFromCores(0.2), - model.ResourceMemory: model.MemoryAmountFromBytes(4e8), - } - baseEstimator := NewConstEstimator(model.Resources{ - model.ResourceCPU: model.CPUAmountFromCores(3.14), - model.ResourceMemory: model.MemoryAmountFromBytes(2e7), - }) - - testedEstimator := &minResourcesEstimator{ - minResources: minResources, - baseEstimator: baseEstimator, - } + constCPUEstimator := NewConstCPUEstimator(model.CPUAmountFromCores(3.14)) + minCPU := model.CPUAmountFromCores(0.2) + cpuEstimator := WithCPUMinResource(minCPU, constCPUEstimator) s := model.NewAggregateContainerState() - resourceEstimation := testedEstimator.GetResourceEstimation(s) - // Original CPU is above min resources - assert.Equal(t, 3.14, model.CoresFromCPUAmount(resourceEstimation[model.ResourceCPU])) - // Original Memory is below min resources - assert.Equal(t, 4e8, model.BytesFromMemoryAmount(resourceEstimation[model.ResourceMemory])) + cpuEstimation := cpuEstimator.GetCPUEstimation(s) + assert.Equal(t, 3.14, model.CoresFromCPUAmount(cpuEstimation)) + + constMemoryEstimator := NewConstMemoryEstimator(model.MemoryAmountFromBytes(4e8)) + minMemory := model.MemoryAmountFromBytes(2e7) + memoryEstimator := WithMemoryMinResource(minMemory, constMemoryEstimator) + memoryEstimation := memoryEstimator.GetMemoryEstimation(s) + assert.Equal(t, 4e8, model.BytesFromMemoryAmount(memoryEstimation)) + } diff --git a/vertical-pod-autoscaler/pkg/recommender/logic/recommender.go b/vertical-pod-autoscaler/pkg/recommender/logic/recommender.go index 5b7b127a571a..8aaf9cbd5e91 100644 --- a/vertical-pod-autoscaler/pkg/recommender/logic/recommender.go +++ b/vertical-pod-autoscaler/pkg/recommender/logic/recommender.go @@ -57,9 +57,12 @@ type RecommendedContainerResources struct { } type podResourceRecommender struct { - targetEstimator ResourceEstimator - lowerBoundEstimator ResourceEstimator - upperBoundEstimator ResourceEstimator + targetCPU CPUEstimator + targetMemory MemoryEstimator + lowerBoundCPU CPUEstimator + lowerBoundMemory MemoryEstimator + upperBoundCPU CPUEstimator + upperBoundMemory MemoryEstimator } func (r *podResourceRecommender) GetRecommendedPodResources(containerNameToAggregateStateMap model.ContainerNameToAggregateStateMap) RecommendedPodResources { @@ -69,15 +72,16 @@ func (r *podResourceRecommender) GetRecommendedPodResources(containerNameToAggre } fraction := 1.0 / float64(len(containerNameToAggregateStateMap)) - minResources := model.Resources{ - model.ResourceCPU: model.ScaleResource(model.CPUAmountFromCores(*podMinCPUMillicores*0.001), fraction), - model.ResourceMemory: model.ScaleResource(model.MemoryAmountFromBytes(*podMinMemoryMb*1024*1024), fraction), - } + minCPU := model.ScaleResource(model.CPUAmountFromCores(*podMinCPUMillicores*0.001), fraction) + minMemory := model.ScaleResource(model.MemoryAmountFromBytes(*podMinMemoryMb*1024*1024), fraction) recommender := &podResourceRecommender{ - WithMinResources(minResources, r.targetEstimator), - WithMinResources(minResources, r.lowerBoundEstimator), - WithMinResources(minResources, r.upperBoundEstimator), + WithCPUMinResource(minCPU, r.targetCPU), + WithMemoryMinResource(minMemory, r.targetMemory), + WithCPUMinResource(minCPU, r.lowerBoundCPU), + WithMemoryMinResource(minMemory, r.lowerBoundMemory), + WithCPUMinResource(minCPU, r.upperBoundCPU), + WithMemoryMinResource(minMemory, r.upperBoundMemory), } for containerName, aggregatedContainerState := range containerNameToAggregateStateMap { @@ -88,10 +92,14 @@ func (r *podResourceRecommender) GetRecommendedPodResources(containerNameToAggre // Takes AggregateContainerState and returns a container recommendation. func (r *podResourceRecommender) estimateContainerResources(s *model.AggregateContainerState) RecommendedContainerResources { + resources := s.GetControlledResources() + target := model.Resources{model.ResourceCPU: r.targetCPU.GetCPUEstimation(s), model.ResourceMemory: r.targetMemory.GetMemoryEstimation(s)} + lowerBound := model.Resources{model.ResourceCPU: r.lowerBoundCPU.GetCPUEstimation(s), model.ResourceMemory: r.lowerBoundMemory.GetMemoryEstimation(s)} + upperBound := model.Resources{model.ResourceCPU: r.upperBoundCPU.GetCPUEstimation(s), model.ResourceMemory: r.upperBoundMemory.GetMemoryEstimation(s)} return RecommendedContainerResources{ - FilterControlledResources(r.targetEstimator.GetResourceEstimation(s), s.GetControlledResources()), - FilterControlledResources(r.lowerBoundEstimator.GetResourceEstimation(s), s.GetControlledResources()), - FilterControlledResources(r.upperBoundEstimator.GetResourceEstimation(s), s.GetControlledResources()), + FilterControlledResources(target, resources), + FilterControlledResources(lowerBound, resources), + FilterControlledResources(upperBound, resources), } } @@ -108,13 +116,23 @@ func FilterControlledResources(estimation model.Resources, controlledResources [ // CreatePodResourceRecommender returns the primary recommender. func CreatePodResourceRecommender() PodResourceRecommender { - targetEstimator := NewPercentileEstimator(*targetCPUPercentile, *targetMemoryPercentile) - lowerBoundEstimator := NewPercentileEstimator(*lowerBoundCPUPercentile, *lowerBoundMemoryPercentile) - upperBoundEstimator := NewPercentileEstimator(*upperBoundCPUPercentile, *upperBoundMemoryPercentile) + targetCPU := NewPercentileCPUEstimator(*targetCPUPercentile) + lowerBoundCPU := NewPercentileCPUEstimator(*lowerBoundCPUPercentile) + upperBoundCPU := NewPercentileCPUEstimator(*upperBoundCPUPercentile) + + // Create base memory estimators + targetMemory := NewPercentileMemoryEstimator(*targetMemoryPercentile) + lowerBoundMemory := NewPercentileMemoryEstimator(*lowerBoundMemoryPercentile) + upperBoundMemory := NewPercentileMemoryEstimator(*upperBoundMemoryPercentile) + + // Apply safety margins + targetCPU = WithCPUMargin(*safetyMarginFraction, targetCPU) + lowerBoundCPU = WithCPUMargin(*safetyMarginFraction, lowerBoundCPU) + upperBoundCPU = WithCPUMargin(*safetyMarginFraction, upperBoundCPU) - targetEstimator = WithMargin(*safetyMarginFraction, targetEstimator) - lowerBoundEstimator = WithMargin(*safetyMarginFraction, lowerBoundEstimator) - upperBoundEstimator = WithMargin(*safetyMarginFraction, upperBoundEstimator) + targetMemory = WithMemoryMargin(*safetyMarginFraction, targetMemory) + lowerBoundMemory = WithMemoryMargin(*safetyMarginFraction, lowerBoundMemory) + upperBoundMemory = WithMemoryMargin(*safetyMarginFraction, upperBoundMemory) // Apply confidence multiplier to the upper bound estimator. This means // that the updater will be less eager to evict pods with short history @@ -127,7 +145,9 @@ func CreatePodResourceRecommender() PodResourceRecommender { // 12h history : *3 (force pod eviction if the request is > 3 * upper bound) // 24h history : *2 // 1 week history : *1.14 - upperBoundEstimator = WithConfidenceMultiplier(1.0, 1.0, upperBoundEstimator) + + upperBoundCPU = WithCPUConfidenceMultiplier(1.0, 1.0, upperBoundCPU) + upperBoundMemory = WithMemoryConfidenceMultiplier(1.0, 1.0, upperBoundMemory) // Apply confidence multiplier to the lower bound estimator. This means // that the updater will be less eager to evict pods with short history @@ -141,12 +161,16 @@ func CreatePodResourceRecommender() PodResourceRecommender { // 5m history : *0.6 (force pod eviction if the request is < 0.6 * lower bound) // 30m history : *0.9 // 60m history : *0.95 - lowerBoundEstimator = WithConfidenceMultiplier(0.001, -2.0, lowerBoundEstimator) - + lowerBoundCPU = WithCPUConfidenceMultiplier(0.001, -2.0, lowerBoundCPU) + lowerBoundMemory = WithMemoryConfidenceMultiplier(0.001, -2.0, lowerBoundMemory) return &podResourceRecommender{ - targetEstimator, - lowerBoundEstimator, - upperBoundEstimator} + targetCPU, + targetMemory, + lowerBoundCPU, + lowerBoundMemory, + upperBoundCPU, + upperBoundMemory, + } } // MapToListOfRecommendedContainerResources converts the map of RecommendedContainerResources into a stable sorted list diff --git a/vertical-pod-autoscaler/pkg/recommender/logic/recommender_test.go b/vertical-pod-autoscaler/pkg/recommender/logic/recommender_test.go index 6e7e788dc391..5824745a4616 100644 --- a/vertical-pod-autoscaler/pkg/recommender/logic/recommender_test.go +++ b/vertical-pod-autoscaler/pkg/recommender/logic/recommender_test.go @@ -17,20 +17,24 @@ limitations under the License. package logic import ( + "testing" + "github.com/stretchr/testify/assert" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/model" - "testing" ) func TestMinResourcesApplied(t *testing.T) { - constEstimator := NewConstEstimator(model.Resources{ - model.ResourceCPU: model.CPUAmountFromCores(0.001), - model.ResourceMemory: model.MemoryAmountFromBytes(1e6), - }) + constCPUEstimator := NewConstCPUEstimator(model.CPUAmountFromCores(0.001)) + constMemoryEstimator := NewConstMemoryEstimator(model.MemoryAmountFromBytes(1e6)) + recommender := podResourceRecommender{ - constEstimator, - constEstimator, - constEstimator} + targetCPU: constCPUEstimator, + targetMemory: constMemoryEstimator, + lowerBoundCPU: constCPUEstimator, + lowerBoundMemory: constMemoryEstimator, + upperBoundCPU: constCPUEstimator, + upperBoundMemory: constMemoryEstimator, + } containerNameToAggregateStateMap := model.ContainerNameToAggregateStateMap{ "container-1": &model.AggregateContainerState{}, @@ -42,14 +46,17 @@ func TestMinResourcesApplied(t *testing.T) { } func TestMinResourcesSplitAcrossContainers(t *testing.T) { - constEstimator := NewConstEstimator(model.Resources{ - model.ResourceCPU: model.CPUAmountFromCores(0.001), - model.ResourceMemory: model.MemoryAmountFromBytes(1e6), - }) + constCPUEstimator := NewConstCPUEstimator(model.CPUAmountFromCores(0.001)) + constMemoryEstimator := NewConstMemoryEstimator(model.MemoryAmountFromBytes(1e6)) + recommender := podResourceRecommender{ - constEstimator, - constEstimator, - constEstimator} + targetCPU: constCPUEstimator, + targetMemory: constMemoryEstimator, + lowerBoundCPU: constCPUEstimator, + lowerBoundMemory: constMemoryEstimator, + upperBoundCPU: constCPUEstimator, + upperBoundMemory: constMemoryEstimator, + } containerNameToAggregateStateMap := model.ContainerNameToAggregateStateMap{ "container-1": &model.AggregateContainerState{}, @@ -64,14 +71,17 @@ func TestMinResourcesSplitAcrossContainers(t *testing.T) { } func TestControlledResourcesFiltered(t *testing.T) { - constEstimator := NewConstEstimator(model.Resources{ - model.ResourceCPU: model.CPUAmountFromCores(0.001), - model.ResourceMemory: model.MemoryAmountFromBytes(1e6), - }) + constCPUEstimator := NewConstCPUEstimator(model.CPUAmountFromCores(0.001)) + constMemoryEstimator := NewConstMemoryEstimator(model.MemoryAmountFromBytes(1e6)) + recommender := podResourceRecommender{ - constEstimator, - constEstimator, - constEstimator} + targetCPU: constCPUEstimator, + targetMemory: constMemoryEstimator, + lowerBoundCPU: constCPUEstimator, + lowerBoundMemory: constMemoryEstimator, + upperBoundCPU: constCPUEstimator, + upperBoundMemory: constMemoryEstimator, + } containerName := "container-1" containerNameToAggregateStateMap := model.ContainerNameToAggregateStateMap{ @@ -90,14 +100,17 @@ func TestControlledResourcesFiltered(t *testing.T) { } func TestControlledResourcesFilteredDefault(t *testing.T) { - constEstimator := NewConstEstimator(model.Resources{ - model.ResourceCPU: model.CPUAmountFromCores(0.001), - model.ResourceMemory: model.MemoryAmountFromBytes(1e6), - }) + constCPUEstimator := NewConstCPUEstimator(model.CPUAmountFromCores(0.001)) + constMemoryEstimator := NewConstMemoryEstimator(model.MemoryAmountFromBytes(1e6)) + recommender := podResourceRecommender{ - constEstimator, - constEstimator, - constEstimator} + targetCPU: constCPUEstimator, + targetMemory: constMemoryEstimator, + lowerBoundCPU: constCPUEstimator, + lowerBoundMemory: constMemoryEstimator, + upperBoundCPU: constCPUEstimator, + upperBoundMemory: constMemoryEstimator, + } containerName := "container-1" containerNameToAggregateStateMap := model.ContainerNameToAggregateStateMap{