volcano.sh/volcano@v1.9.0/pkg/scheduler/plugins/numaaware/provider/cpumanager/cpu_mng.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 "math" 22 23 v1 "k8s.io/api/core/v1" 24 "k8s.io/klog/v2" 25 "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/topology" 26 "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask" 27 "k8s.io/utils/cpuset" 28 29 "volcano.sh/volcano/pkg/scheduler/api" 30 "volcano.sh/volcano/pkg/scheduler/plugins/numaaware/policy" 31 ) 32 33 type cpuMng struct { 34 } 35 36 // NewProvider return a new provider 37 func NewProvider() policy.HintProvider { 38 return &cpuMng{} 39 } 40 41 // Name return the cpu manager name 42 func (mng *cpuMng) Name() string { 43 return "cpuMng" 44 } 45 46 // guaranteedCPUs return the intger num of request cpu 47 func guaranteedCPUs(container *v1.Container) int { 48 cpuQuantity := container.Resources.Requests[v1.ResourceCPU] 49 if cpuQuantity.Value()*1000 != cpuQuantity.MilliValue() { 50 return 0 51 } 52 53 return int(cpuQuantity.Value()) 54 } 55 56 // generateCPUTopologyHints return the numa topology hints based on 57 // - availableCPUs 58 func generateCPUTopologyHints(availableCPUs cpuset.CPUSet, CPUDetails topology.CPUDetails, request int) []policy.TopologyHint { 59 minAffinitySize := CPUDetails.NUMANodes().Size() 60 hints := []policy.TopologyHint{} 61 bitmask.IterateBitMasks(CPUDetails.NUMANodes().List(), func(mask bitmask.BitMask) { 62 // First, update minAffinitySize for the current request size. 63 cpusInMask := CPUDetails.CPUsInNUMANodes(mask.GetBits()...).Size() 64 if cpusInMask >= request && mask.Count() < minAffinitySize { 65 minAffinitySize = mask.Count() 66 } 67 68 // Then check to see if we have enough CPUs available on the current 69 // numa node bitmask to satisfy the CPU request. 70 numMatching := 0 71 // Finally, check to see if enough available CPUs remain on the current 72 // NUMA node combination to satisfy the CPU request. 73 for _, c := range availableCPUs.List() { 74 if mask.IsSet(CPUDetails[c].NUMANodeID) { 75 numMatching++ 76 } 77 } 78 79 // If they don't, then move onto the next combination. 80 if numMatching < request { 81 return 82 } 83 84 // Otherwise, create a new hint from the numa node bitmask and add it to the 85 // list of hints. We set all hint preferences to 'false' on the first 86 // pass through. 87 hints = append(hints, policy.TopologyHint{ 88 NUMANodeAffinity: mask, 89 Preferred: false, 90 }) 91 }) 92 93 // Loop back through all hints and update the 'Preferred' field based on 94 // counting the number of bits sets in the affinity mask and comparing it 95 // to the minAffinitySize. Only those with an equal number of bits set (and 96 // with a minimal set of numa nodes) will be considered preferred. 97 for i := range hints { 98 if hints[i].NUMANodeAffinity.Count() == minAffinitySize { 99 hints[i].Preferred = true 100 } 101 } 102 103 return hints 104 } 105 106 // getPhysicalCoresNum return the number of physical cores. 107 // The resourc-exporter reports core ids only unique in each socket, 108 // we use the platform unique form to get all physical cores. 109 func getPhysicalCoresNum(CPUDetails topology.CPUDetails) int { 110 uniques := make(map[string]struct{}) 111 for _, v := range CPUDetails { 112 key := fmt.Sprintf("%d/%d", v.SocketID, v.CoreID) 113 uniques[key] = struct{}{} 114 } 115 return len(uniques) 116 } 117 118 func (mng *cpuMng) GetTopologyHints(container *v1.Container, 119 topoInfo *api.NumatopoInfo, resNumaSets api.ResNumaSets) map[string][]policy.TopologyHint { 120 if _, ok := container.Resources.Requests[v1.ResourceCPU]; !ok { 121 klog.Warningf("container %s has no cpu request", container.Name) 122 return nil 123 } 124 125 requestNum := guaranteedCPUs(container) 126 if requestNum == 0 { 127 klog.Warningf(" the cpu request isn't integer in container %s", container.Name) 128 return nil 129 } 130 131 cputopo := &topology.CPUTopology{ 132 NumCPUs: topoInfo.CPUDetail.CPUs().Size(), 133 NumCores: getPhysicalCoresNum(topoInfo.CPUDetail), 134 NumSockets: topoInfo.CPUDetail.Sockets().Size(), 135 CPUDetails: topoInfo.CPUDetail, 136 } 137 138 reserved := cpuset.New() 139 reservedCPUs, ok := topoInfo.ResReserved[v1.ResourceCPU] 140 if ok { 141 // Take the ceiling of the reservation, since fractional CPUs cannot be 142 // exclusively allocated. 143 reservedCPUsFloat := float64(reservedCPUs.MilliValue()) / 1000 144 numReservedCPUs := int(math.Ceil(reservedCPUsFloat)) 145 reserved, _ = takeByTopology(cputopo, cputopo.CPUDetails.CPUs(), numReservedCPUs) 146 klog.V(4).Infof("[cpumanager] reserve cpuset :%v", reserved) 147 } 148 149 availableCPUSet, ok := resNumaSets[string(v1.ResourceCPU)] 150 if !ok { 151 klog.Warningf("no cpu resource") 152 return nil 153 } 154 155 availableCPUSet = availableCPUSet.Difference(reserved) 156 klog.V(4).Infof("requested: %d, availableCPUSet: %v", requestNum, availableCPUSet) 157 return map[string][]policy.TopologyHint{ 158 string(v1.ResourceCPU): generateCPUTopologyHints(availableCPUSet, topoInfo.CPUDetail, requestNum), 159 } 160 } 161 162 func (mng *cpuMng) Allocate(container *v1.Container, bestHit *policy.TopologyHint, 163 topoInfo *api.NumatopoInfo, resNumaSets api.ResNumaSets) map[string]cpuset.CPUSet { 164 cputopo := &topology.CPUTopology{ 165 NumCPUs: topoInfo.CPUDetail.CPUs().Size(), 166 NumCores: getPhysicalCoresNum(topoInfo.CPUDetail), 167 NumSockets: topoInfo.CPUDetail.Sockets().Size(), 168 CPUDetails: topoInfo.CPUDetail, 169 } 170 171 reserved := cpuset.New() 172 reservedCPUs, ok := topoInfo.ResReserved[v1.ResourceCPU] 173 if ok { 174 // Take the ceiling of the reservation, since fractional CPUs cannot be 175 // exclusively allocated. 176 reservedCPUsFloat := float64(reservedCPUs.MilliValue()) / 1000 177 numReservedCPUs := int(math.Ceil(reservedCPUsFloat)) 178 reserved, _ = takeByTopology(cputopo, cputopo.CPUDetails.CPUs(), numReservedCPUs) 179 klog.V(3).Infof("[cpumanager] reserve cpuset :%v", reserved) 180 } 181 182 requestNum := guaranteedCPUs(container) 183 availableCPUSet := resNumaSets[string(v1.ResourceCPU)] 184 availableCPUSet = availableCPUSet.Difference(reserved) 185 186 klog.V(4).Infof("alignedCPUs: %v requestNum: %v bestHit %v", availableCPUSet, requestNum, bestHit) 187 188 result := cpuset.New() 189 if bestHit.NUMANodeAffinity != nil { 190 alignedCPUs := cpuset.New() 191 for _, numaNodeID := range bestHit.NUMANodeAffinity.GetBits() { 192 alignedCPUs = alignedCPUs.Union(availableCPUSet.Intersection(cputopo.CPUDetails.CPUsInNUMANodes(numaNodeID))) 193 } 194 195 numAlignedToAlloc := alignedCPUs.Size() 196 if requestNum < numAlignedToAlloc { 197 numAlignedToAlloc = requestNum 198 } 199 200 alignedCPUs, err := takeByTopology(cputopo, alignedCPUs, numAlignedToAlloc) 201 if err != nil { 202 return map[string]cpuset.CPUSet{ 203 string(v1.ResourceCPU): cpuset.New(), 204 } 205 } 206 207 result = result.Union(alignedCPUs) 208 } 209 210 // Get any remaining CPUs from what's leftover after attempting to grab aligned ones. 211 remainingCPUs, err := takeByTopology(cputopo, availableCPUSet.Difference(result), requestNum-result.Size()) 212 if err != nil { 213 return map[string]cpuset.CPUSet{ 214 string(v1.ResourceCPU): cpuset.New(), 215 } 216 } 217 218 result = result.Union(remainingCPUs) 219 220 return map[string]cpuset.CPUSet{ 221 string(v1.ResourceCPU): result, 222 } 223 }