package metrics import ( "context" "fmt" "net/http" "time" grpcprom "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/rs/zerolog/log" "google.golang.org/grpc" ) var ( CacheHits = promauto.NewCounterVec(prometheus.CounterOpts{ Name: "metadata_cache_hits_total", Help: "Number of cache hits by entity type", }, []string{"entity"}) CacheMisses = promauto.NewCounterVec(prometheus.CounterOpts{ Name: "metadata_cache_misses_total", Help: "Number of cache misses by entity type", }, []string{"entity"}) ProviderRequests = promauto.NewCounterVec(prometheus.CounterOpts{ Name: "metadata_provider_requests_total", Help: "Number of requests to external providers", }, []string{"provider", "operation", "status"}) ProviderLatency = promauto.NewHistogramVec(prometheus.HistogramOpts{ Name: "metadata_provider_request_duration_seconds", Help: "Latency of external provider requests", Buckets: []float64{0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10}, }, []string{"provider", "operation"}) DBQueryLatency = promauto.NewHistogramVec(prometheus.HistogramOpts{ Name: "metadata_db_query_duration_seconds", Help: "Latency of database queries", Buckets: prometheus.ExponentialBuckets(0.001, 2, 10), }, []string{"operation"}) ) type ServerMetrics struct { grpcMetrics *grpcprom.ServerMetrics registry *prometheus.Registry } func NewServerMetrics() *ServerMetrics { reg := prometheus.NewRegistry() reg.MustRegister(prometheus.NewGoCollector()) reg.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{})) srvMetrics := grpcprom.NewServerMetrics( grpcprom.WithServerHandlingTimeHistogram( grpcprom.WithHistogramBuckets([]float64{ 0.001, 0.01, 0.05, 0.1, 0.3, 0.6, 1, 3, 6, 10, 30, }), ), ) reg.MustRegister(srvMetrics) reg.MustRegister(CacheHits) reg.MustRegister(CacheMisses) reg.MustRegister(ProviderRequests) reg.MustRegister(ProviderLatency) reg.MustRegister(DBQueryLatency) return &ServerMetrics{ grpcMetrics: srvMetrics, registry: reg, } } func (m *ServerMetrics) UnaryServerInterceptor() grpc.UnaryServerInterceptor { return m.grpcMetrics.UnaryServerInterceptor() } func (m *ServerMetrics) StreamServerInterceptor() grpc.StreamServerInterceptor { return m.grpcMetrics.StreamServerInterceptor() } func (m *ServerMetrics) InitializeMetrics(srv *grpc.Server) { m.grpcMetrics.InitializeMetrics(srv) } func (m *ServerMetrics) StartHTTPServer(ctx context.Context, port int) { mux := http.NewServeMux() mux.Handle("/metrics", promhttp.HandlerFor(m.registry, promhttp.HandlerOpts{ EnableOpenMetrics: true, })) mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("OK")) }) srv := &http.Server{ Addr: fmt.Sprintf(":%d", port), Handler: mux, ReadHeaderTimeout: 5 * time.Second, } go func() { <-ctx.Done() shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _ = srv.Shutdown(shutdownCtx) }() log.Info().Int("port", port).Msg("metrics HTTP server starting") if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Error().Err(err).Msg("metrics HTTP server failed") } }