volcano.sh/volcano@v1.9.0/pkg/scheduler/plugins/binpack/binpack.go (about)

     1  /*
     2  Copyright 2019 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 binpack
    18  
    19  import (
    20  	"fmt"
    21  	"strings"
    22  
    23  	v1 "k8s.io/api/core/v1"
    24  	"k8s.io/klog/v2"
    25  	k8sFramework "k8s.io/kubernetes/pkg/scheduler/framework"
    26  
    27  	"volcano.sh/volcano/pkg/scheduler/api"
    28  	"volcano.sh/volcano/pkg/scheduler/framework"
    29  )
    30  
    31  const (
    32  	// PluginName indicates name of volcano scheduler plugin.
    33  	PluginName = "binpack"
    34  )
    35  
    36  const (
    37  	// BinpackWeight is the key for providing Binpack Priority Weight in YAML
    38  	BinpackWeight = "binpack.weight"
    39  	// BinpackCPU is the key for weight of cpu
    40  	BinpackCPU = "binpack.cpu"
    41  	// BinpackMemory is the key for weight of memory
    42  	BinpackMemory = "binpack.memory"
    43  
    44  	// BinpackResources is the key for additional resource key name
    45  	BinpackResources = "binpack.resources"
    46  	// BinpackResourcesPrefix is the key prefix for additional resource key name
    47  	BinpackResourcesPrefix = BinpackResources + "."
    48  
    49  	resourceFmt = "%s[%d]"
    50  )
    51  
    52  type priorityWeight struct {
    53  	BinPackingWeight    int
    54  	BinPackingCPU       int
    55  	BinPackingMemory    int
    56  	BinPackingResources map[v1.ResourceName]int
    57  }
    58  
    59  func (w *priorityWeight) String() string {
    60  	length := 3
    61  	if extendLength := len(w.BinPackingResources); extendLength == 0 {
    62  		length++
    63  	} else {
    64  		length += extendLength
    65  	}
    66  	msg := make([]string, 0, length)
    67  	msg = append(msg,
    68  		fmt.Sprintf(resourceFmt, BinpackWeight, w.BinPackingWeight),
    69  		fmt.Sprintf(resourceFmt, BinpackCPU, w.BinPackingCPU),
    70  		fmt.Sprintf(resourceFmt, BinpackMemory, w.BinPackingMemory),
    71  	)
    72  
    73  	if len(w.BinPackingResources) == 0 {
    74  		msg = append(msg, "no extend resources.")
    75  	} else {
    76  		for name, weight := range w.BinPackingResources {
    77  			msg = append(msg, fmt.Sprintf(resourceFmt, name, weight))
    78  		}
    79  	}
    80  	return strings.Join(msg, ", ")
    81  }
    82  
    83  type binpackPlugin struct {
    84  	// Arguments given for the plugin
    85  	weight priorityWeight
    86  }
    87  
    88  // New function returns prioritizePlugin object
    89  func New(aruguments framework.Arguments) framework.Plugin {
    90  	weight := calculateWeight(aruguments)
    91  	return &binpackPlugin{weight: weight}
    92  }
    93  
    94  func calculateWeight(args framework.Arguments) priorityWeight {
    95  	/*
    96  	   User Should give priorityWeight in this format(binpack.weight, binpack.cpu, binpack.memory).
    97  	   Support change the weight about cpu, memory and additional resource by arguments.
    98  
    99  	   actions: "enqueue, reclaim, allocate, backfill, preempt"
   100  	   tiers:
   101  	   - plugins:
   102  	     - name: binpack
   103  	       arguments:
   104  	         binpack.weight: 10
   105  	         binpack.cpu: 5
   106  	         binpack.memory: 1
   107  	         binpack.resources: nvidia.com/gpu, example.com/foo
   108  	         binpack.resources.nvidia.com/gpu: 2
   109  	         binpack.resources.example.com/foo: 3
   110  	*/
   111  	// Values are initialized to 1.
   112  	weight := priorityWeight{
   113  		BinPackingWeight:    1,
   114  		BinPackingCPU:       1,
   115  		BinPackingMemory:    1,
   116  		BinPackingResources: make(map[v1.ResourceName]int),
   117  	}
   118  
   119  	// Checks whether binpack.weight is provided or not, if given, modifies the value in weight struct.
   120  	args.GetInt(&weight.BinPackingWeight, BinpackWeight)
   121  	// Checks whether binpack.cpu is provided or not, if given, modifies the value in weight struct.
   122  	args.GetInt(&weight.BinPackingCPU, BinpackCPU)
   123  	if weight.BinPackingCPU < 0 {
   124  		weight.BinPackingCPU = 1
   125  	}
   126  	// Checks whether binpack.memory is provided or not, if given, modifies the value in weight struct.
   127  	args.GetInt(&weight.BinPackingMemory, BinpackMemory)
   128  	if weight.BinPackingMemory < 0 {
   129  		weight.BinPackingMemory = 1
   130  	}
   131  
   132  	resourcesStr, ok := args[BinpackResources].(string)
   133  	if !ok {
   134  		resourcesStr = ""
   135  	}
   136  
   137  	resources := strings.Split(resourcesStr, ",")
   138  	for _, resource := range resources {
   139  		resource = strings.TrimSpace(resource)
   140  		if resource == "" {
   141  			continue
   142  		}
   143  
   144  		// binpack.resources.[ResourceName]
   145  		resourceKey := BinpackResourcesPrefix + resource
   146  		resourceWeight := 1
   147  		args.GetInt(&resourceWeight, resourceKey)
   148  		if resourceWeight < 0 {
   149  			resourceWeight = 1
   150  		}
   151  		weight.BinPackingResources[v1.ResourceName(resource)] = resourceWeight
   152  	}
   153  
   154  	weight.BinPackingResources[v1.ResourceCPU] = weight.BinPackingCPU
   155  	weight.BinPackingResources[v1.ResourceMemory] = weight.BinPackingMemory
   156  
   157  	return weight
   158  }
   159  
   160  func (bp *binpackPlugin) Name() string {
   161  	return PluginName
   162  }
   163  
   164  func (bp *binpackPlugin) OnSessionOpen(ssn *framework.Session) {
   165  	klog.V(5).Infof("Enter binpack plugin ...")
   166  	defer func() {
   167  		klog.V(5).Infof("Leaving binpack plugin. %s ...", bp.weight.String())
   168  	}()
   169  	if klog.V(4).Enabled() {
   170  		notFoundResource := []string{}
   171  		for resource := range bp.weight.BinPackingResources {
   172  			found := false
   173  			for _, nodeInfo := range ssn.Nodes {
   174  				if nodeInfo.Allocatable.Get(resource) > 0 {
   175  					found = true
   176  					break
   177  				}
   178  			}
   179  			if !found {
   180  				notFoundResource = append(notFoundResource, string(resource))
   181  			}
   182  		}
   183  		klog.V(4).Infof("resources [%s] record in weight but not found on any node", strings.Join(notFoundResource, ", "))
   184  	}
   185  
   186  	nodeOrderFn := func(task *api.TaskInfo, node *api.NodeInfo) (float64, error) {
   187  		binPackingScore := BinPackingScore(task, node, bp.weight)
   188  
   189  		klog.V(4).Infof("Binpack score for Task %s/%s on node %s is: %v", task.Namespace, task.Name, node.Name, binPackingScore)
   190  		return binPackingScore, nil
   191  	}
   192  	if bp.weight.BinPackingWeight != 0 {
   193  		ssn.AddNodeOrderFn(bp.Name(), nodeOrderFn)
   194  	} else {
   195  		klog.Infof("binpack weight is zero, skip node order function")
   196  	}
   197  }
   198  
   199  func (bp *binpackPlugin) OnSessionClose(ssn *framework.Session) {
   200  }
   201  
   202  // BinPackingScore use the best fit polices during scheduling.
   203  // Goals:
   204  // - Schedule Jobs using BestFit Policy using Resource Bin Packing Priority Function
   205  // - Reduce Fragmentation of scarce resources on the Cluster
   206  func BinPackingScore(task *api.TaskInfo, node *api.NodeInfo, weight priorityWeight) float64 {
   207  	score := 0.0
   208  	weightSum := 0
   209  	requested := task.Resreq
   210  	allocatable := node.Allocatable
   211  	used := node.Used
   212  
   213  	for _, resource := range requested.ResourceNames() {
   214  		request := requested.Get(resource)
   215  		if request == 0 {
   216  			continue
   217  		}
   218  		allocate := allocatable.Get(resource)
   219  		nodeUsed := used.Get(resource)
   220  
   221  		resourceWeight, found := weight.BinPackingResources[resource]
   222  		if !found {
   223  			continue
   224  		}
   225  
   226  		resourceScore, err := ResourceBinPackingScore(request, allocate, nodeUsed, resourceWeight)
   227  		if err != nil {
   228  			klog.V(4).Infof("task %s/%s cannot binpack node %s: resource: %s is %s, need %f, used %f, allocatable %f",
   229  				task.Namespace, task.Name, node.Name, resource, err.Error(), request, nodeUsed, allocate)
   230  			return 0
   231  		}
   232  		klog.V(5).Infof("task %s/%s on node %s resource %s, need %f, used %f, allocatable %f, weight %d, score %f",
   233  			task.Namespace, task.Name, node.Name, resource, request, nodeUsed, allocate, resourceWeight, resourceScore)
   234  
   235  		score += resourceScore
   236  		weightSum += resourceWeight
   237  	}
   238  
   239  	// mapping the result from [0, weightSum] to [0, 10(MaxPriority)]
   240  	if weightSum > 0 {
   241  		score /= float64(weightSum)
   242  	}
   243  	score *= float64(k8sFramework.MaxNodeScore * int64(weight.BinPackingWeight))
   244  
   245  	return score
   246  }
   247  
   248  // ResourceBinPackingScore calculate the binpack score for resource with provided info
   249  func ResourceBinPackingScore(requested, capacity, used float64, weight int) (float64, error) {
   250  	if capacity == 0 || weight == 0 {
   251  		return 0, nil
   252  	}
   253  
   254  	usedFinally := requested + used
   255  	if usedFinally > capacity {
   256  		return 0, fmt.Errorf("not enough")
   257  	}
   258  
   259  	score := usedFinally * float64(weight) / capacity
   260  	return score, nil
   261  }