google.golang.org/grpc@v1.62.1/xds/internal/balancer/clusterimpl/picker.go (about)

     1  /*
     2   *
     3   * Copyright 2020 gRPC authors.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   */
    18  
    19  package clusterimpl
    20  
    21  import (
    22  	"google.golang.org/grpc/balancer"
    23  	"google.golang.org/grpc/codes"
    24  	"google.golang.org/grpc/connectivity"
    25  	"google.golang.org/grpc/internal/wrr"
    26  	"google.golang.org/grpc/status"
    27  	"google.golang.org/grpc/xds/internal/xdsclient"
    28  	"google.golang.org/grpc/xds/internal/xdsclient/load"
    29  
    30  	v3orcapb "github.com/cncf/xds/go/xds/data/orca/v3"
    31  )
    32  
    33  // NewRandomWRR is used when calculating drops. It's exported so that tests can
    34  // override it.
    35  var NewRandomWRR = wrr.NewRandom
    36  
    37  const million = 1000000
    38  
    39  type dropper struct {
    40  	category string
    41  	w        wrr.WRR
    42  }
    43  
    44  // greatest common divisor (GCD) via Euclidean algorithm
    45  func gcd(a, b uint32) uint32 {
    46  	for b != 0 {
    47  		t := b
    48  		b = a % b
    49  		a = t
    50  	}
    51  	return a
    52  }
    53  
    54  func newDropper(c DropConfig) *dropper {
    55  	w := NewRandomWRR()
    56  	gcdv := gcd(c.RequestsPerMillion, million)
    57  	// Return true for RequestPerMillion, false for the rest.
    58  	w.Add(true, int64(c.RequestsPerMillion/gcdv))
    59  	w.Add(false, int64((million-c.RequestsPerMillion)/gcdv))
    60  
    61  	return &dropper{
    62  		category: c.Category,
    63  		w:        w,
    64  	}
    65  }
    66  
    67  func (d *dropper) drop() (ret bool) {
    68  	return d.w.Next().(bool)
    69  }
    70  
    71  const (
    72  	serverLoadCPUName    = "cpu_utilization"
    73  	serverLoadMemoryName = "mem_utilization"
    74  )
    75  
    76  // loadReporter wraps the methods from the loadStore that are used here.
    77  type loadReporter interface {
    78  	CallStarted(locality string)
    79  	CallFinished(locality string, err error)
    80  	CallServerLoad(locality, name string, val float64)
    81  	CallDropped(locality string)
    82  }
    83  
    84  // Picker implements RPC drop, circuit breaking drop and load reporting.
    85  type picker struct {
    86  	drops     []*dropper
    87  	s         balancer.State
    88  	loadStore loadReporter
    89  	counter   *xdsclient.ClusterRequestsCounter
    90  	countMax  uint32
    91  }
    92  
    93  func newPicker(s balancer.State, config *dropConfigs, loadStore load.PerClusterReporter) *picker {
    94  	return &picker{
    95  		drops:     config.drops,
    96  		s:         s,
    97  		loadStore: loadStore,
    98  		counter:   config.requestCounter,
    99  		countMax:  config.requestCountMax,
   100  	}
   101  }
   102  
   103  func (d *picker) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
   104  	// Don't drop unless the inner picker is READY. Similar to
   105  	// https://github.com/grpc/grpc-go/issues/2622.
   106  	if d.s.ConnectivityState == connectivity.Ready {
   107  		// Check if this RPC should be dropped by category.
   108  		for _, dp := range d.drops {
   109  			if dp.drop() {
   110  				if d.loadStore != nil {
   111  					d.loadStore.CallDropped(dp.category)
   112  				}
   113  				return balancer.PickResult{}, status.Errorf(codes.Unavailable, "RPC is dropped")
   114  			}
   115  		}
   116  	}
   117  
   118  	// Check if this RPC should be dropped by circuit breaking.
   119  	if d.counter != nil {
   120  		if err := d.counter.StartRequest(d.countMax); err != nil {
   121  			// Drops by circuit breaking are reported with empty category. They
   122  			// will be reported only in total drops, but not in per category.
   123  			if d.loadStore != nil {
   124  				d.loadStore.CallDropped("")
   125  			}
   126  			return balancer.PickResult{}, status.Errorf(codes.Unavailable, err.Error())
   127  		}
   128  	}
   129  
   130  	var lIDStr string
   131  	pr, err := d.s.Picker.Pick(info)
   132  	if scw, ok := pr.SubConn.(*scWrapper); ok {
   133  		// This OK check also covers the case err!=nil, because SubConn will be
   134  		// nil.
   135  		pr.SubConn = scw.SubConn
   136  		var e error
   137  		// If locality ID isn't found in the wrapper, an empty locality ID will
   138  		// be used.
   139  		lIDStr, e = scw.localityID().ToString()
   140  		if e != nil {
   141  			logger.Infof("failed to marshal LocalityID: %#v, loads won't be reported", scw.localityID())
   142  		}
   143  	}
   144  
   145  	if err != nil {
   146  		if d.counter != nil {
   147  			// Release one request count if this pick fails.
   148  			d.counter.EndRequest()
   149  		}
   150  		return pr, err
   151  	}
   152  
   153  	if d.loadStore != nil {
   154  		d.loadStore.CallStarted(lIDStr)
   155  		oldDone := pr.Done
   156  		pr.Done = func(info balancer.DoneInfo) {
   157  			if oldDone != nil {
   158  				oldDone(info)
   159  			}
   160  			d.loadStore.CallFinished(lIDStr, info.Err)
   161  
   162  			load, ok := info.ServerLoad.(*v3orcapb.OrcaLoadReport)
   163  			if !ok || load == nil {
   164  				return
   165  			}
   166  			d.loadStore.CallServerLoad(lIDStr, serverLoadCPUName, load.CpuUtilization)
   167  			d.loadStore.CallServerLoad(lIDStr, serverLoadMemoryName, load.MemUtilization)
   168  			for n, c := range load.RequestCost {
   169  				d.loadStore.CallServerLoad(lIDStr, n, c)
   170  			}
   171  			for n, c := range load.Utilization {
   172  				d.loadStore.CallServerLoad(lIDStr, n, c)
   173  			}
   174  		}
   175  	}
   176  
   177  	if d.counter != nil {
   178  		// Update Done() so that when the RPC finishes, the request count will
   179  		// be released.
   180  		oldDone := pr.Done
   181  		pr.Done = func(doneInfo balancer.DoneInfo) {
   182  			d.counter.EndRequest()
   183  			if oldDone != nil {
   184  				oldDone(doneInfo)
   185  			}
   186  		}
   187  	}
   188  
   189  	return pr, err
   190  }