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  }