-
Notifications
You must be signed in to change notification settings - Fork 6
/
evaluate.go
176 lines (149 loc) · 6.48 KB
/
evaluate.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Modifications Copyright 2024 The K8sHorizMetrics Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Modified to split up evaluations and metric gathering to work with the
Custom Pod Autoscaler framework.
Original source:
https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/podautoscaler/horizontal.go
https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/podautoscaler/replica_calculator.go
*/
package k8shorizmetrics
import (
"fmt"
"github.com/jthomperoo/k8shorizmetrics/v4/internal/external"
"github.com/jthomperoo/k8shorizmetrics/v4/internal/object"
"github.com/jthomperoo/k8shorizmetrics/v4/internal/pods"
"github.com/jthomperoo/k8shorizmetrics/v4/internal/replicas"
"github.com/jthomperoo/k8shorizmetrics/v4/internal/resource"
"github.com/jthomperoo/k8shorizmetrics/v4/metrics"
autoscalingv2 "k8s.io/api/autoscaling/v2"
)
// EvaluatorMultiMetricError occurs when evaluating multiple metrics, if any metric fails to be evaluated this error
// will be returned which contains all of the individual errors in the 'Errors' slice, if some metrics
// were evaluated successfully the error will have the 'Partial' property set to true.
type EvaluatorMultiMetricError struct {
Partial bool
Errors []error
}
func (e *EvaluatorMultiMetricError) Error() string {
return fmt.Sprintf("evaluator multi metric error: %d errors, first error is %s", len(e.Errors), e.Errors[0])
}
// ExternalEvaluater produces a replica count based on an external metric provided
type ExternalEvaluater interface {
Evaluate(currentReplicas int32, gatheredMetric *metrics.Metric, tolerance float64) (int32, error)
}
// ObjectEvaluater produces a replica count based on an object metric provided
type ObjectEvaluater interface {
Evaluate(currentReplicas int32, gatheredMetric *metrics.Metric, tolerance float64) (int32, error)
}
// PodsEvaluater produces a replica count based on a pods metric provided
type PodsEvaluater interface {
Evaluate(currentReplicas int32, gatheredMetric *metrics.Metric) int32
}
// ResourceEvaluater produces an evaluation based on a resource metric provided
type ResourceEvaluater interface {
Evaluate(currentReplicas int32, gatheredMetric *metrics.Metric, tolerance float64) (int32, error)
}
// Evaluator provides functionality for deciding how many replicas a resource should have based on provided metrics.
type Evaluator struct {
External ExternalEvaluater
Object ObjectEvaluater
Pods PodsEvaluater
Resource ResourceEvaluater
Tolerance float64
}
// NewEvaluator sets up an evaluate that can process external, object, pod and resource metrics
func NewEvaluator(tolerance float64) *Evaluator {
calculate := &replicas.ReplicaCalculator{
Tolerance: tolerance,
}
return &Evaluator{
External: &external.Evaluate{
Calculater: calculate,
},
Object: &object.Evaluate{
Calculater: calculate,
},
Pods: &pods.Evaluate{
Calculater: calculate,
},
Resource: &resource.Evaluate{
Calculater: calculate,
},
}
}
// Evaluate returns the target replica count for an array of multiple metrics
// If an error occurs evaluating any metric this will return a EvaluatorMultiMetricError. If a partial error occurs,
// meaning some metrics were evaluated successfully and others failed, the 'Partial' property of this error will be
// set to true.
func (e *Evaluator) Evaluate(gatheredMetrics []*metrics.Metric, currentReplicas int32) (int32, error) {
return e.EvaluateWithOptions(gatheredMetrics, currentReplicas, e.Tolerance)
}
// EvaluateWithOptions returns the target replica count for an array of multiple metrics with provided options
// If an error occurs evaluating any metric this will return a EvaluatorMultiMetricError. If a partial error occurs,
// meaning some metrics were evaluated successfully and others failed, the 'Partial' property of this error will be
// set to true.
func (e *Evaluator) EvaluateWithOptions(gatheredMetrics []*metrics.Metric, currentReplicas int32,
tolerance float64) (int32, error) {
var evaluation int32
var evaluationErrors []error
for i, gatheredMetric := range gatheredMetrics {
proposedEvaluation, err := e.EvaluateSingleMetricWithOptions(gatheredMetric, currentReplicas, tolerance)
if err != nil {
evaluationErrors = append(evaluationErrors, err)
continue
}
if i == 0 {
evaluation = proposedEvaluation
}
// Multiple evaluations, take the highest replica count
if proposedEvaluation > evaluation {
evaluation = proposedEvaluation
}
}
if len(evaluationErrors) > 0 {
partial := len(evaluationErrors) < len(gatheredMetrics)
if partial {
return evaluation, &EvaluatorMultiMetricError{
Partial: partial,
Errors: evaluationErrors,
}
}
return 0, &EvaluatorMultiMetricError{
Partial: partial,
Errors: evaluationErrors,
}
}
return evaluation, nil
}
// EvaluateSingleMetric returns the target replica count for a single metrics
func (e *Evaluator) EvaluateSingleMetric(gatheredMetric *metrics.Metric, currentReplicas int32) (int32, error) {
return e.EvaluateSingleMetricWithOptions(gatheredMetric, currentReplicas, e.Tolerance)
}
// EvaluateSingleMetricWithOptions returns the target replica count for a single metrics with provided options
func (e *Evaluator) EvaluateSingleMetricWithOptions(gatheredMetric *metrics.Metric, currentReplicas int32,
tolerance float64) (int32, error) {
switch gatheredMetric.Spec.Type {
case autoscalingv2.ObjectMetricSourceType:
return e.Object.Evaluate(currentReplicas, gatheredMetric, tolerance)
case autoscalingv2.PodsMetricSourceType:
return e.Pods.Evaluate(currentReplicas, gatheredMetric), nil
case autoscalingv2.ResourceMetricSourceType:
return e.Resource.Evaluate(currentReplicas, gatheredMetric, tolerance)
case autoscalingv2.ExternalMetricSourceType:
return e.External.Evaluate(currentReplicas, gatheredMetric, tolerance)
default:
return 0, fmt.Errorf("unknown metric source type %q", string(gatheredMetric.Spec.Type))
}
}