From daa2beea3a3e644fe74f0efeb3b5937736a5338c 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 | 8 +++++- 3 files changed, 67 insertions(+), 1 deletion(-) 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 37827abf..343ea84c 100644 --- a/knotmirror/xrpc/xrpc.go +++ b/knotmirror/xrpc/xrpc.go @@ -41,6 +41,7 @@ func New(logger *slog.Logger, cfg *config.Config, db *sql.DB, resolver *idresolv func (x *Xrpc) Router() http.Handler { r := chi.NewRouter() + r.Use(metricsMiddleware) r.Get("/"+tangled.GitTempGetArchiveNSID, x.GetArchive) r.Get("/"+tangled.GitTempGetBlobNSID, x.GetBlob) diff --git a/nix/modules/knotmirror.nix b/nix/modules/knotmirror.nix index b9f2ed28..1a41e21a 100644 --- a/nix/modules/knotmirror.nix +++ b/nix/modules/knotmirror.nix @@ -36,6 +36,12 @@ in description = "Address to listen on"; }; + metricsListenAddr = mkOption { + type = types.str; + default = "127.0.0.1:7100"; + description = "Listen address for the Prometheus metrics endpoint"; + }; + hostname = mkOption { type = types.str; example = "my.knotmirror.com"; @@ -143,7 +149,7 @@ in "MIRROR_KNOT_USE_SSL=${boolToString cfg.knotUseSSL}" "MIRROR_KNOT_SSRF=${boolToString cfg.knotSSRF}" "MIRROR_RESYNC_PARALLELISM=12" - "MIRROR_METRICS_LISTEN=127.0.0.1:7100" + "MIRROR_METRICS_LISTEN=${cfg.metricsListenAddr}" "MIRROR_ADMIN_LISTEN=${cfg.adminListenAddr}" "MIRROR_SLURPER_CONCURRENCY=4" ]; -- 2.51.2