diff --git a/knotmirror/xrpc/metrics.go b/knotmirror/xrpc/metrics.go new file mode 100644 --- /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 --- a/knotmirror/xrpc/xrpc.go +++ b/knotmirror/xrpc/xrpc.go @@ -46,6 +46,7 @@ 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 --- a/nix/modules/knotmirror.nix +++ b/nix/modules/knotmirror.nix @@ -30,16 +30,16 @@ description = "Address to listen on"; }; - metricsListenAddr = mkOption { - type = types.str; - default = "127.0.0.1:7100"; - description = "Address to listen on"; - }; - adminListenAddr = mkOption { type = types.str; default = "127.0.0.1:7200"; description = "Address to listen on"; + }; + + metricsListenAddr = mkOption { + type = types.str; + default = "0.0.0.0:7100"; + description = "Listen address for the Prometheus metrics endpoint"; }; hostname = mkOption {