metrics.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. /*
  2. Copyright © The ESO Authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package server
  14. import (
  15. "context"
  16. "errors"
  17. "fmt"
  18. "time"
  19. "github.com/prometheus/client_golang/prometheus"
  20. "google.golang.org/grpc"
  21. )
  22. var (
  23. // gRPC latency buckets optimized for typical RPC call durations.
  24. grpcLatencyBuckets = []float64{0.01, 0.05, 0.1, 0.5, 1, 2, 5, 10, 30}
  25. // gRPC Server Counters.
  26. serverRequestsTotal = prometheus.NewCounterVec(
  27. prometheus.CounterOpts{
  28. Name: "grpc_server_requests_total",
  29. Help: "Total number of gRPC server requests",
  30. },
  31. []string{"method", "status"},
  32. )
  33. // gRPC Server Histograms.
  34. serverRequestDuration = prometheus.NewHistogramVec(
  35. prometheus.HistogramOpts{
  36. Name: "grpc_server_request_duration_seconds",
  37. Help: "Duration of gRPC server requests in seconds",
  38. Buckets: grpcLatencyBuckets,
  39. },
  40. []string{"method"},
  41. )
  42. // gRPC Server Gauges.
  43. serverActiveConnections = prometheus.NewGauge(
  44. prometheus.GaugeOpts{
  45. Name: "grpc_server_active_connections",
  46. Help: "Number of active gRPC server connections",
  47. },
  48. )
  49. )
  50. // Metrics provides the hooks used by the server interceptors.
  51. type Metrics interface {
  52. RecordRequest(method string, err error, duration time.Duration)
  53. IncrementActiveConnections()
  54. DecrementActiveConnections()
  55. }
  56. // defaultServerMetrics implements Metrics using Prometheus.
  57. type defaultServerMetrics struct{}
  58. // RecordRequest records metrics for a server request.
  59. func (m *defaultServerMetrics) RecordRequest(method string, err error, duration time.Duration) {
  60. status := "success"
  61. if err != nil {
  62. status = "error"
  63. }
  64. serverRequestDuration.WithLabelValues(method).Observe(duration.Seconds())
  65. serverRequestsTotal.WithLabelValues(method, status).Inc()
  66. }
  67. // IncrementActiveConnections increments the active connections gauge.
  68. func (m *defaultServerMetrics) IncrementActiveConnections() {
  69. serverActiveConnections.Inc()
  70. }
  71. // DecrementActiveConnections decrements the active connections gauge.
  72. func (m *defaultServerMetrics) DecrementActiveConnections() {
  73. serverActiveConnections.Dec()
  74. }
  75. // Global instance.
  76. var serverMetrics Metrics = &defaultServerMetrics{}
  77. // MetricsUnaryInterceptor returns a gRPC unary server interceptor that records metrics.
  78. func MetricsUnaryInterceptor() grpc.UnaryServerInterceptor {
  79. return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
  80. start := time.Now()
  81. // Call the handler
  82. resp, err := handler(ctx, req)
  83. // Record metrics
  84. duration := time.Since(start)
  85. serverMetrics.RecordRequest(info.FullMethod, err, duration)
  86. return resp, err
  87. }
  88. }
  89. // ConnectionCountingStreamServerInterceptor returns a gRPC stream server interceptor that tracks active connections.
  90. func ConnectionCountingStreamServerInterceptor() grpc.StreamServerInterceptor {
  91. return func(srv any, ss grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
  92. serverMetrics.IncrementActiveConnections()
  93. defer serverMetrics.DecrementActiveConnections()
  94. return handler(srv, ss)
  95. }
  96. }
  97. // RegisterMetrics registers all server metrics with Prometheus.
  98. func RegisterMetrics(registry prometheus.Registerer) error {
  99. collectors := []prometheus.Collector{
  100. serverRequestsTotal,
  101. serverRequestDuration,
  102. serverActiveConnections,
  103. }
  104. for _, collector := range collectors {
  105. if err := registry.Register(collector); err != nil {
  106. // Ignore duplicate registration so shared registries can reuse the collectors.
  107. var alreadyRegistered prometheus.AlreadyRegisteredError
  108. if errors.As(err, &alreadyRegistered) {
  109. continue
  110. }
  111. return fmt.Errorf("failed to register server metric: %w", err)
  112. }
  113. }
  114. return nil
  115. }
  116. // GetServerMetrics returns the server metrics instance for testing.
  117. func GetServerMetrics() Metrics {
  118. return serverMetrics
  119. }