google.golang.org/grpc@v1.74.2/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  	"context"
    23  
    24  	v3orcapb "github.com/cncf/xds/go/xds/data/orca/v3"
    25  	"google.golang.org/grpc/balancer"
    26  	"google.golang.org/grpc/codes"
    27  	"google.golang.org/grpc/connectivity"
    28  	"google.golang.org/grpc/internal/stats"
    29  	"google.golang.org/grpc/internal/wrr"
    30  	"google.golang.org/grpc/status"
    31  	"google.golang.org/grpc/xds/internal"
    32  	"google.golang.org/grpc/xds/internal/clients"
    33  	"google.golang.org/grpc/xds/internal/xdsclient"
    34  )
    35  
    36  // NewRandomWRR is used when calculating drops. It's exported so that tests can
    37  // override it.
    38  var NewRandomWRR = wrr.NewRandom
    39  
    40  const million = 1000000
    41  
    42  type dropper struct {
    43  	category string
    44  	w        wrr.WRR
    45  }
    46  
    47  // greatest common divisor (GCD) via Euclidean algorithm
    48  func gcd(a, b uint32) uint32 {
    49  	for b != 0 {
    50  		t := b
    51  		b = a % b
    52  		a = t
    53  	}
    54  	return a
    55  }
    56  
    57  func newDropper(c DropConfig) *dropper {
    58  	w := NewRandomWRR()
    59  	gcdv := gcd(c.RequestsPerMillion, million)
    60  	// Return true for RequestPerMillion, false for the rest.
    61  	w.Add(true, int64(c.RequestsPerMillion/gcdv))
    62  	w.Add(false, int64((million-c.RequestsPerMillion)/gcdv))
    63  
    64  	return &dropper{
    65  		category: c.Category,
    66  		w:        w,
    67  	}
    68  }
    69  
    70  func (d *dropper) drop() (ret bool) {
    71  	return d.w.Next().(bool)
    72  }
    73  
    74  // loadReporter wraps the methods from the loadStore that are used here.
    75  type loadReporter interface {
    76  	CallStarted(locality clients.Locality)
    77  	CallFinished(locality clients.Locality, err error)
    78  	CallServerLoad(locality clients.Locality, name string, val float64)
    79  	CallDropped(category string)
    80  }
    81  
    82  // Picker implements RPC drop, circuit breaking drop and load reporting.
    83  type picker struct {
    84  	drops           []*dropper
    85  	s               balancer.State
    86  	loadStore       loadReporter
    87  	counter         *xdsclient.ClusterRequestsCounter
    88  	countMax        uint32
    89  	telemetryLabels map[string]string
    90  }
    91  
    92  func telemetryLabels(ctx context.Context) map[string]string {
    93  	if ctx == nil {
    94  		return nil
    95  	}
    96  	labels := stats.GetLabels(ctx)
    97  	if labels == nil {
    98  		return nil
    99  	}
   100  	return labels.TelemetryLabels
   101  }
   102  
   103  func (d *picker) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
   104  	// Unconditionally set labels if present, even dropped or queued RPC's can
   105  	// use these labels.
   106  	if labels := telemetryLabels(info.Ctx); labels != nil {
   107  		for key, value := range d.telemetryLabels {
   108  			labels[key] = value
   109  		}
   110  	}
   111  
   112  	// Don't drop unless the inner picker is READY. Similar to
   113  	// https://github.com/grpc/grpc-go/issues/2622.
   114  	if d.s.ConnectivityState == connectivity.Ready {
   115  		// Check if this RPC should be dropped by category.
   116  		for _, dp := range d.drops {
   117  			if dp.drop() {
   118  				if d.loadStore != nil {
   119  					d.loadStore.CallDropped(dp.category)
   120  				}
   121  				return balancer.PickResult{}, status.Errorf(codes.Unavailable, "RPC is dropped")
   122  			}
   123  		}
   124  	}
   125  
   126  	// Check if this RPC should be dropped by circuit breaking.
   127  	if d.counter != nil {
   128  		if err := d.counter.StartRequest(d.countMax); err != nil {
   129  			// Drops by circuit breaking are reported with empty category. They
   130  			// will be reported only in total drops, but not in per category.
   131  			if d.loadStore != nil {
   132  				d.loadStore.CallDropped("")
   133  			}
   134  			return balancer.PickResult{}, status.Error(codes.Unavailable, err.Error())
   135  		}
   136  	}
   137  
   138  	var lID clients.Locality
   139  	pr, err := d.s.Picker.Pick(info)
   140  	if scw, ok := pr.SubConn.(*scWrapper); ok {
   141  		// This OK check also covers the case err!=nil, because SubConn will be
   142  		// nil.
   143  		pr.SubConn = scw.SubConn
   144  		// If locality ID isn't found in the wrapper, an empty locality ID will
   145  		// be used.
   146  		lID = scw.localityID()
   147  	}
   148  
   149  	if err != nil {
   150  		if d.counter != nil {
   151  			// Release one request count if this pick fails.
   152  			d.counter.EndRequest()
   153  		}
   154  		return pr, err
   155  	}
   156  
   157  	if labels := telemetryLabels(info.Ctx); labels != nil {
   158  		labels["grpc.lb.locality"] = internal.LocalityString(lID)
   159  	}
   160  
   161  	if d.loadStore != nil {
   162  		locality := clients.Locality{Region: lID.Region, Zone: lID.Zone, SubZone: lID.SubZone}
   163  		d.loadStore.CallStarted(locality)
   164  		oldDone := pr.Done
   165  		pr.Done = func(info balancer.DoneInfo) {
   166  			if oldDone != nil {
   167  				oldDone(info)
   168  			}
   169  			d.loadStore.CallFinished(locality, info.Err)
   170  
   171  			load, ok := info.ServerLoad.(*v3orcapb.OrcaLoadReport)
   172  			if !ok || load == nil {
   173  				return
   174  			}
   175  			for n, c := range load.NamedMetrics {
   176  				d.loadStore.CallServerLoad(locality, n, c)
   177  			}
   178  		}
   179  	}
   180  
   181  	if d.counter != nil {
   182  		// Update Done() so that when the RPC finishes, the request count will
   183  		// be released.
   184  		oldDone := pr.Done
   185  		pr.Done = func(doneInfo balancer.DoneInfo) {
   186  			d.counter.EndRequest()
   187  			if oldDone != nil {
   188  				oldDone(doneInfo)
   189  			}
   190  		}
   191  	}
   192  
   193  	return pr, err
   194  }