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 }