volcano.sh/volcano@v1.9.0/pkg/scheduler/util/scheduler_helper.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes 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 util
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"math"
    23  	"math/rand"
    24  	"sort"
    25  	"sync"
    26  
    27  	v1 "k8s.io/api/core/v1"
    28  	"k8s.io/apimachinery/pkg/api/resource"
    29  	"k8s.io/client-go/util/workqueue"
    30  	"k8s.io/klog/v2"
    31  	k8sframework "k8s.io/kubernetes/pkg/scheduler/framework"
    32  
    33  	"volcano.sh/volcano/cmd/scheduler/app/options"
    34  	"volcano.sh/volcano/pkg/scheduler/api"
    35  )
    36  
    37  const baselinePercentageOfNodesToFind = 50
    38  
    39  var lastProcessedNodeIndex int
    40  
    41  // CalculateNumOfFeasibleNodesToFind returns the number of feasible nodes that once found,
    42  // the scheduler stops its search for more feasible nodes.
    43  func CalculateNumOfFeasibleNodesToFind(numAllNodes int32) (numNodes int32) {
    44  	opts := options.ServerOpts
    45  	if numAllNodes <= opts.MinNodesToFind || opts.PercentageOfNodesToFind >= 100 {
    46  		return numAllNodes
    47  	}
    48  
    49  	adaptivePercentage := opts.PercentageOfNodesToFind
    50  	if adaptivePercentage <= 0 {
    51  		adaptivePercentage = baselinePercentageOfNodesToFind - numAllNodes/125
    52  		if adaptivePercentage < opts.MinPercentageOfNodesToFind {
    53  			adaptivePercentage = opts.MinPercentageOfNodesToFind
    54  		}
    55  	}
    56  
    57  	numNodes = numAllNodes * adaptivePercentage / 100
    58  	if numNodes < opts.MinNodesToFind {
    59  		numNodes = opts.MinNodesToFind
    60  	}
    61  	return numNodes
    62  }
    63  
    64  // PrioritizeNodes returns a map whose key is node's score and value are corresponding nodes
    65  func PrioritizeNodes(task *api.TaskInfo, nodes []*api.NodeInfo, batchFn api.BatchNodeOrderFn, mapFn api.NodeOrderMapFn, reduceFn api.NodeOrderReduceFn) map[float64][]*api.NodeInfo {
    66  	pluginNodeScoreMap := map[string]k8sframework.NodeScoreList{}
    67  	nodeOrderScoreMap := map[string]float64{}
    68  	nodeScores := map[float64][]*api.NodeInfo{}
    69  	var workerLock sync.Mutex
    70  	scoreNode := func(index int) {
    71  		node := nodes[index]
    72  		mapScores, orderScore, err := mapFn(task, node)
    73  		if err != nil {
    74  			klog.Errorf("Error in Calculating Priority for the node:%v", err)
    75  			return
    76  		}
    77  
    78  		workerLock.Lock()
    79  		for plugin, score := range mapScores {
    80  			nodeScoreList, ok := pluginNodeScoreMap[plugin]
    81  			if !ok {
    82  				nodeScoreList = k8sframework.NodeScoreList{}
    83  			}
    84  			hp := k8sframework.NodeScore{}
    85  			hp.Name = node.Name
    86  			hp.Score = int64(math.Floor(score))
    87  			pluginNodeScoreMap[plugin] = append(nodeScoreList, hp)
    88  		}
    89  		nodeOrderScoreMap[node.Name] = orderScore
    90  		workerLock.Unlock()
    91  	}
    92  	workqueue.ParallelizeUntil(context.TODO(), 16, len(nodes), scoreNode)
    93  	reduceScores, err := reduceFn(task, pluginNodeScoreMap)
    94  	if err != nil {
    95  		klog.Errorf("Error in Calculating Priority for the node:%v", err)
    96  		return nodeScores
    97  	}
    98  
    99  	batchNodeScore, err := batchFn(task, nodes)
   100  	if err != nil {
   101  		klog.Errorf("Error in Calculating batch Priority for the node, err %v", err)
   102  		return nodeScores
   103  	}
   104  
   105  	nodeScoreMap := map[string]float64{}
   106  	for _, node := range nodes {
   107  		// If no plugin is applied to this node, the default is 0.0
   108  		score := 0.0
   109  		if reduceScore, ok := reduceScores[node.Name]; ok {
   110  			score += reduceScore
   111  		}
   112  		if orderScore, ok := nodeOrderScoreMap[node.Name]; ok {
   113  			score += orderScore
   114  		}
   115  		if batchScore, ok := batchNodeScore[node.Name]; ok {
   116  			score += batchScore
   117  		}
   118  		nodeScores[score] = append(nodeScores[score], node)
   119  
   120  		if klog.V(5).Enabled() {
   121  			nodeScoreMap[node.Name] = score
   122  		}
   123  	}
   124  
   125  	klog.V(5).Infof("Prioritize nodeScoreMap for task<%s/%s> is: %v", task.Namespace, task.Name, nodeScoreMap)
   126  	return nodeScores
   127  }
   128  
   129  // SortNodes returns nodes by order of score
   130  func SortNodes(nodeScores map[float64][]*api.NodeInfo) []*api.NodeInfo {
   131  	var nodesInorder []*api.NodeInfo
   132  	var keys []float64
   133  	for key := range nodeScores {
   134  		keys = append(keys, key)
   135  	}
   136  	sort.Sort(sort.Reverse(sort.Float64Slice(keys)))
   137  	for _, key := range keys {
   138  		nodes := nodeScores[key]
   139  		nodesInorder = append(nodesInorder, nodes...)
   140  	}
   141  	return nodesInorder
   142  }
   143  
   144  // SelectBestNode returns best node whose score is highest, pick one randomly if there are many nodes with same score.
   145  func SelectBestNode(nodeScores map[float64][]*api.NodeInfo) *api.NodeInfo {
   146  	var bestNodes []*api.NodeInfo
   147  	maxScore := -1.0
   148  	for score, nodes := range nodeScores {
   149  		if score > maxScore {
   150  			maxScore = score
   151  			bestNodes = nodes
   152  		}
   153  	}
   154  
   155  	if len(bestNodes) == 0 {
   156  		return nil
   157  	}
   158  
   159  	return bestNodes[rand.Intn(len(bestNodes))]
   160  }
   161  
   162  // GetNodeList returns values of the map 'nodes'
   163  func GetNodeList(nodes map[string]*api.NodeInfo, nodeList []string) []*api.NodeInfo {
   164  	result := make([]*api.NodeInfo, 0, len(nodeList))
   165  	for _, nodename := range nodeList {
   166  		if ni, ok := nodes[nodename]; ok {
   167  			result = append(result, ni)
   168  		}
   169  	}
   170  	return result
   171  }
   172  
   173  // ValidateVictims returns an error if the resources of the victims can't satisfy the preemptor
   174  func ValidateVictims(preemptor *api.TaskInfo, node *api.NodeInfo, victims []*api.TaskInfo) error {
   175  	// Victims should not be judged to be empty here.
   176  	// It is possible to complete the scheduling of the preemptor without evicting the task.
   177  	// In the first round, a large task (CPU: 8) is expelled, and a small task is scheduled (CPU: 2)
   178  	// When the following rounds of victims are empty, it is still allowed to schedule small tasks (CPU: 2)
   179  	futureIdle := node.FutureIdle()
   180  	for _, victim := range victims {
   181  		futureIdle.Add(victim.Resreq)
   182  	}
   183  	// Every resource of the preemptor needs to be less or equal than corresponding
   184  	// idle resource after preemption.
   185  	if !preemptor.InitResreq.LessEqual(futureIdle, api.Zero) {
   186  		return fmt.Errorf("not enough resources: requested <%v>, but future idle <%v>",
   187  			preemptor.InitResreq, futureIdle)
   188  	}
   189  	return nil
   190  }
   191  
   192  // GetMinInt return minimum int from vals
   193  func GetMinInt(vals ...int) int {
   194  	if len(vals) == 0 {
   195  		return 0
   196  	}
   197  
   198  	min := vals[0]
   199  	for _, val := range vals {
   200  		if val <= min {
   201  			min = val
   202  		}
   203  	}
   204  	return min
   205  }
   206  
   207  // ConvertRes2ResList convert resource type from api.Resource in scheduler to v1.ResourceList in yaml
   208  func ConvertRes2ResList(res *api.Resource) v1.ResourceList {
   209  	var rl = v1.ResourceList{}
   210  	rl[v1.ResourceCPU] = *resource.NewMilliQuantity(int64(res.MilliCPU), resource.DecimalSI)
   211  	rl[v1.ResourceMemory] = *resource.NewQuantity(int64(res.Memory), resource.BinarySI)
   212  	for resourceName, f := range res.ScalarResources {
   213  		if resourceName == v1.ResourcePods {
   214  			rl[resourceName] = *resource.NewQuantity(int64(f), resource.DecimalSI)
   215  			continue
   216  		}
   217  		rl[resourceName] = *resource.NewMilliQuantity(int64(f), resource.DecimalSI)
   218  	}
   219  	return rl
   220  }