github.com/kubewharf/katalyst-core@v0.5.3/pkg/agent/qrm-plugins/cpu/nativepolicy/policy_hint_handlers.go (about)

     1  /*
     2  Copyright 2022 The Katalyst Authors.
     3  Copyright 2017 The Kubernetes Authors.
     4  
     5  Licensed under the Apache License, Version 2.0 (the "License");
     6  you may not use this file except in compliance with the License.
     7  You may obtain a copy of the License at
     8  
     9      http://www.apache.org/licenses/LICENSE-2.0
    10  
    11  Unless required by applicable law or agreed to in writing, software
    12  distributed under the License is distributed on an "AS IS" BASIS,
    13  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  See the License for the specific language governing permissions and
    15  limitations under the License.
    16  */
    17  
    18  package nativepolicy
    19  
    20  import (
    21  	"context"
    22  	"fmt"
    23  
    24  	v1 "k8s.io/api/core/v1"
    25  	pluginapi "k8s.io/kubelet/pkg/apis/resourceplugin/v1alpha1"
    26  	"k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask"
    27  
    28  	nativepolicyutil "github.com/kubewharf/katalyst-core/pkg/agent/qrm-plugins/cpu/nativepolicy/util"
    29  	cpuutil "github.com/kubewharf/katalyst-core/pkg/agent/qrm-plugins/cpu/util"
    30  	"github.com/kubewharf/katalyst-core/pkg/agent/qrm-plugins/util"
    31  	"github.com/kubewharf/katalyst-core/pkg/util/general"
    32  	"github.com/kubewharf/katalyst-core/pkg/util/machine"
    33  )
    34  
    35  func (p *NativePolicy) dedicatedCoresHintHandler(ctx context.Context,
    36  	req *pluginapi.ResourceRequest,
    37  ) (*pluginapi.ResourceHintsResponse, error) {
    38  	if req == nil {
    39  		return nil, fmt.Errorf("HintHandler got nil req")
    40  	}
    41  
    42  	reqInt, _, err := util.GetQuantityFromResourceReq(req)
    43  	if err != nil {
    44  		return nil, fmt.Errorf("getReqQuantityFromResourceReq failed with error: %v", err)
    45  	}
    46  
    47  	machineState := p.state.GetMachineState()
    48  	var hints map[string]*pluginapi.ListOfTopologyHints
    49  
    50  	allocationInfo := p.state.GetAllocationInfo(req.PodUid, req.ContainerName)
    51  	if allocationInfo != nil {
    52  		hints = cpuutil.RegenerateHints(allocationInfo, reqInt)
    53  
    54  		// regenerateHints failed. need to clear container record and re-calculate.
    55  		if hints == nil {
    56  			podEntries := p.state.GetPodEntries()
    57  			delete(podEntries[req.PodUid], req.ContainerName)
    58  			if len(podEntries[req.PodUid]) == 0 {
    59  				delete(podEntries, req.PodUid)
    60  			}
    61  
    62  			var err error
    63  			machineState, err = nativepolicyutil.GenerateMachineStateFromPodEntries(p.machineInfo.CPUTopology, podEntries)
    64  			if err != nil {
    65  				general.Errorf("pod: %s/%s, container: %s GenerateMachineStateFromPodEntries failed with error: %v",
    66  					req.PodNamespace, req.PodName, req.ContainerName, err)
    67  				return nil, fmt.Errorf("GenerateMachineStateFromPodEntries failed with error: %v", err)
    68  			}
    69  		}
    70  	}
    71  
    72  	// otherwise, calculate hint for container without allocated memory
    73  	if hints == nil {
    74  		// Get a list of available CPUs.
    75  		available := machineState.GetAvailableCPUSet(p.reservedCPUs)
    76  
    77  		// Get a list of reusable CPUs (e.g. CPUs reused from initContainers).
    78  		// It should be an empty CPUSet for a newly created pod.
    79  		reusable := p.cpusToReuse[req.PodUid]
    80  
    81  		// calculate hint for container without allocated cpus
    82  		hints = p.generateCPUTopologyHints(available, reusable, reqInt)
    83  	}
    84  
    85  	general.InfoS("TopologyHints generated", "pod", fmt.Sprintf("%s/%s", req.PodNamespace, req.PodName), "containerName", req.ContainerName, "cpuHints", hints)
    86  
    87  	return util.PackResourceHintsResponse(req, string(v1.ResourceCPU), hints)
    88  }
    89  
    90  func (p *NativePolicy) sharedPoolHintHandler(_ context.Context,
    91  	req *pluginapi.ResourceRequest,
    92  ) (*pluginapi.ResourceHintsResponse, error) {
    93  	return util.PackResourceHintsResponse(req, string(v1.ResourceCPU),
    94  		map[string]*pluginapi.ListOfTopologyHints{
    95  			string(v1.ResourceCPU): nil, // indicates that there is no numa preference
    96  		})
    97  }
    98  
    99  // generateCPUtopologyHints generates a set of TopologyHints given the set of
   100  // available CPUs and the number of CPUs being requested.
   101  //
   102  // It follows the convention of marking all hints that have the same number of
   103  // bits set as the narrowest matching NUMANodeAffinity with 'Preferred: true', and
   104  // marking all others with 'Preferred: false'.
   105  func (p *NativePolicy) generateCPUTopologyHints(availableCPUs machine.CPUSet, reusableCPUs machine.CPUSet, request int) map[string]*pluginapi.ListOfTopologyHints {
   106  	// Initialize minAffinitySize to include all NUMA Nodes.
   107  	minAffinitySize := p.machineInfo.CPUDetails.NUMANodes().Size()
   108  
   109  	hints := map[string]*pluginapi.ListOfTopologyHints{
   110  		string(v1.ResourceCPU): {
   111  			Hints: []*pluginapi.TopologyHint{},
   112  		},
   113  	}
   114  
   115  	// Iterate through all combinations of numa nodes bitmask and build hints from them.
   116  	bitmask.IterateBitMasks(p.machineInfo.CPUDetails.NUMANodes().ToSliceInt(), func(mask bitmask.BitMask) {
   117  		// First, update minAffinitySize for the current request size.
   118  		cpusInMask := p.machineInfo.CPUDetails.CPUsInNUMANodes(mask.GetBits()...).Size()
   119  		if cpusInMask >= request && mask.Count() < minAffinitySize {
   120  			minAffinitySize = mask.Count()
   121  		}
   122  
   123  		// Then check to see if we have enough CPUs available on the current
   124  		// numa node bitmask to satisfy the CPU request.
   125  		numMatching := 0
   126  		for _, c := range reusableCPUs.ToSliceInt() {
   127  			// Disregard this mask if its NUMANode isn't part of it.
   128  			if !mask.IsSet(p.machineInfo.CPUDetails[c].NUMANodeID) {
   129  				return
   130  			}
   131  			numMatching++
   132  		}
   133  
   134  		// Finally, check to see if enough available CPUs remain on the current
   135  		// NUMA node combination to satisfy the CPU request.
   136  		for _, c := range availableCPUs.ToSliceInt() {
   137  			if mask.IsSet(p.machineInfo.CPUDetails[c].NUMANodeID) {
   138  				numMatching++
   139  			}
   140  		}
   141  
   142  		// If they don't, then move onto the next combination.
   143  		if numMatching < request {
   144  			return
   145  		}
   146  
   147  		// Otherwise, create a new hint from the numa node bitmask and add it to the
   148  		// list of hints.  We set all hint preferences to 'false' on the first
   149  		// pass through.
   150  		hints[string(v1.ResourceCPU)].Hints = append(hints[string(v1.ResourceCPU)].Hints, &pluginapi.TopologyHint{
   151  			Nodes:     machine.MaskToUInt64Array(mask),
   152  			Preferred: mask.Count() == minAffinitySize,
   153  		})
   154  	})
   155  
   156  	return hints
   157  }