From e2dbe5e6c0b78a5f372ac2798e4ad7944f6a80a5 Mon Sep 17 00:00:00 2001 From: Anirudh Oppiliappan Date: Mon, 11 May 2026 23:47:26 +0300 Subject: [PATCH] knotmirror/xrpc: add more prometheus metrics Signed-off-by: Anirudh Oppiliappan --- knotmirror/xrpc/metrics.go | 59 ++++++++++++++++++++++++++++++++++++++ knotmirror/xrpc/xrpc.go | 1 + nix/modules/knotmirror.nix | 10 +++---- 3 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 knotmirror/xrpc/metrics.go diff --git a/knotmirror/xrpc/metrics.go b/knotmirror/xrpc/metrics.go new file mode 100644 index 00000000..1618f340 --- /dev/null +++ b/knotmirror/xrpc/metrics.go @@ -0,0 +1,59 @@ +package xrpc + +import ( + "fmt" + "net/http" + "time" + + "github.com/go-chi/chi/v5" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +var ( + httpRequestsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "knotmirror_http_requests_total", + Help: "Total number of HTTP requests", + }, []string{"method", "path", "status", "repo"}) + + httpRequestDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{ + Name: "knotmirror_http_request_duration_seconds", + Help: "HTTP request duration in seconds", + Buckets: prometheus.DefBuckets, + }, []string{"method", "path", "status", "repo"}) +) + +type statusRecorder struct { + http.ResponseWriter + status int +} + +func (r *statusRecorder) WriteHeader(status int) { + r.status = status + r.ResponseWriter.WriteHeader(status) +} + +func metricsMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + rec := &statusRecorder{ResponseWriter: w, status: http.StatusOK} + start := time.Now() + + next.ServeHTTP(rec, r) + + routePattern := chi.RouteContext(r.Context()).RoutePattern() + if routePattern == "" { + routePattern = "unknown" + } + + repo := r.URL.Query().Get("repo") + if repo == "" { + repo = "unknown" + } + + status := fmt.Sprintf("%d", rec.status) + duration := time.Since(start).Seconds() + + httpRequestsTotal.WithLabelValues(r.Method, routePattern, status, repo).Inc() + httpRequestDuration.WithLabelValues(r.Method, routePattern, status, repo).Observe(duration) + }) +} diff --git a/knotmirror/xrpc/xrpc.go b/knotmirror/xrpc/xrpc.go index 9852e740..557fbb94 100644 --- a/knotmirror/xrpc/xrpc.go +++ b/knotmirror/xrpc/xrpc.go @@ -46,6 +46,7 @@ func New(logger *slog.Logger, cfg *config.Config, db *sql.DB, rdb *redis.Client, func (x *Xrpc) Router() http.Handler { r := chi.NewRouter() + r.Use(metricsMiddleware) r.Group(func(r chi.Router) { r.Use(x.inflight.middleware) diff --git a/nix/modules/knotmirror.nix b/nix/modules/knotmirror.nix index 2759f88b..d981fd0a 100644 --- a/nix/modules/knotmirror.nix +++ b/nix/modules/knotmirror.nix @@ -30,16 +30,16 @@ in description = "Address to listen on"; }; - metricsListenAddr = mkOption { + adminListenAddr = mkOption { type = types.str; - default = "127.0.0.1:7100"; + default = "127.0.0.1:7200"; description = "Address to listen on"; }; - adminListenAddr = mkOption { + metricsListenAddr = mkOption { type = types.str; - default = "127.0.0.1:7200"; - description = "Address to listen on"; + default = "0.0.0.0:7100"; + description = "Listen address for the Prometheus metrics endpoint"; }; hostname = mkOption { -- 2.51.2