github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/rpc/stats_handler.go (about) 1 // Copyright 2017 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package rpc 12 13 import ( 14 "context" 15 "sync/atomic" 16 17 "github.com/cockroachdb/cockroach/pkg/util/log" 18 "golang.org/x/sync/syncmap" 19 "google.golang.org/grpc/stats" 20 ) 21 22 type remoteAddrKey struct{} 23 24 // Stats stores network statistics between this node and another. 25 type Stats struct { 26 count int64 27 incoming int64 28 outgoing int64 29 } 30 31 // Count returns the total number of RPCs. 32 func (s *Stats) Count() int64 { 33 return atomic.LoadInt64(&s.count) 34 } 35 36 // Incoming returns the total bytes of incoming network traffic. 37 func (s *Stats) Incoming() int64 { 38 return atomic.LoadInt64(&s.incoming) 39 } 40 41 // Outgoing returns the total bytes of outgoing network traffic. 42 func (s *Stats) Outgoing() int64 { 43 return atomic.LoadInt64(&s.outgoing) 44 } 45 46 func (s *Stats) record(rpcStats stats.RPCStats) { 47 switch v := rpcStats.(type) { 48 case *stats.InHeader: 49 atomic.AddInt64(&s.incoming, int64(v.WireLength)) 50 case *stats.InPayload: 51 // TODO(spencer): remove the +5 offset on wire length here, which 52 // is a temporary stand-in for the missing GRPC framing offset. 53 // See: https://github.com/grpc/grpc-go/issues/1647. 54 atomic.AddInt64(&s.incoming, int64(v.WireLength+5)) 55 case *stats.InTrailer: 56 atomic.AddInt64(&s.incoming, int64(v.WireLength)) 57 case *stats.OutHeader: 58 // No wire length. 59 case *stats.OutPayload: 60 atomic.AddInt64(&s.outgoing, int64(v.WireLength)) 61 case *stats.OutTrailer: 62 atomic.AddInt64(&s.outgoing, int64(v.WireLength)) 63 case *stats.End: 64 atomic.AddInt64(&s.count, 1) 65 } 66 } 67 68 type clientStatsHandler struct { 69 stats *Stats 70 } 71 72 var _ stats.Handler = &clientStatsHandler{} 73 74 // TagRPC implements the grpc.stats.Handler interface. 75 func (cs *clientStatsHandler) TagRPC(ctx context.Context, rti *stats.RPCTagInfo) context.Context { 76 return ctx 77 } 78 79 // HandleRPC implements the grpc.stats.Handler interface. 80 func (cs *clientStatsHandler) HandleRPC(ctx context.Context, rpcStats stats.RPCStats) { 81 cs.stats.record(rpcStats) 82 } 83 84 // TagConn implements the grpc.stats.Handler interface. 85 func (cs *clientStatsHandler) TagConn(ctx context.Context, cti *stats.ConnTagInfo) context.Context { 86 return ctx 87 } 88 89 // HandleConn implements the grpc.stats.Handler interface. 90 func (cs *clientStatsHandler) HandleConn(context.Context, stats.ConnStats) { 91 } 92 93 // StatsHandler manages a map of Stats objects, one per connection. 94 // It implements grpc.stats.Handler, and is used directly for any 95 // incoming connections which connect to this node's server. It uses 96 // the newClient() method to return handlers for use with outgoing 97 // client connections from this node to remote nodes. 98 type StatsHandler struct { 99 // stats is a map from remote targets to Stats objects. Note that we 100 // never remove items from this map; because we don't expect to add 101 // and remove sufficiently many nodes, this should be fine in practice. 102 stats syncmap.Map 103 } 104 105 var _ stats.Handler = &StatsHandler{} 106 107 // newClient returns a new clientStatsHandler which references the stats 108 // object bound to the specified target remote address. 109 func (sh *StatsHandler) newClient(target string) stats.Handler { 110 value, _ := sh.stats.LoadOrStore(target, &Stats{}) 111 return &clientStatsHandler{ 112 stats: value.(*Stats), 113 } 114 } 115 116 // TagRPC implements the grpc.stats.Handler interface. This 117 // interface is used directly for server-side stats recording. 118 func (sh *StatsHandler) TagRPC(ctx context.Context, rti *stats.RPCTagInfo) context.Context { 119 return ctx 120 } 121 122 // HandleRPC implements the grpc.stats.Handler interface. This 123 // interface is used directly for server-side stats recording. We 124 // consult the provided context for the remote address and use that 125 // to key into our stats map in order to properly update incoming 126 // and outgoing throughput for the implicated remote node. 127 func (sh *StatsHandler) HandleRPC(ctx context.Context, rpcStats stats.RPCStats) { 128 remoteAddr, ok := ctx.Value(remoteAddrKey{}).(string) 129 if !ok { 130 log.Warningf(ctx, "unable to record stats (%+v); remote addr not found in context", rpcStats) 131 return 132 } 133 // There is a race here, but it's meaningless in practice. Worst 134 // case is we fail to record a handful of observations. We do 135 // this to avoid creating a new Stats object on every invocation. 136 value, ok := sh.stats.Load(remoteAddr) 137 if !ok { 138 value, _ = sh.stats.LoadOrStore(remoteAddr, &Stats{}) 139 } 140 value.(*Stats).record(rpcStats) 141 } 142 143 // TagConn implements the grpc.stats.Handler interface. This interface 144 // is used directly for server-side stats recording. We tag the 145 // provided context with the remote address provided by the 146 // ConnTagInfo, and use that to properly update the Stats object 147 // belonging to that remote address. 148 func (sh *StatsHandler) TagConn(ctx context.Context, cti *stats.ConnTagInfo) context.Context { 149 return context.WithValue(ctx, remoteAddrKey{}, cti.RemoteAddr.String()) 150 } 151 152 // HandleConn implements the grpc.stats.Handler interface. This 153 // interface is used directly for server-side stats recording. 154 func (sh *StatsHandler) HandleConn(context.Context, stats.ConnStats) { 155 }