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  }