package grpcstats import ( "context" "gitlab.com/gitlab-org/gitaly/v16/internal/log" "google.golang.org/grpc/stats" ) // PayloadBytes implements stats.Handler and tracks amount of bytes received and send by gRPC service // for each method call. The information about statistics is added into the context and can be // extracted with payloadBytesStatsFromContext. type PayloadBytes struct{} type payloadBytesStatsKey struct{} // HandleConn exists to satisfy gRPC stats.Handler. func (s *PayloadBytes) HandleConn(context.Context, stats.ConnStats) {} // TagConn exists to satisfy gRPC stats.Handler. We don't gather connection level stats // and are thus not currently using it. func (s *PayloadBytes) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context { return ctx } // HandleRPC implements per-RPC tracing and stats instrumentation. func (s *PayloadBytes) HandleRPC(ctx context.Context, rs stats.RPCStats) { switch st := rs.(type) { case *stats.InPayload: bytesStats := ctx.Value(payloadBytesStatsKey{}).(*PayloadBytesStats) bytesStats.InPayloadBytes += int64(st.Length) case *stats.OutPayload: bytesStats := ctx.Value(payloadBytesStatsKey{}).(*PayloadBytesStats) bytesStats.OutPayloadBytes += int64(st.Length) } } // TagRPC initializes context with an RPC specific stats collector. // The returned context will be used in method invocation as is passed into HandleRPC. func (s *PayloadBytes) TagRPC(ctx context.Context, _ *stats.RPCTagInfo) context.Context { return context.WithValue(ctx, payloadBytesStatsKey{}, new(PayloadBytesStats)) } // PayloadBytesStats contains info about bytes received and sent by the gRPC method. type PayloadBytesStats struct { // InPayloadBytes amount of bytes received. InPayloadBytes int64 // OutPayloadBytes amount of bytes sent. OutPayloadBytes int64 } // Fields returns logging info. func (s *PayloadBytesStats) Fields() log.Fields { return log.Fields{ "grpc.request.payload_bytes": s.InPayloadBytes, "grpc.response.payload_bytes": s.OutPayloadBytes, } } // FieldsProducer extracts stats info from the context and returns it as a logging fields. func FieldsProducer(ctx context.Context, _ error) log.Fields { payloadBytesStats := payloadBytesStatsFromContext(ctx) if payloadBytesStats != nil { return payloadBytesStats.Fields() } return nil } func payloadBytesStatsFromContext(ctx context.Context) *PayloadBytesStats { v, ok := ctx.Value(payloadBytesStatsKey{}).(*PayloadBytesStats) if !ok { return nil } return v }