github.com/kubewharf/katalyst-core@v0.5.3/pkg/util/native/resources.go (about) 1 /* 2 Copyright 2022 The Katalyst Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package native 18 19 import ( 20 "fmt" 21 "sort" 22 "strconv" 23 "strings" 24 "sync" 25 26 v1 "k8s.io/api/core/v1" 27 "k8s.io/apimachinery/pkg/api/resource" 28 "k8s.io/klog/v2" 29 30 "github.com/kubewharf/katalyst-api/pkg/consts" 31 "github.com/kubewharf/katalyst-core/pkg/metrics" 32 ) 33 34 type QuantityGetter func(resourceList v1.ResourceList) resource.Quantity 35 36 var ( 37 quantityGetterMutex = sync.RWMutex{} 38 cpuQuantityGetter = DefaultCPUQuantityGetter 39 memoryQuantityGetter = DefaultMemoryQuantityGetter 40 ) 41 42 func CPUQuantityGetter() QuantityGetter { 43 quantityGetterMutex.RLock() 44 defer quantityGetterMutex.RUnlock() 45 46 return cpuQuantityGetter 47 } 48 49 func SetCPUQuantityGetter(getter QuantityGetter) { 50 quantityGetterMutex.Lock() 51 defer quantityGetterMutex.Unlock() 52 53 cpuQuantityGetter = getter 54 } 55 56 func MemoryQuantityGetter() QuantityGetter { 57 quantityGetterMutex.RLock() 58 defer quantityGetterMutex.RUnlock() 59 60 return memoryQuantityGetter 61 } 62 63 func SetMemoryQuantityGetter(getter QuantityGetter) { 64 quantityGetterMutex.Lock() 65 defer quantityGetterMutex.Unlock() 66 67 memoryQuantityGetter = getter 68 } 69 70 // DefaultCPUQuantityGetter returns cpu quantity for resourceList. since we may have 71 // different representations for cpu resource name, the prioritizes will be: 72 // native cpu name -> reclaimed milli cpu name 73 func DefaultCPUQuantityGetter(resourceList v1.ResourceList) resource.Quantity { 74 if quantity, ok := resourceList[v1.ResourceCPU]; ok { 75 return quantity 76 } 77 78 if quantity, ok := resourceList[consts.ReclaimedResourceMilliCPU]; ok { 79 return *resource.NewMilliQuantity(quantity.Value(), quantity.Format) 80 } 81 82 return resource.Quantity{} 83 } 84 85 // DefaultMemoryQuantityGetter returns memory quantity for resourceList. since we may have 86 // different representations for memory resource name, the prioritizes will be: 87 // native memory name -> reclaimed memory name 88 func DefaultMemoryQuantityGetter(resourceList v1.ResourceList) resource.Quantity { 89 if quantity, ok := resourceList[v1.ResourceMemory]; ok { 90 return quantity 91 } 92 93 if quantity, ok := resourceList[consts.ReclaimedResourceMemory]; ok { 94 return quantity 95 } 96 97 return resource.Quantity{} 98 } 99 100 // ResourceThreshold is map of resource name to threshold of water level 101 type ResourceThreshold map[v1.ResourceName]float64 102 103 func (t *ResourceThreshold) Type() string { 104 return "ResourceThreshold" 105 } 106 107 func (t *ResourceThreshold) String() string { 108 var pairs []string 109 for k, v := range *t { 110 pairs = append(pairs, fmt.Sprintf("%s=%f", k, v)) 111 } 112 sort.Strings(pairs) 113 return strings.Join(pairs, ",") 114 } 115 116 func (t *ResourceThreshold) Set(value string) error { 117 for _, s := range strings.Split(value, ",") { 118 if len(s) == 0 { 119 continue 120 } 121 arr := strings.SplitN(s, "=", 2) 122 if len(arr) == 2 { 123 parseFloat, err := strconv.ParseFloat(arr[1], 64) 124 if err != nil { 125 return err 126 } 127 (*t)[v1.ResourceName(strings.TrimSpace(arr[0]))] = parseFloat 128 } 129 } 130 return nil 131 } 132 133 // ResourcesEqual checks whether the given resources are equal with each other 134 func ResourcesEqual(a, b v1.ResourceList) bool { 135 if len(a) != len(b) { 136 return false 137 } 138 139 for name, quantity := range a { 140 if !quantity.Equal(b[name]) { 141 return false 142 } 143 } 144 145 return true 146 } 147 148 // ResourcesLess checks whether the given resources a are less than b 149 func ResourcesLess(a, b v1.ResourceList, resourceNameList []v1.ResourceName) bool { 150 for _, name := range resourceNameList { 151 quantityA, okA := a[name] 152 quantityB, okB := b[name] 153 if okA != okB { 154 return okA 155 } else if okA { 156 if quantityA.Cmp(quantityB) < 0 { 157 return true 158 } 159 } 160 } 161 162 return false 163 } 164 165 // AddResources sums up two ResourceList, and returns the summed as results. 166 func AddResources(a, b v1.ResourceList) v1.ResourceList { 167 res := make(v1.ResourceList) 168 for resourceName := range a { 169 res[resourceName] = a[resourceName].DeepCopy() 170 } 171 for resourceName := range b { 172 quantity := b[resourceName].DeepCopy() 173 if _, ok := res[resourceName]; ok { 174 quantity.Add(res[resourceName]) 175 } 176 177 res[resourceName] = quantity 178 } 179 return res 180 } 181 182 // MergeResources merge multi ResourceList into one ResourceList, the resource of 183 // same resource name in all ResourceList we only use the first merged one. 184 func MergeResources(updateList ...*v1.ResourceList) *v1.ResourceList { 185 var result *v1.ResourceList 186 187 for _, update := range updateList { 188 if update == nil { 189 continue 190 } 191 192 if result == nil { 193 result = &v1.ResourceList{} 194 } 195 196 for name, res := range *update { 197 if _, ok := (*result)[name]; !ok { 198 (*result)[name] = res.DeepCopy() 199 } 200 } 201 } 202 203 return result 204 } 205 206 // EmitResourceMetrics emit metrics for given ResourceList. 207 func EmitResourceMetrics(name string, resourceList v1.ResourceList, 208 tags map[string]string, emitter metrics.MetricEmitter, 209 ) { 210 if emitter == nil { 211 klog.Warningf("EmitResourceMetrics by nil emitter") 212 return 213 } 214 215 for k, q := range resourceList { 216 resourceName, value := getResourceMetricsName(k, q) 217 tags["resource"] = resourceName 218 _ = emitter.StoreInt64(name, value, metrics.MetricTypeNameRaw, metrics.ConvertMapToTags(tags)...) 219 } 220 } 221 222 // IsResourceGreaterThan checks if recommended resource is scaling down 223 func IsResourceGreaterThan(a resource.Quantity, b resource.Quantity) bool { 224 return (&a).Cmp(b) > 0 225 } 226 227 // PodResourceDiff checks if pod resources are not the same as the given resource map, both for requests and limits. 228 func PodResourceDiff(pod *v1.Pod, containerResourcesToUpdate map[string]v1.ResourceRequirements) bool { 229 for c, resources := range containerResourcesToUpdate { 230 findContainer := false 231 for _, container := range pod.Spec.Containers { 232 if container.Name == c { 233 findContainer = true 234 for res, q := range resources.Limits { 235 findResource := false 236 for n, l := range container.Resources.Limits { 237 if n == res { 238 findResource = true 239 if !q.Equal(l) { 240 return true 241 } 242 } 243 } 244 if !findResource { 245 return true 246 } 247 } 248 for res, q := range resources.Requests { 249 findResource := false 250 for n, l := range container.Resources.Requests { 251 if n == res { 252 findResource = true 253 if !q.Equal(l) { 254 return true 255 } 256 } 257 } 258 if !findResource { 259 return true 260 } 261 } 262 } 263 } 264 if !findContainer { 265 return false 266 } 267 } 268 return false 269 } 270 271 // ResourceQuantityToInt64Value returns the int64 value according to its resource name 272 func ResourceQuantityToInt64Value(resourceName v1.ResourceName, quantity resource.Quantity) int64 { 273 switch resourceName { 274 case v1.ResourceCPU: 275 return quantity.MilliValue() 276 default: 277 return quantity.Value() 278 } 279 } 280 281 // getResourceMetricsName returns the normalized tags name and accuracy of quantity. 282 func getResourceMetricsName(resourceName v1.ResourceName, quantity resource.Quantity) (string, int64) { 283 return resourceName.String(), ResourceQuantityToInt64Value(resourceName, quantity) 284 } 285 286 // MultiplyResourceQuantity scales quantity according to its resource name. 287 func MultiplyResourceQuantity(resourceName v1.ResourceName, quantity resource.Quantity, y float64) resource.Quantity { 288 switch resourceName { 289 case v1.ResourceCPU: 290 return MultiplyMilliQuantity(quantity, y) 291 case v1.ResourceMemory: 292 fallthrough 293 default: 294 return MultiplyQuantity(quantity, y) 295 } 296 } 297 298 // MultiplyMilliQuantity scales quantity by y. 299 func MultiplyMilliQuantity(quantity resource.Quantity, y float64) resource.Quantity { 300 if 0 == y { 301 return *resource.NewMilliQuantity(0, quantity.Format) 302 } 303 if 1 == y { 304 return quantity 305 } 306 307 milliValue := quantity.MilliValue() 308 if 0 == milliValue { 309 return quantity 310 } 311 312 milliValue = int64(float64(milliValue) * y) 313 return *resource.NewMilliQuantity(milliValue, quantity.Format) 314 } 315 316 // MultiplyQuantity scales quantity by y. 317 func MultiplyQuantity(quantity resource.Quantity, y float64) resource.Quantity { 318 if 0 == y { 319 return *resource.NewQuantity(0, quantity.Format) 320 } 321 if 1 == y { 322 return quantity 323 } 324 325 value := quantity.Value() 326 if 0 == value { 327 return quantity 328 } 329 330 value = int64(float64(value) * y) 331 return *resource.NewQuantity(value, quantity.Format) 332 } 333 334 // AggregateSumQuantities get the sum of quantities 335 func AggregateSumQuantities(quantities []resource.Quantity) *resource.Quantity { 336 var res *resource.Quantity 337 for _, quantity := range quantities { 338 if res == nil { 339 sum := quantity.DeepCopy() 340 res = &sum 341 } else { 342 res.Add(quantity) 343 } 344 } 345 return res 346 } 347 348 // AggregateAvgQuantities get the average of the quantities 349 func AggregateAvgQuantities(quantities []resource.Quantity) *resource.Quantity { 350 sum := AggregateSumQuantities(quantities) 351 var res *resource.Quantity 352 if sum != nil { 353 r := sum.Value() / int64(len(quantities)) 354 res = resource.NewQuantity(r, sum.Format) 355 } 356 357 return res 358 } 359 360 // AggregateMaxQuantities get the maximum of the quantities 361 func AggregateMaxQuantities(quantities []resource.Quantity) *resource.Quantity { 362 var res *resource.Quantity 363 for _, quantity := range quantities { 364 if res == nil || res.Cmp(quantity) < 0 { 365 maxQuantity := quantity.DeepCopy() 366 res = &maxQuantity 367 } 368 } 369 return res 370 }