From 22312a1b72e1f4ac33bd71bd4754c366ad399fda Mon Sep 17 00:00:00 2001 From: Seongmin Lee Date: Mon, 11 May 2026 20:34:18 +0900 Subject: [PATCH] knotmirror: add more metrics Signed-off-by: Seongmin Lee --- knotmirror/xrpc/git_get_tree.go | 1 + knotmirror/xrpc/git_list_languages.go | 1 + knotmirror/xrpc/metrics.go | 19 ++++++++++++++ knotmirror/xrpc/xrpc.go | 38 +++++++++++++++++++++++++++ 4 files changed, 59 insertions(+) create mode 100644 knotmirror/xrpc/metrics.go diff --git a/knotmirror/xrpc/git_get_tree.go b/knotmirror/xrpc/git_get_tree.go index 51f8c3c8..ada01e41 100644 --- a/knotmirror/xrpc/git_get_tree.go +++ b/knotmirror/xrpc/git_get_tree.go @@ -23,6 +23,7 @@ func (x *Xrpc) GetTree(w http.ResponseWriter, r *http.Request) { path = r.URL.Query().Get("path") // path can be empty (defaults to root) ) l := x.logger.With("method", "git.getTree", "repo", repoQuery, "ref", ref) + l.Debug("call") repo, err := syntax.ParseATURI(repoQuery) if err != nil || repo.RecordKey() == "" { diff --git a/knotmirror/xrpc/git_list_languages.go b/knotmirror/xrpc/git_list_languages.go index 7507a218..b0c3573a 100644 --- a/knotmirror/xrpc/git_list_languages.go +++ b/knotmirror/xrpc/git_list_languages.go @@ -19,6 +19,7 @@ func (x *Xrpc) ListLanguages(w http.ResponseWriter, r *http.Request) { ref = r.URL.Query().Get("ref") ) l := x.logger.With("method", "git.listLanguages", "repo", repoQuery, "ref", ref) + l.Debug("call") repo, err := syntax.ParseATURI(repoQuery) if err != nil || repo.RecordKey() == "" { diff --git a/knotmirror/xrpc/metrics.go b/knotmirror/xrpc/metrics.go new file mode 100644 index 00000000..e09c44cc --- /dev/null +++ b/knotmirror/xrpc/metrics.go @@ -0,0 +1,19 @@ +package xrpc + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +var ( + xrpcRequestsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "knotmirror_xrpc_requests_total", + Help: "Total number of xrpc requests handled, labeled by method (NSID), repo at-uri, and HTTP status", + }, []string{"method", "repo", "status"}) + + xrpcRequestDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{ + Name: "knotmirror_xrpc_request_duration_seconds", + Help: "Duration of xrpc requests, labeled by method (NSID) and repo at-uri", + Buckets: prometheus.ExponentialBuckets(0.001, 2, 16), + }, []string{"method", "repo"}) +) diff --git a/knotmirror/xrpc/xrpc.go b/knotmirror/xrpc/xrpc.go index 37827abf..1734353f 100644 --- a/knotmirror/xrpc/xrpc.go +++ b/knotmirror/xrpc/xrpc.go @@ -6,10 +6,13 @@ import ( "errors" "log/slog" "net/http" + "strconv" + "strings" "time" "github.com/bluesky-social/indigo/atproto/atclient" "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" "tangled.org/core/api/tangled" "tangled.org/core/idresolver" "tangled.org/core/knotmirror/config" @@ -42,6 +45,8 @@ func New(logger *slog.Logger, cfg *config.Config, db *sql.DB, resolver *idresolv func (x *Xrpc) Router() http.Handler { r := chi.NewRouter() + r.Use(x.metrics) + r.Get("/"+tangled.GitTempGetArchiveNSID, x.GetArchive) r.Get("/"+tangled.GitTempGetBlobNSID, x.GetBlob) r.Get("/"+tangled.GitTempGetBranchNSID, x.GetBranch) @@ -61,6 +66,39 @@ func (x *Xrpc) Router() http.Handler { return r } +func (x *Xrpc) metrics(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + method := strings.TrimPrefix(r.URL.Path, "/xrpc/") + repo := r.URL.Query().Get("repo") + ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor) + start := time.Now() + + next.ServeHTTP(ww, r) + + duration := time.Since(start) + status := ww.Status() + if status == 0 { + status = http.StatusOK + } + + xrpcRequestsTotal.WithLabelValues(method, repo, strconv.Itoa(status)).Inc() + xrpcRequestDuration.WithLabelValues(method, repo).Observe(duration.Seconds()) + + attrs := []any{ + "method", method, + "status", status, + "duration_ms", duration.Milliseconds(), + } + for k, v := range r.URL.Query() { + if len(v) == 1 { + attrs = append(attrs, k, v[0]) + } else { + attrs = append(attrs, k, v) + } + } + }) +} + func writeJson(w http.ResponseWriter, status int, response any) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) -- 2.51.2