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  }