From c5fc6defb8e4b94e6cd1387644664f1dc3cd2005 Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Wed, 17 Dec 2025 21:08:40 +0400 Subject: [PATCH] fix: report correct request paths from workspace proxy metrics (#21302) I noticed while looking at scale test metrics that we don't always report a useful path in the API request metrics. ![image.png](https://app.graphite.com/user-attachments/assets/a5b0dadf-9c2f-46a8-a6c1-3ad5f6201edb.png) There are a lot of requests with path `/*`. I chased this problem to the workspace proxy, where we mount a the proxy router as a child of a "root" router to support some high level endpoints like `latency-check`. Because we query the path from the Chi route context in the prometheus middleware _before_ the request is actually handled, we can have a partially resolved pattern match only corresponding to the root router. The fix is to always re-resolve the path, rather than accept a partially resolved path. --- coderd/httpmw/prometheus.go | 5 ----- coderd/httpmw/prometheus_test.go | 38 ++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/coderd/httpmw/prometheus.go b/coderd/httpmw/prometheus.go index 8b7b33381c..7ace256182 100644 --- a/coderd/httpmw/prometheus.go +++ b/coderd/httpmw/prometheus.go @@ -106,11 +106,6 @@ func getRoutePattern(r *http.Request) string { return "" } - if pattern := rctx.RoutePattern(); pattern != "" { - // Pattern is already available - return pattern - } - routePath := r.URL.Path if r.URL.RawPath != "" { routePath = r.URL.RawPath diff --git a/coderd/httpmw/prometheus_test.go b/coderd/httpmw/prometheus_test.go index e05ae53d38..87928259e9 100644 --- a/coderd/httpmw/prometheus_test.go +++ b/coderd/httpmw/prometheus_test.go @@ -2,11 +2,13 @@ package httpmw_test import ( "context" + "fmt" "net/http" "net/http/httptest" "testing" "github.com/go-chi/chi/v5" + "github.com/google/uuid" "github.com/prometheus/client_golang/prometheus" cm "github.com/prometheus/client_model/go" "github.com/stretchr/testify/assert" @@ -164,6 +166,42 @@ func TestPrometheus(t *testing.T) { require.Equal(t, "UNKNOWN", reqProcessed["path"]) require.Equal(t, "GET", reqProcessed["method"]) }) + + t.Run("Subrouter", func(t *testing.T) { + t.Parallel() + reg := prometheus.NewRegistry() + promMW := httpmw.Prometheus(reg) + + r := chi.NewRouter() + r.Use(promMW) + r.Get("/api/v2/workspaceagents/{workspaceagent}/pty", func(w http.ResponseWriter, r *http.Request) {}) + + // Mount under a root router like wsproxy does. + rootRouter := chi.NewRouter() + rootRouter.Get("/latency-check", func(w http.ResponseWriter, r *http.Request) {}) + rootRouter.Mount("/", r) + + agentID := uuid.UUID{1} + req := httptest.NewRequest("GET", fmt.Sprintf("/api/v2/workspaceagents/%s/pty", agentID.String()), nil) + + sw := &tracing.StatusWriter{ResponseWriter: httptest.NewRecorder()} + rootRouter.ServeHTTP(sw, req) + + metrics, err := reg.Gather() + require.NoError(t, err) + require.Greater(t, len(metrics), 0) + metricLabels := getMetricLabels(metrics) + + reqProcessed, ok := metricLabels["coderd_api_requests_processed_total"] + require.True(t, ok, "coderd_api_requests_processed_total metric not found") + require.Equal(t, "/api/v2/workspaceagents/{workspaceagent}/pty", reqProcessed["path"]) + require.Equal(t, "GET", reqProcessed["method"]) + + concurrentRequests, ok := metricLabels["coderd_api_concurrent_requests"] + require.True(t, ok, "coderd_api_concurrent_requests metric not found") + require.Equal(t, "/api/v2/workspaceagents/{workspaceagent}/pty", concurrentRequests["path"]) + require.Equal(t, "GET", concurrentRequests["method"]) + }) } func getMetricLabels(metrics []*cm.MetricFamily) map[string]map[string]string {