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