github.com/kubewharf/katalyst-core@v0.5.3/pkg/agent/orm/topology/policy_numeric.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 topology
    18  
    19  import (
    20  	"sort"
    21  
    22  	v1 "k8s.io/api/core/v1"
    23  	"k8s.io/klog/v2"
    24  
    25  	"github.com/kubewharf/katalyst-core/pkg/util/bitmask"
    26  )
    27  
    28  // numericPolicy implements a policy:
    29  // 1. for align resources, hints should be totally equal and at-least-one preferred.
    30  // 2. for other resources, bigger hints always contains smaller hints.
    31  // 3. more preferredHint count hints permutation is preferred.
    32  // 4. smaller maxNumaCount hints permutation is preferred.
    33  type numericPolicy struct {
    34  	// alignResourceNames are those resources which should be aligned in numa node.
    35  	alignResourceNames []string
    36  }
    37  
    38  // PolicyNumeric policy name.
    39  const PolicyNumeric string = "numeric"
    40  
    41  var defaultAlignResourceNames = []string{v1.ResourceCPU.String(), v1.ResourceMemory.String()}
    42  
    43  // NewNumericPolicy returns numeric policy.
    44  func NewNumericPolicy(alignResourceNames []string) Policy {
    45  	if alignResourceNames == nil {
    46  		alignResourceNames = defaultAlignResourceNames
    47  	}
    48  	return &numericPolicy{
    49  		alignResourceNames: alignResourceNames,
    50  	}
    51  }
    52  
    53  // Name returns numericPolicy name
    54  func (p *numericPolicy) Name() string {
    55  	return PolicyNumeric
    56  }
    57  
    58  func (p *numericPolicy) Merge(providersHints []map[string][]TopologyHint) (map[string]TopologyHint, bool) {
    59  	if len(providersHints) == 0 {
    60  		return map[string]TopologyHint{
    61  			defaultResourceKey: {nil, true},
    62  		}, true
    63  	}
    64  
    65  	if existEmptyHintSlice(providersHints) {
    66  		klog.Infof("[numeric_policy] admit failed due to existing empty hint slice")
    67  		return nil, false
    68  	}
    69  
    70  	filteredHints, resourceNames := filterProvidersHints(providersHints)
    71  
    72  	bestHints := findBestNumericPermutation(filteredHints, getAlignResourceIndexes(resourceNames, p.alignResourceNames))
    73  	// no permutation fits the policy
    74  	if bestHints == nil {
    75  		return nil, false
    76  	}
    77  
    78  	if len(bestHints) != len(resourceNames) {
    79  		// This should not happen.
    80  		klog.Warningf("[numeric policy] wrong hints length %d vs resource length %d", len(bestHints), len(resourceNames))
    81  		return nil, false
    82  	}
    83  
    84  	result := make(map[string]TopologyHint)
    85  	for i := range resourceNames {
    86  		result[resourceNames[i]] = bestHints[i]
    87  	}
    88  	return result, true
    89  }
    90  
    91  // existEmptyHintSlice returns true if there is empty hint slice in providersHints
    92  func existEmptyHintSlice(providersHints []map[string][]TopologyHint) bool {
    93  	for _, hints := range providersHints {
    94  		for resource := range hints {
    95  			// hint providers return nil if there is no possible NUMA affinity for resource
    96  			// hint providers return empty slice if there is no preference NUMA affinity for resource
    97  			if hints[resource] != nil && len(hints[resource]) == 0 {
    98  				klog.Infof("[numeric_policy] hint Provider has no possible NUMA affinity for resource: %s", resource)
    99  				return true
   100  			}
   101  		}
   102  	}
   103  
   104  	return false
   105  }
   106  
   107  // findBestNumericPermutation finds the best numeric permutation.
   108  func findBestNumericPermutation(filteredHints [][]TopologyHint, alignResourceIndexes []int) []TopologyHint {
   109  	var bestHints []TopologyHint
   110  
   111  	iterateAllProviderTopologyHints(filteredHints, func(permutation []TopologyHint) {
   112  		// the length of permutation and the order of the resources hints in it are equal to filteredHints,
   113  		// align and unaligned resource hints can be filtered by alignResourceIndexes
   114  
   115  		// 1. check if align resource hints are equal,
   116  		// and there should be at least one preferred hint.
   117  		var alignHasPreferred bool
   118  		for i := 0; i < len(alignResourceIndexes)-1; i++ {
   119  			cur := alignResourceIndexes[i]
   120  			next := alignResourceIndexes[i+1]
   121  
   122  			if !numaAffinityAligned(permutation[cur], permutation[next]) {
   123  				// hints are not aligned
   124  				return
   125  			}
   126  			alignHasPreferred = permutation[cur].Preferred || permutation[next].Preferred
   127  		}
   128  		if len(alignResourceIndexes) == 1 {
   129  			alignHasPreferred = permutation[alignResourceIndexes[0]].Preferred
   130  		}
   131  		if len(alignResourceIndexes) > 0 && !alignHasPreferred {
   132  			// all hints are not preferred
   133  			return
   134  		}
   135  
   136  		// 2. check if bigger numa-node hints contains smaller numa-node hints.
   137  		if !isSubsetPermutation(permutation) {
   138  			return
   139  		}
   140  
   141  		if bestHints == nil {
   142  			bestHints = DeepCopyTopologyHints(permutation)
   143  		}
   144  
   145  		// 3. If preferredHint count beside align resources in this permutation is larger than
   146  		// that in current bestHints, always choose more preferredHint permutation.
   147  		if preferredCountBesideAlign(permutation, alignResourceIndexes) >
   148  			preferredCountBesideAlign(bestHints, alignResourceIndexes) {
   149  			bestHints = DeepCopyTopologyHints(permutation)
   150  			return
   151  		}
   152  
   153  		// 4. Only Consider permutation that have smaller maxNumaCount than the
   154  		// maxNumaCount in the current bestHint.
   155  		if getMaxNumaCount(permutation) < getMaxNumaCount(bestHints) {
   156  			bestHints = DeepCopyTopologyHints(permutation)
   157  			return
   158  		}
   159  	})
   160  
   161  	return bestHints
   162  }
   163  
   164  // getAlignResourceIndexes gets align resource indexes in resources array.
   165  func getAlignResourceIndexes(resources []string, alignResourceNames []string) []int {
   166  	resourceIndexes := make(map[string]int)
   167  	for i, rn := range resources {
   168  		resourceIndexes[rn] = i
   169  	}
   170  	var result []int
   171  	for _, align := range alignResourceNames {
   172  		index, ok := resourceIndexes[align]
   173  		if ok {
   174  			result = append(result, index)
   175  		}
   176  	}
   177  	return result
   178  }
   179  
   180  // getMaxNumaCount returns the max numa count in the given hints.
   181  func getMaxNumaCount(permutation []TopologyHint) int {
   182  	var result int
   183  	for _, hint := range permutation {
   184  		if hint.NUMANodeAffinity == nil {
   185  			continue
   186  		}
   187  		if hint.NUMANodeAffinity.Count() > result {
   188  			result = hint.NUMANodeAffinity.Count()
   189  		}
   190  	}
   191  	return result
   192  }
   193  
   194  // preferredCountBesideAlign counts the preferred hints beside align resources.
   195  func preferredCountBesideAlign(hints []TopologyHint, alignIndexes []int) int {
   196  	var result int
   197  	alignIndexesMap := map[int]bool{}
   198  	for _, index := range alignIndexes {
   199  		alignIndexesMap[index] = true
   200  	}
   201  	for i, hint := range hints {
   202  		if _, ok := alignIndexesMap[i]; ok {
   203  			continue
   204  		}
   205  		if hint.Preferred {
   206  			result++
   207  		}
   208  	}
   209  	return result
   210  }
   211  
   212  // numaAffinityAligned checks a,b TopologyHint could be aligned or not.
   213  func numaAffinityAligned(a, b TopologyHint) bool {
   214  	if a.NUMANodeAffinity == nil && b.NUMANodeAffinity == nil {
   215  		return a.Preferred == b.Preferred
   216  	} else if a.NUMANodeAffinity == nil { // b.NUMANodeAffinity != nil
   217  		// if a.Preferred, there is no NUMA preference for a, so it can be aligned with b.
   218  		return a.Preferred
   219  	} else if b.NUMANodeAffinity == nil { // a.NUMANodeAffinity != nil
   220  		// if b.Preferred, there is no NUMA preference for b, so it can be aligned with a.
   221  		return b.Preferred
   222  	}
   223  
   224  	// NUMANodeAffinity of alignResources should be totally equal
   225  	return a.NUMANodeAffinity.IsEqual(b.NUMANodeAffinity)
   226  }
   227  
   228  // isSubsetPermutation checks whether permutation meets that bigger numa-node hints always
   229  // contain smaller numa-node hints or not.
   230  func isSubsetPermutation(permutation []TopologyHint) bool {
   231  	// When NUMANodeAffinity is nil,  means this has no preference.
   232  	// We should ignore it.
   233  	var filters []TopologyHint
   234  	for _, hint := range permutation {
   235  		if hint.NUMANodeAffinity != nil {
   236  			filters = append(filters, hint)
   237  		}
   238  	}
   239  
   240  	// Sort from small numa node count to big count.
   241  	sort.Slice(filters, func(i, j int) bool {
   242  		return filters[i].NUMANodeAffinity.Count() <= filters[j].NUMANodeAffinity.Count()
   243  	})
   244  
   245  	for i := 0; i < len(filters)-1; i++ {
   246  		cur := filters[i]
   247  		next := filters[i+1]
   248  		if !bitmask.And(next.NUMANodeAffinity, cur.NUMANodeAffinity).IsEqual(cur.NUMANodeAffinity) {
   249  			return false
   250  		}
   251  	}
   252  
   253  	return true
   254  }