github.com/kubewharf/katalyst-core@v0.5.3/pkg/scheduler/plugins/qosawarenoderesources/balanced_allocation.go (about) 1 /* 2 Copyright 2022 The Katalyst 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 qosawarenoderesources 18 19 import ( 20 "context" 21 "fmt" 22 "math" 23 24 v1 "k8s.io/api/core/v1" 25 "k8s.io/apimachinery/pkg/runtime" 26 kubeschedulerconfig "k8s.io/kubernetes/pkg/scheduler/apis/config" 27 "k8s.io/kubernetes/pkg/scheduler/framework" 28 "k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature" 29 "k8s.io/kubernetes/pkg/scheduler/framework/plugins/noderesources" 30 31 "github.com/kubewharf/katalyst-api/pkg/apis/scheduling/config" 32 "github.com/kubewharf/katalyst-api/pkg/apis/scheduling/config/validation" 33 "github.com/kubewharf/katalyst-core/pkg/scheduler/cache" 34 "github.com/kubewharf/katalyst-core/pkg/scheduler/eventhandlers" 35 "github.com/kubewharf/katalyst-core/pkg/scheduler/util" 36 ) 37 38 // BalancedAllocation is a score plugin that calculates the difference between the cpu and memory fraction 39 // of capacity, and prioritizes the host based on how close the two metrics are to each other. 40 type BalancedAllocation struct { 41 handle framework.Handle 42 resourceAllocationScorer 43 nativeBalancedAllocation *noderesources.BalancedAllocation 44 } 45 46 var _ = framework.ScorePlugin(&BalancedAllocation{}) 47 48 // BalancedAllocationName is the name of the plugin used in the plugin registry and configurations. 49 const BalancedAllocationName = "QoSAwareNodeResourcesBalancedAllocation" 50 51 // Name returns name of the plugin. It is used in logs, etc. 52 func (ba *BalancedAllocation) Name() string { 53 return BalancedAllocationName 54 } 55 56 // Score invoked at the score extension point. 57 func (ba *BalancedAllocation) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) { 58 if util.IsReclaimedPod(pod) { 59 extendedNodeInfo, err := cache.GetCache().GetNodeInfo(nodeName) 60 if err != nil { 61 return 0, framework.AsStatus(fmt.Errorf("getting node %q error: %w", nodeName, err)) 62 } 63 64 // ba.score favors nodes with balanced resource usage rate. 65 // It calculates the standard deviation for those resources and prioritizes the node based on how close the usage of those resources is to each other. 66 // Detail: score = (1 - std) * MaxNodeScore, where std is calculated by the root square of Σ((fraction(i)-mean)^2)/len(resources) 67 // The algorithm is partly inspired by: 68 // "Wei Huang et al. An Energy Efficient Virtual Machine Placement Algorithm with Balanced Resource Utilization" 69 return ba.score(pod, extendedNodeInfo, nodeName) 70 } 71 72 return ba.nativeBalancedAllocation.Score(ctx, state, pod, nodeName) 73 } 74 75 // ScoreExtensions of the Score plugin. 76 func (ba *BalancedAllocation) ScoreExtensions() framework.ScoreExtensions { 77 return nil 78 } 79 80 // NewBalancedAllocation initializes a new plugin and returns it. 81 func NewBalancedAllocation(baArgs runtime.Object, h framework.Handle) (framework.Plugin, error) { 82 args, ok := baArgs.(*config.QoSAwareNodeResourcesBalancedAllocationArgs) 83 if !ok { 84 return nil, fmt.Errorf("want args to be of type NodeQoSResourcesBalancedAllocationArgs, got %T", baArgs) 85 } 86 87 if err := validation.ValidateQoSAwareNodeResourcesBalancedAllocationArgs(nil, args); err != nil { 88 return nil, err 89 } 90 91 resToWeightMap := make(resourceToWeightMap) 92 for _, resource := range args.ReclaimedResources { 93 resToWeightMap[v1.ResourceName(resource.Name)] = resource.Weight 94 } 95 96 nativeBalancedAllocation, err := newNativeBalancedAllocation(args, h) 97 if err != nil { 98 return nil, err 99 } 100 101 eventhandlers.RegisterCommonPodHandler() 102 eventhandlers.RegisterCommonCNRHandler() 103 104 return &BalancedAllocation{ 105 handle: h, 106 resourceAllocationScorer: resourceAllocationScorer{ 107 Name: BalancedAllocationName, 108 scorer: balancedResourceScorer, 109 useRequested: true, 110 resourceToWeightMap: resToWeightMap, 111 }, 112 nativeBalancedAllocation: nativeBalancedAllocation, 113 }, nil 114 } 115 116 func newNativeBalancedAllocation(args *config.QoSAwareNodeResourcesBalancedAllocationArgs, h framework.Handle) (*noderesources.BalancedAllocation, error) { 117 nativeBalancedAllocationPlugin, err := noderesources.NewBalancedAllocation( 118 &kubeschedulerconfig.NodeResourcesBalancedAllocationArgs{ 119 Resources: args.Resources, 120 }, h, feature.Features{}, 121 ) 122 if err != nil { 123 return nil, err 124 } 125 126 nativeBalancedAllocation, ok := nativeBalancedAllocationPlugin.(*noderesources.BalancedAllocation) 127 if !ok { 128 return nil, fmt.Errorf("assert nativeBalancedAllocation type error, got %T", nativeBalancedAllocationPlugin) 129 } 130 131 return nativeBalancedAllocation, nil 132 } 133 134 func balancedResourceScorer(requested, allocatable resourceToValueMap) int64 { 135 var resourceToFractions []float64 136 var totalFraction float64 137 for name, value := range requested { 138 fraction := float64(value) / float64(allocatable[name]) 139 if fraction > 1 { 140 fraction = 1 141 } 142 totalFraction += fraction 143 resourceToFractions = append(resourceToFractions, fraction) 144 } 145 146 std := 0.0 147 148 // For most cases, resources are limited to cpu and memory, the std could be simplified to std := (fraction1-fraction2)/2 149 // len(fractions) > 2: calculate std based on the well-known formula - root square of Σ((fraction(i)-mean)^2)/len(fractions) 150 // Otherwise, set the std to zero is enough. 151 if len(resourceToFractions) == 2 { 152 std = math.Abs((resourceToFractions[0] - resourceToFractions[1]) / 2) 153 } else if len(resourceToFractions) > 2 { 154 mean := totalFraction / float64(len(resourceToFractions)) 155 var sum float64 156 for _, fraction := range resourceToFractions { 157 sum = sum + (fraction-mean)*(fraction-mean) 158 } 159 std = math.Sqrt(sum / float64(len(resourceToFractions))) 160 } 161 162 // STD (standard deviation) is always a positive value. 1-deviation lets the score to be higher for node which has least deviation and 163 // multiplying it with `MaxNodeScore` provides the scaling factor needed. 164 return int64((1 - std) * float64(framework.MaxNodeScore)) 165 }