volcano.sh/volcano@v1.9.0/pkg/scheduler/plugins/numaaware/provider/cpumanager/cpu_assignment.go (about) 1 /* 2 Copyright 2021 The Volcano 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 cpumanager 18 19 import ( 20 "fmt" 21 "sort" 22 23 "k8s.io/klog/v2" 24 "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/topology" 25 "k8s.io/utils/cpuset" 26 ) 27 28 type cpuAccumulator struct { 29 topo *topology.CPUTopology 30 details topology.CPUDetails 31 numCPUsNeeded int 32 result cpuset.CPUSet 33 } 34 35 func newCPUAccumulator(topo *topology.CPUTopology, availableCPUs cpuset.CPUSet, numCPUs int) *cpuAccumulator { 36 return &cpuAccumulator{ 37 topo: topo, 38 details: topo.CPUDetails.KeepOnly(availableCPUs), 39 numCPUsNeeded: numCPUs, 40 result: cpuset.New(), 41 } 42 } 43 44 func (a *cpuAccumulator) take(cpus cpuset.CPUSet) { 45 a.result = a.result.Union(cpus) 46 a.details = a.details.KeepOnly(a.details.CPUs().Difference(a.result)) 47 a.numCPUsNeeded -= cpus.Size() 48 } 49 50 // freeSockets Returns free socket IDs as a slice sorted by: 51 // - socket ID, ascending. 52 func (a *cpuAccumulator) freeSockets() []int { 53 return a.details.Sockets().Intersection(a.details.CPUs()).List() 54 } 55 56 // freeCores Returns core IDs as a slice sorted by: 57 // - the number of whole available cores on the socket, ascending 58 // - socket ID, ascending 59 // - core ID, ascending 60 func (a *cpuAccumulator) freeCores() []int { 61 socketIDs := a.details.Sockets().UnsortedList() 62 sort.Slice(socketIDs, 63 func(i, j int) bool { 64 iCores := a.details.CoresInSockets(socketIDs[i]).Intersection(a.details.CPUs()) 65 jCores := a.details.CoresInSockets(socketIDs[j]).Intersection(a.details.CPUs()) 66 return iCores.Size() < jCores.Size() || socketIDs[i] < socketIDs[j] 67 }) 68 69 coreIDs := []int{} 70 for _, s := range socketIDs { 71 coreIDs = append(coreIDs, a.details.CoresInSockets(s).Intersection(a.details.CPUs()).List()...) 72 } 73 return coreIDs 74 } 75 76 // freeCPUs Returns CPU IDs as a slice sorted by: 77 // - socket affinity with result 78 // - number of CPUs available on the same socket 79 // - number of CPUs available on the same core 80 // - socket ID. 81 // - core ID. 82 func (a *cpuAccumulator) freeCPUs() []int { 83 result := []int{} 84 cores := a.details.Cores().List() 85 86 sort.Slice( 87 cores, 88 func(i, j int) bool { 89 iCore := cores[i] 90 jCore := cores[j] 91 92 iCPUs := a.topo.CPUDetails.CPUsInCores(iCore).List() 93 jCPUs := a.topo.CPUDetails.CPUsInCores(jCore).List() 94 95 iSocket := a.topo.CPUDetails[iCPUs[0]].SocketID 96 jSocket := a.topo.CPUDetails[jCPUs[0]].SocketID 97 98 // Compute the number of CPUs in the result reside on the same socket 99 // as each core. 100 iSocketColoScore := a.topo.CPUDetails.CPUsInSockets(iSocket).Intersection(a.result).Size() 101 jSocketColoScore := a.topo.CPUDetails.CPUsInSockets(jSocket).Intersection(a.result).Size() 102 103 // Compute the number of available CPUs available on the same socket 104 // as each core. 105 iSocketFreeScore := a.details.CPUsInSockets(iSocket).Size() 106 jSocketFreeScore := a.details.CPUsInSockets(jSocket).Size() 107 108 // Compute the number of available CPUs on each core. 109 iCoreFreeScore := a.details.CPUsInCores(iCore).Size() 110 jCoreFreeScore := a.details.CPUsInCores(jCore).Size() 111 112 return iSocketColoScore > jSocketColoScore || 113 iSocketFreeScore < jSocketFreeScore || 114 iCoreFreeScore < jCoreFreeScore || 115 iSocket < jSocket || 116 iCore < jCore 117 }) 118 119 // For each core, append sorted CPU IDs to result. 120 for _, core := range cores { 121 result = append(result, a.details.CPUsInCores(core).List()...) 122 } 123 return result 124 } 125 126 func (a *cpuAccumulator) needs(n int) bool { 127 return a.numCPUsNeeded >= n 128 } 129 130 func (a *cpuAccumulator) isSatisfied() bool { 131 return a.numCPUsNeeded < 1 132 } 133 134 func (a *cpuAccumulator) isFailed() bool { 135 return a.numCPUsNeeded > a.details.CPUs().Size() 136 } 137 138 // takeByTopology return the assigned cpuset 139 func takeByTopology(topo *topology.CPUTopology, availableCPUs cpuset.CPUSet, numCPUs int) (cpuset.CPUSet, error) { 140 acc := newCPUAccumulator(topo, availableCPUs, numCPUs) 141 if acc.isSatisfied() { 142 return acc.result, nil 143 } 144 if acc.isFailed() { 145 return cpuset.New(), fmt.Errorf("not enough cpus available to satisfy request") 146 } 147 148 // Algorithm: topology-aware best-fit 149 // 1. Acquire whole sockets, if available and the container requires at 150 // least a socket's-worth of CPUs. 151 if acc.needs(acc.topo.CPUsPerSocket()) { 152 for _, s := range acc.freeSockets() { 153 klog.V(4).Infof("[cpumanager] takeByTopology: claiming socket [%d]", s) 154 acc.take(acc.details.CPUsInSockets(s)) 155 if acc.isSatisfied() { 156 return acc.result, nil 157 } 158 if !acc.needs(acc.topo.CPUsPerSocket()) { 159 break 160 } 161 } 162 } 163 164 // 2. Acquire whole cores, if available and the container requires at least 165 // a core's-worth of CPUs. 166 if acc.needs(acc.topo.CPUsPerCore()) { 167 for _, c := range acc.freeCores() { 168 klog.V(4).Infof("[cpumanager] takeByTopology: claiming core [%d]", c) 169 acc.take(acc.details.CPUsInCores(c)) 170 if acc.isSatisfied() { 171 return acc.result, nil 172 } 173 if !acc.needs(acc.topo.CPUsPerCore()) { 174 break 175 } 176 } 177 } 178 179 // 3. Acquire single threads, preferring to fill partially-allocated cores 180 // on the same sockets as the whole cores we have already taken in this 181 // allocation. 182 for _, c := range acc.freeCPUs() { 183 klog.V(4).Infof("[cpumanager] takeByTopology: claiming CPU [%d]", c) 184 if acc.needs(1) { 185 acc.take(cpuset.New(c)) 186 } 187 if acc.isSatisfied() { 188 return acc.result, nil 189 } 190 } 191 192 return cpuset.New(), fmt.Errorf("failed to allocate cpus") 193 }