github.com/msales/pkg/v3@v3.24.0/grpcx/stats.go (about)

     1  package grpcx
     2  
     3  import (
     4  	"github.com/msales/pkg/v3/stats"
     5  	"golang.org/x/net/context"
     6  	grpcstats "google.golang.org/grpc/stats"
     7  )
     8  
     9  const (
    10  	methodKey ctxKey = iota
    11  	tagsKey
    12  )
    13  
    14  type ctxKey int
    15  
    16  // Compile-time interface checks.
    17  var (
    18  	_ grpcstats.Handler = (*rpcStatsHandler)(nil)
    19  	_ grpcstats.Handler = (*aggregateHandler)(nil)
    20  	_ grpcstats.Handler = (*handler)(nil)
    21  )
    22  
    23  // WithHandlers returns an aggregated stats handler. All inner handlers are called in order.
    24  func WithHandlers(handlers ...grpcstats.Handler) grpcstats.Handler {
    25  	return &aggregateHandler{
    26  		handlers: handlers,
    27  	}
    28  }
    29  
    30  // WithRPCStats returns a handler that collects and tags RPC stats.
    31  func WithRPCStats(stats stats.Stats, tagsFns ...TagsFunc) grpcstats.Handler {
    32  	return &rpcStatsHandler{stats: stats, tagsFns: tagsFns}
    33  }
    34  
    35  // TagsFunc represents a function that returns a set of tags for the stats.
    36  // The payload is the incoming message object.
    37  type TagsFunc func(ctx context.Context, payload interface{}) stats.Tags
    38  
    39  // rpcStatsHandler records stats of each RPC: message rate and request duration.
    40  type rpcStatsHandler struct {
    41  	handler
    42  
    43  	stats   stats.Stats
    44  	tagsFns []TagsFunc
    45  }
    46  
    47  // TagRPC can attach some information to the given context.
    48  func (h *rpcStatsHandler) TagRPC(ctx context.Context, info *grpcstats.RPCTagInfo) context.Context {
    49  	if h.tagsFns != nil {
    50  		tags := make([]interface{}, 0)
    51  		ctx = context.WithValue(ctx, tagsKey, &tags)
    52  	}
    53  	return context.WithValue(ctx, methodKey, info.FullMethodName)
    54  }
    55  
    56  // HandleRPC processes the RPC stats.
    57  func (h *rpcStatsHandler) HandleRPC(ctx context.Context, rpcStats grpcstats.RPCStats) {
    58  	if in, ok := rpcStats.(*grpcstats.InPayload); ok && h.tagsFns != nil {
    59  		tags := make([]interface{}, 0)
    60  
    61  		for _, fn := range h.tagsFns {
    62  			for tag, val := range fn(ctx, in.Payload) {
    63  				tags = append(tags, tag, val)
    64  			}
    65  		}
    66  
    67  		ctxTags := ctx.Value(tagsKey).(*[]interface{})
    68  		*ctxTags = tags
    69  
    70  		return
    71  	}
    72  
    73  	if end, ok := rpcStats.(*grpcstats.End); ok {
    74  		tags := make([]interface{}, 0)
    75  
    76  		t, ok := ctx.Value(tagsKey).(*[]interface{})
    77  		if ok {
    78  			tags = *t
    79  		}
    80  
    81  		tags = append(tags,
    82  			"method", h.methodFromContext(ctx),
    83  			"status", h.getStatus(end),
    84  		)
    85  
    86  		_ = h.stats.Inc("rpc.end", 1, 1, tags...)
    87  		_ = h.stats.Timing("rpc.time", end.EndTime.Sub(end.BeginTime), 1, tags...)
    88  	}
    89  }
    90  
    91  // methodFromContext retrieves a full RPC method name from context.
    92  func (h *rpcStatsHandler) methodFromContext(ctx context.Context) string {
    93  	method := ctx.Value(methodKey)
    94  	if method == nil {
    95  		method = "unknown"
    96  	}
    97  
    98  	return method.(string)
    99  }
   100  
   101  // getStatus returns the status of the current RPC.
   102  func (h *rpcStatsHandler) getStatus(end *grpcstats.End) string {
   103  	status := "success"
   104  	if end.Error != nil {
   105  		status = "error"
   106  	}
   107  
   108  	return status
   109  }
   110  
   111  // aggregateHandler represents an aggregated stats handler.
   112  type aggregateHandler struct {
   113  	handlers []grpcstats.Handler
   114  }
   115  
   116  // TagRPC can attach some information to the given context.
   117  func (a *aggregateHandler) TagRPC(ctx context.Context, info *grpcstats.RPCTagInfo) context.Context {
   118  	a.withEachHandler(func(h grpcstats.Handler) {
   119  		ctx = h.TagRPC(ctx, info)
   120  	})
   121  
   122  	return ctx
   123  }
   124  
   125  // HandleRPC processes the RPC stats.
   126  func (a *aggregateHandler) HandleRPC(ctx context.Context, rpcStats grpcstats.RPCStats) {
   127  	a.withEachHandler(func(h grpcstats.Handler) {
   128  		h.HandleRPC(ctx, rpcStats)
   129  	})
   130  }
   131  
   132  // TagConn can attach some information to the given context.
   133  func (a *aggregateHandler) TagConn(ctx context.Context, connStats *grpcstats.ConnTagInfo) context.Context {
   134  	a.withEachHandler(func(h grpcstats.Handler) {
   135  		ctx = h.TagConn(ctx, connStats)
   136  	})
   137  
   138  	return ctx
   139  }
   140  
   141  // HandleConn processes the Conn stats.
   142  func (a *aggregateHandler) HandleConn(ctx context.Context, connStats grpcstats.ConnStats) {
   143  	a.withEachHandler(func(h grpcstats.Handler) {
   144  		h.HandleConn(ctx, connStats)
   145  	})
   146  }
   147  
   148  // withEachHandler executes a callback on each inner handler.
   149  func (a *aggregateHandler) withEachHandler(fn func(grpcstats.Handler)) {
   150  	for _, h := range a.handlers {
   151  		fn(h)
   152  	}
   153  }
   154  
   155  // handler represents a no-op stats handler. Can be used as a base for specialised handlers.
   156  type handler struct{}
   157  
   158  // TagRPC can attach some information to the given context.
   159  func (*handler) TagRPC(ctx context.Context, _ *grpcstats.RPCTagInfo) context.Context {
   160  	return ctx
   161  }
   162  
   163  // HandleRPC processes the RPC stats.
   164  func (*handler) HandleRPC(context.Context, grpcstats.RPCStats) {}
   165  
   166  // TagConn can attach some information to the given context.
   167  func (*handler) TagConn(ctx context.Context, _ *grpcstats.ConnTagInfo) context.Context {
   168  	return ctx
   169  }
   170  
   171  // HandleConn processes the Conn stats.
   172  func (*handler) HandleConn(context.Context, grpcstats.ConnStats) {}