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