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  }