| // Copyright 2016 Michal Witkowski. All Rights Reserved. |
| // See LICENSE for licensing terms. |
| |
| package grpc_prometheus |
| |
| import ( |
| "time" |
| |
| "google.golang.org/grpc/codes" |
| |
| prom "github.com/prometheus/client_golang/prometheus" |
| "google.golang.org/grpc" |
| ) |
| |
| type grpcType string |
| |
| const ( |
| Unary grpcType = "unary" |
| ClientStream grpcType = "client_stream" |
| ServerStream grpcType = "server_stream" |
| BidiStream grpcType = "bidi_stream" |
| ) |
| |
| var ( |
| serverStartedCounter = prom.NewCounterVec( |
| prom.CounterOpts{ |
| Namespace: "grpc", |
| Subsystem: "server", |
| Name: "started_total", |
| Help: "Total number of RPCs started on the server.", |
| }, []string{"grpc_type", "grpc_service", "grpc_method"}) |
| |
| serverHandledCounter = prom.NewCounterVec( |
| prom.CounterOpts{ |
| Namespace: "grpc", |
| Subsystem: "server", |
| Name: "handled_total", |
| Help: "Total number of RPCs completed on the server, regardless of success or failure.", |
| }, []string{"grpc_type", "grpc_service", "grpc_method", "grpc_code"}) |
| |
| serverStreamMsgReceived = prom.NewCounterVec( |
| prom.CounterOpts{ |
| Namespace: "grpc", |
| Subsystem: "server", |
| Name: "msg_received_total", |
| Help: "Total number of RPC stream messages received on the server.", |
| }, []string{"grpc_type", "grpc_service", "grpc_method"}) |
| |
| serverStreamMsgSent = prom.NewCounterVec( |
| prom.CounterOpts{ |
| Namespace: "grpc", |
| Subsystem: "server", |
| Name: "msg_sent_total", |
| Help: "Total number of gRPC stream messages sent by the server.", |
| }, []string{"grpc_type", "grpc_service", "grpc_method"}) |
| |
| serverHandledHistogramEnabled = false |
| serverHandledHistogramOpts = prom.HistogramOpts{ |
| Namespace: "grpc", |
| Subsystem: "server", |
| Name: "handling_seconds", |
| Help: "Histogram of response latency (seconds) of gRPC that had been application-level handled by the server.", |
| Buckets: prom.DefBuckets, |
| } |
| serverHandledHistogram *prom.HistogramVec |
| ) |
| |
| func init() { |
| prom.MustRegister(serverStartedCounter) |
| prom.MustRegister(serverHandledCounter) |
| prom.MustRegister(serverStreamMsgReceived) |
| prom.MustRegister(serverStreamMsgSent) |
| } |
| |
| type HistogramOption func(*prom.HistogramOpts) |
| |
| // WithHistogramBuckets allows you to specify custom bucket ranges for histograms if EnableHandlingTimeHistogram is on. |
| func WithHistogramBuckets(buckets []float64) HistogramOption { |
| return func(o *prom.HistogramOpts) { o.Buckets = buckets } |
| } |
| |
| // EnableHandlingTimeHistogram turns on recording of handling time of RPCs for server-side interceptors. |
| // Histogram metrics can be very expensive for Prometheus to retain and query. |
| func EnableHandlingTimeHistogram(opts ...HistogramOption) { |
| for _, o := range opts { |
| o(&serverHandledHistogramOpts) |
| } |
| if !serverHandledHistogramEnabled { |
| serverHandledHistogram = prom.NewHistogramVec( |
| serverHandledHistogramOpts, |
| []string{"grpc_type", "grpc_service", "grpc_method"}, |
| ) |
| prom.Register(serverHandledHistogram) |
| } |
| serverHandledHistogramEnabled = true |
| } |
| |
| type serverReporter struct { |
| rpcType grpcType |
| serviceName string |
| methodName string |
| startTime time.Time |
| } |
| |
| func newServerReporter(rpcType grpcType, fullMethod string) *serverReporter { |
| r := &serverReporter{rpcType: rpcType} |
| if serverHandledHistogramEnabled { |
| r.startTime = time.Now() |
| } |
| r.serviceName, r.methodName = splitMethodName(fullMethod) |
| serverStartedCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc() |
| return r |
| } |
| |
| func (r *serverReporter) ReceivedMessage() { |
| serverStreamMsgReceived.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc() |
| } |
| |
| func (r *serverReporter) SentMessage() { |
| serverStreamMsgSent.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc() |
| } |
| |
| func (r *serverReporter) Handled(code codes.Code) { |
| serverHandledCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName, code.String()).Inc() |
| if serverHandledHistogramEnabled { |
| serverHandledHistogram.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Observe(time.Since(r.startTime).Seconds()) |
| } |
| } |
| |
| // preRegisterMethod is invoked on Register of a Server, allowing all gRPC services labels to be pre-populated. |
| func preRegisterMethod(serviceName string, mInfo *grpc.MethodInfo) { |
| methodName := mInfo.Name |
| methodType := string(typeFromMethodInfo(mInfo)) |
| // These are just references (no increments), as just referencing will create the labels but not set values. |
| serverStartedCounter.GetMetricWithLabelValues(methodType, serviceName, methodName) |
| serverStreamMsgReceived.GetMetricWithLabelValues(methodType, serviceName, methodName) |
| serverStreamMsgSent.GetMetricWithLabelValues(methodType, serviceName, methodName) |
| if serverHandledHistogramEnabled { |
| serverHandledHistogram.GetMetricWithLabelValues(methodType, serviceName, methodName) |
| } |
| for _, code := range allCodes { |
| serverHandledCounter.GetMetricWithLabelValues(methodType, serviceName, methodName, code.String()) |
| } |
| } |
| |
| func typeFromMethodInfo(mInfo *grpc.MethodInfo) grpcType { |
| if mInfo.IsClientStream == false && mInfo.IsServerStream == false { |
| return Unary |
| } |
| if mInfo.IsClientStream == true && mInfo.IsServerStream == false { |
| return ClientStream |
| } |
| if mInfo.IsClientStream == false && mInfo.IsServerStream == true { |
| return ServerStream |
| } |
| return BidiStream |
| } |