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 }