github.com/kubewharf/katalyst-core@v0.5.3/pkg/agent/orm/topology/policy.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 "k8s.io/klog/v2" 21 22 "github.com/kubewharf/katalyst-core/pkg/util/bitmask" 23 ) 24 25 // Policy interface for Topology Manager Pod Admit Result 26 type Policy interface { 27 // Returns Policy Name 28 Name() string 29 // Returns a merged TopologyHint based on input from hint providers 30 // and a Pod Admit Handler Response based on hints and policy type 31 Merge(providersHints []map[string][]TopologyHint) (map[string]TopologyHint, bool) 32 } 33 34 // Merge a TopologyHints permutation to a single hint by performing a bitwise-AND 35 // of their affinity masks. The hint shall be preferred if all hits in the permutation 36 // are preferred. 37 func mergePermutation(numaNodes []int, permutation []TopologyHint) TopologyHint { 38 // Get the NUMANodeAffinity from each hint in the permutation and see if any 39 // of them encode unpreferred allocations. 40 preferred := true 41 defaultAffinity, _ := bitmask.NewBitMask(numaNodes...) 42 var numaAffinities []bitmask.BitMask 43 for _, hint := range permutation { 44 // Only consider hints that have an actual NUMANodeAffinity set. 45 if hint.NUMANodeAffinity != nil { 46 numaAffinities = append(numaAffinities, hint.NUMANodeAffinity) 47 // Only mark preferred if all affinities are equal. 48 if !hint.NUMANodeAffinity.IsEqual(numaAffinities[0]) { 49 preferred = false 50 } 51 } 52 // Only mark preferred if all affinities are preferred. 53 if !hint.Preferred { 54 preferred = false 55 } 56 } 57 58 // Merge the affinities using a bitwise-and operation. 59 mergedAffinity := bitmask.And(defaultAffinity, numaAffinities...) 60 // Build a mergedHint from the merged affinity mask, setting preferred as 61 // appropriate based on the logic above. 62 return TopologyHint{mergedAffinity, preferred} 63 } 64 65 func filterProvidersHints(providersHints []map[string][]TopologyHint) ([][]TopologyHint, []string) { 66 // Loop through all hint providers and save an accumulated list of the 67 // hints returned by each hint provider. If no hints are provided, assume 68 // that provider has no preference for topology-aware allocation. 69 var ( 70 allProviderHints [][]TopologyHint 71 resourceNames []string 72 ) 73 for _, hints := range providersHints { 74 // If hints is nil, insert a single, preferred any-numa hint into allProviderHints. 75 if len(hints) == 0 { 76 klog.InfoS("Hint Provider has no preference for NUMA affinity with any resource") 77 allProviderHints = append(allProviderHints, []TopologyHint{{nil, true}}) 78 // Here, we add a defaultResourceKey to resourceNames because we don't know 79 // which resource this hint is for. 80 resourceNames = append(resourceNames, defaultResourceKey) 81 continue 82 } 83 84 // Otherwise, accumulate the hints for each resource type into allProviderHints. 85 for resource := range hints { 86 resourceNames = append(resourceNames, resource) 87 if hints[resource] == nil { 88 klog.InfoS("Hint Provider has no preference for NUMA affinity with resource", "resource", resource) 89 allProviderHints = append(allProviderHints, []TopologyHint{{nil, true}}) 90 continue 91 } 92 93 if len(hints[resource]) == 0 { 94 klog.InfoS("Hint Provider has no possible NUMA affinities for resource", "resource", resource) 95 allProviderHints = append(allProviderHints, []TopologyHint{{nil, false}}) 96 continue 97 } 98 99 allProviderHints = append(allProviderHints, hints[resource]) 100 } 101 } 102 return allProviderHints, resourceNames 103 } 104 105 func narrowestHint(hints []TopologyHint) *TopologyHint { 106 if len(hints) == 0 { 107 return nil 108 } 109 var narrowestHint *TopologyHint 110 for i := range hints { 111 if hints[i].NUMANodeAffinity == nil { 112 continue 113 } 114 if narrowestHint == nil { 115 narrowestHint = &hints[i] 116 } 117 if hints[i].NUMANodeAffinity.IsNarrowerThan(narrowestHint.NUMANodeAffinity) { 118 narrowestHint = &hints[i] 119 } 120 } 121 return narrowestHint 122 } 123 124 func maxOfMinAffinityCounts(filteredHints [][]TopologyHint) int { 125 maxOfMinCount := 0 126 for _, resourceHints := range filteredHints { 127 narrowestHint := narrowestHint(resourceHints) 128 if narrowestHint == nil { 129 continue 130 } 131 if narrowestHint.NUMANodeAffinity.Count() > maxOfMinCount { 132 maxOfMinCount = narrowestHint.NUMANodeAffinity.Count() 133 } 134 } 135 return maxOfMinCount 136 } 137 138 func compareHints(bestNonPreferredAffinityCount int, current *TopologyHint, candidate *TopologyHint) *TopologyHint { 139 // Only consider candidates that result in a NUMANodeAffinity > 0 to 140 // replace the current bestHint. 141 if candidate.NUMANodeAffinity.Count() == 0 { 142 return current 143 } 144 145 // If no current bestHint is set, return the candidate as the bestHint. 146 if current == nil { 147 return candidate 148 } 149 150 // If the current bestHint is non-preferred and the candidate hint is 151 // preferred, always choose the preferred hint over the non-preferred one. 152 if !current.Preferred && candidate.Preferred { 153 return candidate 154 } 155 156 // If the current bestHint is preferred and the candidate hint is 157 // non-preferred, never update the bestHint, regardless of the 158 // candidate hint's narowness. 159 if current.Preferred && !candidate.Preferred { 160 return current 161 } 162 163 // If the current bestHint and the candidate hint are both preferred, 164 // then only consider candidate hints that have a narrower 165 // NUMANodeAffinity than the NUMANodeAffinity in the current bestHint. 166 if current.Preferred && candidate.Preferred { 167 if candidate.NUMANodeAffinity.IsNarrowerThan(current.NUMANodeAffinity) { 168 return candidate 169 } 170 return current 171 } 172 173 // The only case left is if the current best bestHint and the candidate 174 // hint are both non-preferred. In this case, try and find a hint whose 175 // affinity count is as close to (but not higher than) the 176 // bestNonPreferredAffinityCount as possible. To do this we need to 177 // consider the following cases and react accordingly: 178 // 179 // 1. current.NUMANodeAffinity.Count() > bestNonPreferredAffinityCount 180 // 2. current.NUMANodeAffinity.Count() == bestNonPreferredAffinityCount 181 // 3. current.NUMANodeAffinity.Count() < bestNonPreferredAffinityCount 182 // 183 // For case (1), the current bestHint is larger than the 184 // bestNonPreferredAffinityCount, so updating to any narrower mergeHint 185 // is preferred over staying where we are. 186 // 187 // For case (2), the current bestHint is equal to the 188 // bestNonPreferredAffinityCount, so we would like to stick with what 189 // we have *unless* the candidate hint is also equal to 190 // bestNonPreferredAffinityCount and it is narrower. 191 // 192 // For case (3), the current bestHint is less than 193 // bestNonPreferredAffinityCount, so we would like to creep back up to 194 // bestNonPreferredAffinityCount as close as we can. There are three 195 // cases to consider here: 196 // 197 // 3a. candidate.NUMANodeAffinity.Count() > bestNonPreferredAffinityCount 198 // 3b. candidate.NUMANodeAffinity.Count() == bestNonPreferredAffinityCount 199 // 3c. candidate.NUMANodeAffinity.Count() < bestNonPreferredAffinityCount 200 // 201 // For case (3a), we just want to stick with the current bestHint 202 // because choosing a new hint that is greater than 203 // bestNonPreferredAffinityCount would be counter-productive. 204 // 205 // For case (3b), we want to immediately update bestHint to the 206 // candidate hint, making it now equal to bestNonPreferredAffinityCount. 207 // 208 // For case (3c), we know that *both* the current bestHint and the 209 // candidate hint are less than bestNonPreferredAffinityCount, so we 210 // want to choose one that brings us back up as close to 211 // bestNonPreferredAffinityCount as possible. There are three cases to 212 // consider here: 213 // 214 // 3ca. candidate.NUMANodeAffinity.Count() > current.NUMANodeAffinity.Count() 215 // 3cb. candidate.NUMANodeAffinity.Count() < current.NUMANodeAffinity.Count() 216 // 3cc. candidate.NUMANodeAffinity.Count() == current.NUMANodeAffinity.Count() 217 // 218 // For case (3ca), we want to immediately update bestHint to the 219 // candidate hint because that will bring us closer to the (higher) 220 // value of bestNonPreferredAffinityCount. 221 // 222 // For case (3cb), we want to stick with the current bestHint because 223 // choosing the candidate hint would strictly move us further away from 224 // the bestNonPreferredAffinityCount. 225 // 226 // Finally, for case (3cc), we know that the current bestHint and the 227 // candidate hint are equal, so we simply choose the narrower of the 2. 228 229 // Case 1 230 if current.NUMANodeAffinity.Count() > bestNonPreferredAffinityCount { 231 if candidate.NUMANodeAffinity.IsNarrowerThan(current.NUMANodeAffinity) { 232 return candidate 233 } 234 return current 235 } 236 // Case 2 237 if current.NUMANodeAffinity.Count() == bestNonPreferredAffinityCount { 238 if candidate.NUMANodeAffinity.Count() != bestNonPreferredAffinityCount { 239 return current 240 } 241 if candidate.NUMANodeAffinity.IsNarrowerThan(current.NUMANodeAffinity) { 242 return candidate 243 } 244 return current 245 } 246 // Case 3a 247 if candidate.NUMANodeAffinity.Count() > bestNonPreferredAffinityCount { 248 return current 249 } 250 // Case 3b 251 if candidate.NUMANodeAffinity.Count() == bestNonPreferredAffinityCount { 252 return candidate 253 } 254 // Case 3ca 255 if candidate.NUMANodeAffinity.Count() > current.NUMANodeAffinity.Count() { 256 return candidate 257 } 258 // Case 3cb 259 if candidate.NUMANodeAffinity.Count() < current.NUMANodeAffinity.Count() { 260 return current 261 } 262 // Case 3cc 263 if candidate.NUMANodeAffinity.IsNarrowerThan(current.NUMANodeAffinity) { 264 return candidate 265 } 266 return current 267 } 268 269 func mergeFilteredHints(numaNodes []int, filteredHints [][]TopologyHint) TopologyHint { 270 // Set bestNonPreferredAffinityCount to help decide which affinity mask is 271 // preferred amongst all non-preferred hints. We calculate this value as 272 // the maximum of the minimum affinity counts supplied for any given hint 273 // provider. In other words, prefer a hint that has an affinity mask that 274 // includes all of the NUMA nodes from the provider that requires the most 275 // NUMA nodes to satisfy its allocation. 276 bestNonPreferredAffinityCount := maxOfMinAffinityCounts(filteredHints) 277 278 var bestHint *TopologyHint 279 iterateAllProviderTopologyHints(filteredHints, func(permutation []TopologyHint) { 280 // Get the NUMANodeAffinity from each hint in the permutation and see if any 281 // of them encode unpreferred allocations. 282 mergedHint := mergePermutation(numaNodes, permutation) 283 284 // Compare the current bestHint with the candidate mergedHint and 285 // update bestHint if appropriate. 286 bestHint = compareHints(bestNonPreferredAffinityCount, bestHint, &mergedHint) 287 }) 288 289 if bestHint == nil { 290 defaultAffinity, _ := bitmask.NewBitMask(numaNodes...) 291 bestHint = &TopologyHint{defaultAffinity, false} 292 } 293 294 return *bestHint 295 } 296 297 // Iterate over all permutations of hints in 'allProviderHints [][]TopologyHint'. 298 // 299 // This procedure is implemented as a recursive function over the set of hints 300 // in 'allproviderHints[i]'. It applies the function 'callback' to each 301 // permutation as it is found. It is the equivalent of: 302 // 303 // for i := 0; i < len(providerHints[0]); i++ 304 // 305 // for j := 0; j < len(providerHints[1]); j++ 306 // for k := 0; k < len(providerHints[2]); k++ 307 // ... 308 // for z := 0; z < len(providerHints[-1]); z++ 309 // permutation := []TopologyHint{ 310 // providerHints[0][i], 311 // providerHints[1][j], 312 // providerHints[2][k], 313 // ... 314 // providerHints[-1][z] 315 // } 316 // callback(permutation) 317 func iterateAllProviderTopologyHints(allProviderHints [][]TopologyHint, callback func([]TopologyHint)) { 318 // Internal helper function to accumulate the permutation before calling the callback. 319 var iterate func(i int, accum []TopologyHint) 320 iterate = func(i int, accum []TopologyHint) { 321 // Base case: we have looped through all providers and have a full permutation. 322 if i == len(allProviderHints) { 323 callback(accum) 324 return 325 } 326 327 // Loop through all hints for provider 'i', and recurse to build the 328 // the permutation of this hint with all hints from providers 'i++'. 329 for j := range allProviderHints[i] { 330 iterate(i+1, append(accum, allProviderHints[i][j])) 331 } 332 } 333 iterate(0, []TopologyHint{}) 334 } 335 336 // generateResourceHints generates the map from resourceName to given hint. 337 // all providers get a same bestHint under native policy(None,best_effort,restricted,single_numa_node), 338 // we just map resources to the bestHint 339 func generateResourceHints(resourceNames []string, hint TopologyHint) map[string]TopologyHint { 340 result := make(map[string]TopologyHint) 341 for _, resource := range resourceNames { 342 result[resource] = hint 343 } 344 // If non resourceNames are provided, we add defaultResourceKey here. 345 if len(resourceNames) == 0 { 346 result[defaultResourceKey] = hint 347 } 348 return result 349 }