@@ -40,29 +40,41 @@ const (
40
40
defaultTestingDelayOfInitialReadinessStatus = 10 * time .Second
41
41
)
42
42
43
+ // Tolerances contains metric usage ratio scale-up and scale-down tolerances.
44
+ type Tolerances struct {
45
+ scaleDown float64
46
+ scaleUp float64
47
+ }
48
+
49
+ func (t Tolerances ) String () string {
50
+ return fmt .Sprintf ("[down:%.1f%%, up:%.1f%%]" , t .scaleDown * 100. , t .scaleUp * 100. )
51
+ }
52
+
53
+ func (t Tolerances ) isWithin (usageRatio float64 ) bool {
54
+ return (1.0 - t .scaleDown ) <= usageRatio && usageRatio <= (1.0 + t .scaleUp )
55
+ }
56
+
43
57
// ReplicaCalculator bundles all needed information to calculate the target amount of replicas
44
58
type ReplicaCalculator struct {
45
59
metricsClient metricsclient.MetricsClient
46
60
podLister corelisters.PodLister
47
- tolerance float64
48
61
cpuInitializationPeriod time.Duration
49
62
delayOfInitialReadinessStatus time.Duration
50
63
}
51
64
52
65
// NewReplicaCalculator creates a new ReplicaCalculator and passes all necessary information to the new instance
53
- func NewReplicaCalculator (metricsClient metricsclient.MetricsClient , podLister corelisters.PodLister , tolerance float64 , cpuInitializationPeriod , delayOfInitialReadinessStatus time.Duration ) * ReplicaCalculator {
66
+ func NewReplicaCalculator (metricsClient metricsclient.MetricsClient , podLister corelisters.PodLister , cpuInitializationPeriod , delayOfInitialReadinessStatus time.Duration ) * ReplicaCalculator {
54
67
return & ReplicaCalculator {
55
68
metricsClient : metricsClient ,
56
69
podLister : podLister ,
57
- tolerance : tolerance ,
58
70
cpuInitializationPeriod : cpuInitializationPeriod ,
59
71
delayOfInitialReadinessStatus : delayOfInitialReadinessStatus ,
60
72
}
61
73
}
62
74
63
75
// GetResourceReplicas calculates the desired replica count based on a target resource utilization percentage
64
76
// of the given resource for pods matching the given selector in the given namespace, and the current replica count
65
- func (c * ReplicaCalculator ) GetResourceReplicas (ctx context.Context , currentReplicas int32 , targetUtilization int32 , resource v1.ResourceName , namespace string , selector labels.Selector , container string ) (replicaCount int32 , utilization int32 , rawUtilization int64 , timestamp time.Time , err error ) {
77
+ func (c * ReplicaCalculator ) GetResourceReplicas (ctx context.Context , currentReplicas int32 , targetUtilization int32 , resource v1.ResourceName , tolerances Tolerances , namespace string , selector labels.Selector , container string ) (replicaCount int32 , utilization int32 , rawUtilization int64 , timestamp time.Time , err error ) {
66
78
metrics , timestamp , err := c .metricsClient .GetResourceMetric (ctx , resource , namespace , selector , container )
67
79
if err != nil {
68
80
return 0 , 0 , 0 , time.Time {}, fmt .Errorf ("unable to get metrics for resource %s: %v" , resource , err )
@@ -94,7 +106,7 @@ func (c *ReplicaCalculator) GetResourceReplicas(ctx context.Context, currentRepl
94
106
95
107
scaleUpWithUnready := len (unreadyPods ) > 0 && usageRatio > 1.0
96
108
if ! scaleUpWithUnready && len (missingPods ) == 0 {
97
- if math . Abs ( 1.0 - usageRatio ) <= c . tolerance {
109
+ if tolerances . isWithin ( usageRatio ) {
98
110
// return the current replicas if the change would be too small
99
111
return currentReplicas , utilization , rawUtilization , timestamp , nil
100
112
}
@@ -132,7 +144,7 @@ func (c *ReplicaCalculator) GetResourceReplicas(ctx context.Context, currentRepl
132
144
return 0 , utilization , rawUtilization , time.Time {}, err
133
145
}
134
146
135
- if math . Abs ( 1.0 - newUsageRatio ) <= c . tolerance || (usageRatio < 1.0 && newUsageRatio > 1.0 ) || (usageRatio > 1.0 && newUsageRatio < 1.0 ) {
147
+ if tolerances . isWithin ( newUsageRatio ) || (usageRatio < 1.0 && newUsageRatio > 1.0 ) || (usageRatio > 1.0 && newUsageRatio < 1.0 ) {
136
148
// return the current replicas if the change would be too small,
137
149
// or if the new usage ratio would cause a change in scale direction
138
150
return currentReplicas , utilization , rawUtilization , timestamp , nil
@@ -151,31 +163,31 @@ func (c *ReplicaCalculator) GetResourceReplicas(ctx context.Context, currentRepl
151
163
152
164
// GetRawResourceReplicas calculates the desired replica count based on a target resource usage (as a raw milli-value)
153
165
// for pods matching the given selector in the given namespace, and the current replica count
154
- func (c * ReplicaCalculator ) GetRawResourceReplicas (ctx context.Context , currentReplicas int32 , targetUsage int64 , resource v1.ResourceName , namespace string , selector labels.Selector , container string ) (replicaCount int32 , usage int64 , timestamp time.Time , err error ) {
166
+ func (c * ReplicaCalculator ) GetRawResourceReplicas (ctx context.Context , currentReplicas int32 , targetUsage int64 , resource v1.ResourceName , tolerances Tolerances , namespace string , selector labels.Selector , container string ) (replicaCount int32 , usage int64 , timestamp time.Time , err error ) {
155
167
metrics , timestamp , err := c .metricsClient .GetResourceMetric (ctx , resource , namespace , selector , container )
156
168
if err != nil {
157
169
return 0 , 0 , time.Time {}, fmt .Errorf ("unable to get metrics for resource %s: %v" , resource , err )
158
170
}
159
171
160
- replicaCount , usage , err = c .calcPlainMetricReplicas (metrics , currentReplicas , targetUsage , namespace , selector , resource )
172
+ replicaCount , usage , err = c .calcPlainMetricReplicas (metrics , currentReplicas , targetUsage , tolerances , namespace , selector , resource )
161
173
return replicaCount , usage , timestamp , err
162
174
}
163
175
164
176
// GetMetricReplicas calculates the desired replica count based on a target metric usage
165
177
// (as a milli-value) for pods matching the given selector in the given namespace, and the
166
178
// current replica count
167
- func (c * ReplicaCalculator ) GetMetricReplicas (currentReplicas int32 , targetUsage int64 , metricName string , namespace string , selector labels.Selector , metricSelector labels.Selector ) (replicaCount int32 , usage int64 , timestamp time.Time , err error ) {
179
+ func (c * ReplicaCalculator ) GetMetricReplicas (currentReplicas int32 , targetUsage int64 , metricName string , tolerances Tolerances , namespace string , selector labels.Selector , metricSelector labels.Selector ) (replicaCount int32 , usage int64 , timestamp time.Time , err error ) {
168
180
metrics , timestamp , err := c .metricsClient .GetRawMetric (metricName , namespace , selector , metricSelector )
169
181
if err != nil {
170
182
return 0 , 0 , time.Time {}, fmt .Errorf ("unable to get metric %s: %v" , metricName , err )
171
183
}
172
184
173
- replicaCount , usage , err = c .calcPlainMetricReplicas (metrics , currentReplicas , targetUsage , namespace , selector , v1 .ResourceName ("" ))
185
+ replicaCount , usage , err = c .calcPlainMetricReplicas (metrics , currentReplicas , targetUsage , tolerances , namespace , selector , v1 .ResourceName ("" ))
174
186
return replicaCount , usage , timestamp , err
175
187
}
176
188
177
189
// calcPlainMetricReplicas calculates the desired replicas for plain (i.e. non-utilization percentage) metrics.
178
- func (c * ReplicaCalculator ) calcPlainMetricReplicas (metrics metricsclient.PodMetricsInfo , currentReplicas int32 , targetUsage int64 , namespace string , selector labels.Selector , resource v1.ResourceName ) (replicaCount int32 , usage int64 , err error ) {
190
+ func (c * ReplicaCalculator ) calcPlainMetricReplicas (metrics metricsclient.PodMetricsInfo , currentReplicas int32 , targetUsage int64 , tolerances Tolerances , namespace string , selector labels.Selector , resource v1.ResourceName ) (replicaCount int32 , usage int64 , err error ) {
179
191
180
192
podList , err := c .podLister .Pods (namespace ).List (selector )
181
193
if err != nil {
@@ -199,7 +211,7 @@ func (c *ReplicaCalculator) calcPlainMetricReplicas(metrics metricsclient.PodMet
199
211
scaleUpWithUnready := len (unreadyPods ) > 0 && usageRatio > 1.0
200
212
201
213
if ! scaleUpWithUnready && len (missingPods ) == 0 {
202
- if math . Abs ( 1.0 - usageRatio ) <= c . tolerance {
214
+ if tolerances . isWithin ( usageRatio ) {
203
215
// return the current replicas if the change would be too small
204
216
return currentReplicas , usage , nil
205
217
}
@@ -232,7 +244,7 @@ func (c *ReplicaCalculator) calcPlainMetricReplicas(metrics metricsclient.PodMet
232
244
// re-run the usage calculation with our new numbers
233
245
newUsageRatio , _ := metricsclient .GetMetricUsageRatio (metrics , targetUsage )
234
246
235
- if math . Abs ( 1.0 - newUsageRatio ) <= c . tolerance || (usageRatio < 1.0 && newUsageRatio > 1.0 ) || (usageRatio > 1.0 && newUsageRatio < 1.0 ) {
247
+ if tolerances . isWithin ( newUsageRatio ) || (usageRatio < 1.0 && newUsageRatio > 1.0 ) || (usageRatio > 1.0 && newUsageRatio < 1.0 ) {
236
248
// return the current replicas if the change would be too small,
237
249
// or if the new usage ratio would cause a change in scale direction
238
250
return currentReplicas , usage , nil
@@ -251,22 +263,22 @@ func (c *ReplicaCalculator) calcPlainMetricReplicas(metrics metricsclient.PodMet
251
263
252
264
// GetObjectMetricReplicas calculates the desired replica count based on a target metric usage (as a milli-value)
253
265
// for the given object in the given namespace, and the current replica count.
254
- func (c * ReplicaCalculator ) GetObjectMetricReplicas (currentReplicas int32 , targetUsage int64 , metricName string , namespace string , objectRef * autoscaling.CrossVersionObjectReference , selector labels.Selector , metricSelector labels.Selector ) (replicaCount int32 , usage int64 , timestamp time.Time , err error ) {
266
+ func (c * ReplicaCalculator ) GetObjectMetricReplicas (currentReplicas int32 , targetUsage int64 , metricName string , tolerances Tolerances , namespace string , objectRef * autoscaling.CrossVersionObjectReference , selector labels.Selector , metricSelector labels.Selector ) (replicaCount int32 , usage int64 , timestamp time.Time , err error ) {
255
267
usage , _ , err = c .metricsClient .GetObjectMetric (metricName , namespace , objectRef , metricSelector )
256
268
if err != nil {
257
269
return 0 , 0 , time.Time {}, fmt .Errorf ("unable to get metric %s: %v on %s %s/%s" , metricName , objectRef .Kind , namespace , objectRef .Name , err )
258
270
}
259
271
260
272
usageRatio := float64 (usage ) / float64 (targetUsage )
261
- replicaCount , timestamp , err = c .getUsageRatioReplicaCount (currentReplicas , usageRatio , namespace , selector )
273
+ replicaCount , timestamp , err = c .getUsageRatioReplicaCount (currentReplicas , usageRatio , tolerances , namespace , selector )
262
274
return replicaCount , usage , timestamp , err
263
275
}
264
276
265
277
// getUsageRatioReplicaCount calculates the desired replica count based on usageRatio and ready pods count.
266
278
// For currentReplicas=0 doesn't take into account ready pods count and tolerance to support scaling to zero pods.
267
- func (c * ReplicaCalculator ) getUsageRatioReplicaCount (currentReplicas int32 , usageRatio float64 , namespace string , selector labels.Selector ) (replicaCount int32 , timestamp time.Time , err error ) {
279
+ func (c * ReplicaCalculator ) getUsageRatioReplicaCount (currentReplicas int32 , usageRatio float64 , tolerances Tolerances , namespace string , selector labels.Selector ) (replicaCount int32 , timestamp time.Time , err error ) {
268
280
if currentReplicas != 0 {
269
- if math . Abs ( 1.0 - usageRatio ) <= c . tolerance {
281
+ if tolerances . isWithin ( usageRatio ) {
270
282
// return the current replicas if the change would be too small
271
283
return currentReplicas , timestamp , nil
272
284
}
@@ -286,15 +298,15 @@ func (c *ReplicaCalculator) getUsageRatioReplicaCount(currentReplicas int32, usa
286
298
287
299
// GetObjectPerPodMetricReplicas calculates the desired replica count based on a target metric usage (as a milli-value)
288
300
// for the given object in the given namespace, and the current replica count.
289
- func (c * ReplicaCalculator ) GetObjectPerPodMetricReplicas (statusReplicas int32 , targetAverageUsage int64 , metricName string , namespace string , objectRef * autoscaling.CrossVersionObjectReference , metricSelector labels.Selector ) (replicaCount int32 , usage int64 , timestamp time.Time , err error ) {
301
+ func (c * ReplicaCalculator ) GetObjectPerPodMetricReplicas (statusReplicas int32 , targetAverageUsage int64 , metricName string , tolerances Tolerances , namespace string , objectRef * autoscaling.CrossVersionObjectReference , metricSelector labels.Selector ) (replicaCount int32 , usage int64 , timestamp time.Time , err error ) {
290
302
usage , timestamp , err = c .metricsClient .GetObjectMetric (metricName , namespace , objectRef , metricSelector )
291
303
if err != nil {
292
304
return 0 , 0 , time.Time {}, fmt .Errorf ("unable to get metric %s: %v on %s %s/%s" , metricName , objectRef .Kind , namespace , objectRef .Name , err )
293
305
}
294
306
295
307
replicaCount = statusReplicas
296
308
usageRatio := float64 (usage ) / (float64 (targetAverageUsage ) * float64 (replicaCount ))
297
- if math . Abs ( 1.0 - usageRatio ) > c . tolerance {
309
+ if ! tolerances . isWithin ( usageRatio ) {
298
310
// update number of replicas if change is large enough
299
311
replicaCount = int32 (math .Ceil (float64 (usage ) / float64 (targetAverageUsage )))
300
312
}
@@ -329,7 +341,7 @@ func (c *ReplicaCalculator) getReadyPodsCount(namespace string, selector labels.
329
341
// GetExternalMetricReplicas calculates the desired replica count based on a
330
342
// target metric value (as a milli-value) for the external metric in the given
331
343
// namespace, and the current replica count.
332
- func (c * ReplicaCalculator ) GetExternalMetricReplicas (currentReplicas int32 , targetUsage int64 , metricName , namespace string , metricSelector * metav1.LabelSelector , podSelector labels.Selector ) (replicaCount int32 , usage int64 , timestamp time.Time , err error ) {
344
+ func (c * ReplicaCalculator ) GetExternalMetricReplicas (currentReplicas int32 , targetUsage int64 , metricName string , tolerances Tolerances , namespace string , metricSelector * metav1.LabelSelector , podSelector labels.Selector ) (replicaCount int32 , usage int64 , timestamp time.Time , err error ) {
333
345
metricLabelSelector , err := metav1 .LabelSelectorAsSelector (metricSelector )
334
346
if err != nil {
335
347
return 0 , 0 , time.Time {}, err
@@ -344,14 +356,14 @@ func (c *ReplicaCalculator) GetExternalMetricReplicas(currentReplicas int32, tar
344
356
}
345
357
346
358
usageRatio := float64 (usage ) / float64 (targetUsage )
347
- replicaCount , timestamp , err = c .getUsageRatioReplicaCount (currentReplicas , usageRatio , namespace , podSelector )
359
+ replicaCount , timestamp , err = c .getUsageRatioReplicaCount (currentReplicas , usageRatio , tolerances , namespace , podSelector )
348
360
return replicaCount , usage , timestamp , err
349
361
}
350
362
351
363
// GetExternalPerPodMetricReplicas calculates the desired replica count based on a
352
364
// target metric value per pod (as a milli-value) for the external metric in the
353
365
// given namespace, and the current replica count.
354
- func (c * ReplicaCalculator ) GetExternalPerPodMetricReplicas (statusReplicas int32 , targetUsagePerPod int64 , metricName , namespace string , metricSelector * metav1.LabelSelector ) (replicaCount int32 , usage int64 , timestamp time.Time , err error ) {
366
+ func (c * ReplicaCalculator ) GetExternalPerPodMetricReplicas (statusReplicas int32 , targetUsagePerPod int64 , metricName string , tolerances Tolerances , namespace string , metricSelector * metav1.LabelSelector ) (replicaCount int32 , usage int64 , timestamp time.Time , err error ) {
355
367
metricLabelSelector , err := metav1 .LabelSelectorAsSelector (metricSelector )
356
368
if err != nil {
357
369
return 0 , 0 , time.Time {}, err
@@ -367,7 +379,7 @@ func (c *ReplicaCalculator) GetExternalPerPodMetricReplicas(statusReplicas int32
367
379
368
380
replicaCount = statusReplicas
369
381
usageRatio := float64 (usage ) / (float64 (targetUsagePerPod ) * float64 (replicaCount ))
370
- if math . Abs ( 1.0 - usageRatio ) > c . tolerance {
382
+ if ! tolerances . isWithin ( usageRatio ) {
371
383
// update number of replicas if the change is large enough
372
384
replicaCount = int32 (math .Ceil (float64 (usage ) / float64 (targetUsagePerPod )))
373
385
}
0 commit comments