-
Notifications
You must be signed in to change notification settings - Fork 128
/
resource.go
395 lines (357 loc) · 12.6 KB
/
resource.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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
package resource
import (
"math"
"math/big"
"sort"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
)
// FromResourceList function takes a map with keys of type ResourceName and values of type
// "resource.Quantity" as defined in the K8s API.
//
// It converts the keys to strings and creates a new map with the same keys, but with deep copies of the values.
// The resulting map is of type map[string]resource.Quantity.
func FromResourceList(list v1.ResourceList) ComputeResources {
resources := make(ComputeResources)
for k, v := range list {
resources[string(k)] = v.DeepCopy()
}
return resources
}
// QuantityAsFloat64 returns a float64 representation of a quantity.
// We need our own function because q.AsApproximateFloat64 sometimes returns surprising results.
// For example, resource.MustParse("5188205838208Ki").AsApproximateFloat64() returns 0.004291583283300088,
// whereas this function returns 5.312722778324993e+15.
func QuantityAsFloat64(q resource.Quantity) float64 {
dec := q.AsDec()
unscaled := dec.UnscaledBig()
scale := dec.Scale()
unscaledFloat, _ := new(big.Float).SetInt(unscaled).Float64()
return unscaledFloat * math.Pow10(-int(scale))
}
type ComputeResources map[string]resource.Quantity
// String function handles the string representation of ComputeResources i.e.
// how the output of the struct is represented when functions like fmt.Print()
// is called on it.
func (a ComputeResources) String() string {
str := ""
keys := make([]string, 0, len(a))
for k := range a {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
if str != "" {
str += ", "
}
v := a[k]
str += k + ": " + v.String()
}
return str
}
func (a ComputeResources) Add(b ComputeResources) {
for k, v := range b {
existing, ok := a[k]
if ok {
existing.Add(v)
a[k] = existing
} else {
a[k] = v.DeepCopy()
}
}
}
// Max function checks if the key in the ComputeResources Map supplied as an argument, b exists
// and has a value greater than the already existing value mapped to the key in ComputeResources Map
// supplied as the method's target, "a", if so, the value of the key is replaced by b's value.
// If the key does not exist in "a", it is created and made to have a DeepCopy value of its value in b.
func (a ComputeResources) Max(b ComputeResources) {
for k, v := range b {
existing, ok := a[k]
if ok {
if v.Cmp(existing) > 0 {
a[k] = v.DeepCopy()
}
} else {
a[k] = v.DeepCopy()
}
}
}
// Equal function compares a and b, returns false:
// - if both are nil.
// - if they have different lengths
// - if a key exists in "a" but does not exist in "b" or vice versa.
func (a ComputeResources) Equal(b ComputeResources) bool {
if a == nil || b == nil {
return false
}
if len(a) != len(b) {
return false
}
for k, v := range b {
existing, ok := a[k]
if !ok {
return false
}
if !existing.Equal(v) {
return false
}
}
return true
}
// Dominates function compares two ComputeResources maps, a and b,
// to determine whether "a" is sufficient to meet or exceed the resource quantity of b
// if it does then "true" is returned if not "false" is returned.
func (a ComputeResources) Dominates(b ComputeResources) bool {
reduced := a.DeepCopy()
reduced.Sub(b)
hasRemainder := false
for _, value := range reduced {
if value.Sign() < 0 {
return false
}
if value.Sign() > 0 {
hasRemainder = true
}
}
return hasRemainder
}
// IsValid function checks if all the values in "a" is greater than or equal to zero.
// It returns true if all values are valid i.e. all values are greater than or equal to zero,
// and false if any of the values are negative
func (a ComputeResources) IsValid() bool {
valid := true
for _, value := range a {
valid = valid && value.Sign() >= 0
}
return valid
}
// LimitToZero function limits each value in "a" to a minimum value of zero.
// In the case any value in "a" has a value less than zero, it is replaced with a value of zero.
func (a ComputeResources) LimitToZero() {
for key, value := range a {
if value.Sign() < 0 {
a[key] = *resource.NewQuantity(0, resource.BinarySI)
}
}
}
// IsZero function checks if every value in "a" is zero. If any value is not zero it returns false, if all are zero, it returns true.
func (a ComputeResources) IsZero() bool {
if len(a) == 0 {
return true
}
for _, value := range a {
if !value.IsZero() {
return false
}
}
return true
}
// The Sub function subtracts the values in "a" from the values
// in "b". In the case a value exists in "b" but not in "a", the negative
// of the value is mapped to its key in "a". The Sub function can be visually
// represented as (a - b).
func (a ComputeResources) Sub(b ComputeResources) {
if b == nil {
return
}
for k, v := range b {
existing, ok := a[k]
if ok {
existing.Sub(v)
a[k] = existing
} else {
cpy := v.DeepCopy()
cpy.Neg()
a[k] = cpy
}
}
}
func (a ComputeResources) DeepCopy() ComputeResources {
targetComputeResource := make(ComputeResources)
for key, value := range a {
targetComputeResource[key] = value.DeepCopy()
}
return targetComputeResource
}
// The Mul function takes a ComputeResources object called "a" and multiplies each value in it with a given "factor".
// It then stores the result of each computation in a new ComputeResourcesFloat object,
// where each key in "a" maps to its corresponding value converted to a float64 type and multiplied by "factor"
func (a ComputeResources) Mul(factor float64) ComputeResourcesFloat {
targetComputeResource := make(ComputeResourcesFloat)
for key, value := range a {
targetComputeResource[key] = QuantityAsFloat64(value) * factor
}
return targetComputeResource
}
// MulByResource function takes a ComputeResources object called "a" and a map of factors,
// where each key in the factors map corresponds to a key in "a".
// It multiplies each value in "a" by its corresponding factor from the map, or by 1 if the factor does not exist in the map.
// The computed values are stored in a new ComputeResourcesFloat object, which is returned by the function.
// The purpose of this function is to scale the values in a ComputeResources object by the factors specified in the input map.
func (a ComputeResources) MulByResource(factors map[string]float64) ComputeResourcesFloat {
targetComputeResource := make(ComputeResourcesFloat)
for key, value := range a {
factor, exists := factors[key]
if !exists {
factor = 1
}
targetComputeResource[key] = QuantityAsFloat64(value) * factor
}
return targetComputeResource
}
// AsFloat function, converts ComputeResources to ComputeResourcesFloat.
func (a ComputeResources) AsFloat() ComputeResourcesFloat {
targetComputeResource := make(ComputeResourcesFloat)
for key, value := range a {
targetComputeResource[key] = QuantityAsFloat64(value)
}
return targetComputeResource
}
// ComputeResourcesFloat is float version of compute resource, prefer calculations with quantity where possible
type ComputeResourcesFloat map[string]float64
// IsValid function checks if all the values in "a" is greater than or equal to zero.
// It returns true if all values are valid i.e. all values are greater than or equal to zero,
// and false if any of the values are negative
func (a ComputeResourcesFloat) IsValid() bool {
valid := true
for _, value := range a {
valid = valid && value >= 0
}
return valid
}
// The Sub function subtracts the values in "a" from the values
// in "b". In the case a value exists in "b" but not in "a", the negative
// of the value is mapped to its key in "a". The Sub function can be visually
// represented as (a - b).
func (a ComputeResourcesFloat) Sub(b ComputeResourcesFloat) {
if b == nil {
return
}
for k, v := range b {
existing, ok := a[k]
if ok {
a[k] = existing - v
} else {
a[k] = -v
}
}
}
func (a ComputeResourcesFloat) Add(b ComputeResourcesFloat) {
if b == nil {
return
}
for k, v := range b {
existing, ok := a[k]
if ok {
a[k] = existing + v
} else {
a[k] = v
}
}
}
// The Max function maps every key in "a" with its maximum when compared with its value and its corresponding value in "b".
func (a ComputeResourcesFloat) Max(b ComputeResourcesFloat) {
for k, v := range b {
existing, ok := a[k]
if ok {
a[k] = math.Max(v, existing)
} else {
a[k] = v
}
}
}
func (a ComputeResourcesFloat) DeepCopy() ComputeResourcesFloat {
targetComputeResource := make(ComputeResourcesFloat)
for key, value := range a {
targetComputeResource[key] = value
}
return targetComputeResource
}
// IsLessThan function checks if any value a contains any negative value after carrying out a
// subtraction operation of a - b using the "Sub" method. If there are any negative value in "a" after this
// operation, then "a" is less than "b", a boolean value, "true" is returned, otherwise
// the boolean value, "false" is returned.
func (a ComputeResourcesFloat) IsLessThan(b ComputeResourcesFloat) bool {
reduced := a.DeepCopy()
reduced.Sub(b)
return !reduced.IsValid()
}
// LimitWith function limits the values of "a" to the corresponding values in the limit object.
// It creates a new ComputeResourcesFloat object with the same keys as the original "a" object,
// but with values limited by the corresponding values in the limit object using the math.Min() function.
// If any value in the "a" map is greater than its corresponding value in the limit map,
// then that value in "a" is replaced with the corresponding value from the limit map.
// This means that the limit map acts as a maximum limit on the values in the "a" map.
func (a ComputeResourcesFloat) LimitWith(limit ComputeResourcesFloat) ComputeResourcesFloat {
targetComputeResource := make(ComputeResourcesFloat)
for key, value := range a {
targetComputeResource[key] = math.Min(value, limit[key])
}
return targetComputeResource
}
// MergeWith represents the merged in values take precedence and override existing values for the same key
func (a ComputeResourcesFloat) MergeWith(merged ComputeResourcesFloat) ComputeResourcesFloat {
targetComputeResource := a.DeepCopy()
for key, value := range merged {
targetComputeResource[key] = value
}
return targetComputeResource
}
// LimitToZero function limits each value in "a" to a minimum value of zero.
// In the case any value in "a" has a value less than zero, it is replaced with a value of zero.
func (a ComputeResourcesFloat) LimitToZero() {
for key, value := range a {
a[key] = math.Max(value, 0)
}
}
// The Mul function takes a ComputeResources object called "a" and multiplies each value in it with a given "factor".
// It then stores the result of each computation in a new ComputeResourcesFloat object,
// where each key in "a" maps to its corresponding value multiplied by "factor"
func (a ComputeResourcesFloat) Mul(factor float64) ComputeResourcesFloat {
targetComputeResource := make(ComputeResourcesFloat)
for key, value := range a {
targetComputeResource[key] = value * factor
}
return targetComputeResource
}
// TotalPodResourceRequest represents the resource request for a given pod is the maximum of:
// - sum of all containers
// - any individual init container
//
// This is because:
// - containers run in parallel (so need to sum resources)
// - init containers run sequentially (so only their individual resource need be considered)
//
// So pod resource usage is the max for each resource type (cpu/memory etc.) that could be used at any given time
func TotalPodResourceRequest(podSpec *v1.PodSpec) ComputeResources {
totalResources := make(ComputeResources)
for _, container := range podSpec.Containers {
containerResource := FromResourceList(container.Resources.Requests)
totalResources.Add(containerResource)
}
for _, initContainer := range podSpec.InitContainers {
containerResource := FromResourceList(initContainer.Resources.Requests)
totalResources.Max(containerResource)
}
return totalResources
}
// CalculateTotalResource computes the combined total quantity of each resource (cpu, memory, etc) available for scheduling
// in the slice of nodes supplied as argument in the function.
func CalculateTotalResource(nodes []*v1.Node) ComputeResources {
totalResources := make(ComputeResources)
for _, node := range nodes {
nodeAllocatableResource := FromResourceList(node.Status.Allocatable)
totalResources.Add(nodeAllocatableResource)
}
return totalResources
}
// CalculateTotalResourceRequest computes the combined total quantity of each resource (cpu, memory, etc) requested by each pod
// in the slice of pods supplied as argument in the function.
func CalculateTotalResourceRequest(pods []*v1.Pod) ComputeResources {
totalResources := make(ComputeResources)
for _, pod := range pods {
podResource := TotalPodResourceRequest(&pod.Spec)
totalResources.Add(podResource)
}
return totalResources
}