// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package middleware import ( "context" "sync/atomic" "github.com/armon/go-metrics" "github.com/armon/go-metrics/prometheus" "google.golang.org/grpc" "google.golang.org/grpc/stats" ) var StatsGauges = []prometheus.GaugeDefinition{ { Name: []string{"grpc", "server", "connections"}, Help: "Measures the number of active gRPC connections open on the server.", }, { Name: []string{"grpc", "client", "connections"}, Help: "Measures the number of active gRPC connections open from the client agent to any Consul servers.", }, { Name: []string{"grpc", "server", "streams"}, Help: "Measures the number of active gRPC streams handled by the server.", }, } var StatsCounters = []prometheus.CounterDefinition{ { Name: []string{"grpc", "client", "request", "count"}, Help: "Counts the number of gRPC requests made by the client agent to a Consul server.", }, { Name: []string{"grpc", "server", "request", "count"}, Help: "Counts the number of gRPC requests received by the server.", }, { Name: []string{"grpc", "client", "connection", "count"}, Help: "Counts the number of new gRPC connections opened by the client agent to a Consul server.", }, { Name: []string{"grpc", "server", "connection", "count"}, Help: "Counts the number of new gRPC connections received by the server.", }, { Name: []string{"grpc", "server", "stream", "count"}, Help: "Counts the number of new gRPC streams received by the server.", }, } // statsHandler is a grpc/stats.StatsHandler which emits connection and // request metrics to go-metrics. type statsHandler struct { // activeConns is used with sync/atomic and MUST be 64-bit aligned. To ensure // alignment on 32-bit platforms this field must remain the first field in // the struct. See https://golang.org/pkg/sync/atomic/#pkg-note-BUG. activeConns uint64 metrics *metrics.Metrics labels []metrics.Label } func NewStatsHandler(m *metrics.Metrics, labels []metrics.Label) *statsHandler { return &statsHandler{metrics: m, labels: labels} } // TagRPC implements grpcStats.StatsHandler func (c *statsHandler) TagRPC(ctx context.Context, _ *stats.RPCTagInfo) context.Context { // No-op return ctx } // HandleRPC implements grpcStats.StatsHandler func (c *statsHandler) HandleRPC(_ context.Context, s stats.RPCStats) { label := "server" if s.IsClient() { label = "client" } switch s.(type) { case *stats.InHeader: c.metrics.IncrCounterWithLabels([]string{"grpc", label, "request", "count"}, 1, c.labels) } } // TagConn implements grpcStats.StatsHandler func (c *statsHandler) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context { // No-op return ctx } // HandleConn implements grpcStats.StatsHandler func (c *statsHandler) HandleConn(_ context.Context, s stats.ConnStats) { label := "server" if s.IsClient() { label = "client" } var count uint64 switch s.(type) { case *stats.ConnBegin: count = atomic.AddUint64(&c.activeConns, 1) c.metrics.IncrCounterWithLabels([]string{"grpc", label, "connection", "count"}, 1, c.labels) case *stats.ConnEnd: // Decrement! count = atomic.AddUint64(&c.activeConns, ^uint64(0)) } c.metrics.SetGaugeWithLabels([]string{"grpc", label, "connections"}, float32(count), c.labels) } // Intercept matches the Unary interceptor function signature. This unary interceptor will count RPC requests // but does not handle any connection processing or perform RPC "tagging" func (c *statsHandler) Intercept(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { c.metrics.IncrCounterWithLabels([]string{"grpc", "server", "request", "count"}, 1, c.labels) return handler(ctx, req) } type activeStreamCounter struct { // count is used with sync/atomic and MUST be 64-bit aligned. To ensure // alignment on 32-bit platforms this field must remain the first field in // the struct. See https://golang.org/pkg/sync/atomic/#pkg-note-BUG. count uint64 metrics *metrics.Metrics labels []metrics.Label } func NewActiveStreamCounter(m *metrics.Metrics, labels []metrics.Label) *activeStreamCounter { return &activeStreamCounter{metrics: m, labels: labels} } // GRPCCountingStreamInterceptor is a grpc.ServerStreamInterceptor that emits a // a metric of the count of open streams. func (i *activeStreamCounter) Intercept( srv interface{}, ss grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler, ) error { count := atomic.AddUint64(&i.count, 1) i.metrics.SetGaugeWithLabels([]string{"grpc", "server", "streams"}, float32(count), i.labels) i.metrics.IncrCounterWithLabels([]string{"grpc", "server", "stream", "count"}, 1, i.labels) defer func() { count := atomic.AddUint64(&i.count, ^uint64(0)) i.metrics.SetGaugeWithLabels([]string{"grpc", "server", "streams"}, float32(count), i.labels) }() return handler(srv, ss) }