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 }