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 }