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) {}