Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitbda202f

Browse files
feat: add path & method labels to prometheus metrics (cherry-pick#17362) (#17416)
1 parent0f27da0 commitbda202f

File tree

2 files changed

+133
-10
lines changed

2 files changed

+133
-10
lines changed

‎coderd/httpmw/prometheus.go

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package httpmw
33
import (
44
"net/http"
55
"strconv"
6+
"strings"
67
"time"
78

89
"github.com/go-chi/chi/v5"
@@ -22,18 +23,18 @@ func Prometheus(register prometheus.Registerer) func(http.Handler) http.Handler
2223
Name:"requests_processed_total",
2324
Help:"The total number of processed API requests",
2425
}, []string{"code","method","path"})
25-
requestsConcurrent:=factory.NewGauge(prometheus.GaugeOpts{
26+
requestsConcurrent:=factory.NewGaugeVec(prometheus.GaugeOpts{
2627
Namespace:"coderd",
2728
Subsystem:"api",
2829
Name:"concurrent_requests",
2930
Help:"The number of concurrent API requests.",
30-
})
31-
websocketsConcurrent:=factory.NewGauge(prometheus.GaugeOpts{
31+
}, []string{"method","path"})
32+
websocketsConcurrent:=factory.NewGaugeVec(prometheus.GaugeOpts{
3233
Namespace:"coderd",
3334
Subsystem:"api",
3435
Name:"concurrent_websockets",
3536
Help:"The total number of concurrent API websockets.",
36-
})
37+
}, []string{"path"})
3738
websocketsDist:=factory.NewHistogramVec(prometheus.HistogramOpts{
3839
Namespace:"coderd",
3940
Subsystem:"api",
@@ -61,7 +62,6 @@ func Prometheus(register prometheus.Registerer) func(http.Handler) http.Handler
6162
var (
6263
start=time.Now()
6364
method=r.Method
64-
rctx=chi.RouteContext(r.Context())
6565
)
6666

6767
sw,ok:=w.(*tracing.StatusWriter)
@@ -72,24 +72,25 @@ func Prometheus(register prometheus.Registerer) func(http.Handler) http.Handler
7272
var (
7373
dist*prometheus.HistogramVec
7474
distOpts []string
75+
path=getRoutePattern(r)
7576
)
77+
7678
// We want to count WebSockets separately.
7779
ifhttpapi.IsWebsocketUpgrade(r) {
78-
websocketsConcurrent.Inc()
79-
deferwebsocketsConcurrent.Dec()
80+
websocketsConcurrent.WithLabelValues(path).Inc()
81+
deferwebsocketsConcurrent.WithLabelValues(path).Dec()
8082

8183
dist=websocketsDist
8284
}else {
83-
requestsConcurrent.Inc()
84-
deferrequestsConcurrent.Dec()
85+
requestsConcurrent.WithLabelValues(method,path).Inc()
86+
deferrequestsConcurrent.WithLabelValues(method,path).Dec()
8587

8688
dist=requestsDist
8789
distOpts= []string{method}
8890
}
8991

9092
next.ServeHTTP(w,r)
9193

92-
path:=rctx.RoutePattern()
9394
distOpts=append(distOpts,path)
9495
statusStr:=strconv.Itoa(sw.Status)
9596

@@ -98,3 +99,34 @@ func Prometheus(register prometheus.Registerer) func(http.Handler) http.Handler
9899
})
99100
}
100101
}
102+
103+
funcgetRoutePattern(r*http.Request)string {
104+
rctx:=chi.RouteContext(r.Context())
105+
ifrctx==nil {
106+
return""
107+
}
108+
109+
ifpattern:=rctx.RoutePattern();pattern!="" {
110+
// Pattern is already available
111+
returnpattern
112+
}
113+
114+
routePath:=r.URL.Path
115+
ifr.URL.RawPath!="" {
116+
routePath=r.URL.RawPath
117+
}
118+
119+
tctx:=chi.NewRouteContext()
120+
routes:=rctx.Routes
121+
ifroutes!=nil&&!routes.Match(tctx,r.Method,routePath) {
122+
// No matching pattern. /api/* requests will be matched as "UNKNOWN"
123+
// All other ones will be matched as "STATIC".
124+
ifstrings.HasPrefix(routePath,"/api/") {
125+
return"UNKNOWN"
126+
}
127+
return"STATIC"
128+
}
129+
130+
// tctx has the updated pattern, since Match mutates it
131+
returntctx.RoutePattern()
132+
}

‎coderd/httpmw/prometheus_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,19 @@ import (
88

99
"github.com/go-chi/chi/v5"
1010
"github.com/prometheus/client_golang/prometheus"
11+
cm"github.com/prometheus/client_model/go"
12+
"github.com/stretchr/testify/assert"
1113
"github.com/stretchr/testify/require"
1214

1315
"github.com/coder/coder/v2/coderd/httpmw"
1416
"github.com/coder/coder/v2/coderd/tracing"
17+
"github.com/coder/coder/v2/testutil"
18+
"github.com/coder/websocket"
1519
)
1620

1721
funcTestPrometheus(t*testing.T) {
1822
t.Parallel()
23+
1924
t.Run("All",func(t*testing.T) {
2025
t.Parallel()
2126
req:=httptest.NewRequest("GET","/",nil)
@@ -29,4 +34,90 @@ func TestPrometheus(t *testing.T) {
2934
require.NoError(t,err)
3035
require.Greater(t,len(metrics),0)
3136
})
37+
38+
t.Run("Concurrent",func(t*testing.T) {
39+
t.Parallel()
40+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitShort)
41+
defercancel()
42+
43+
reg:=prometheus.NewRegistry()
44+
promMW:=httpmw.Prometheus(reg)
45+
46+
// Create a test handler to simulate a WebSocket connection
47+
testHandler:=http.HandlerFunc(func(rw http.ResponseWriter,r*http.Request) {
48+
conn,err:=websocket.Accept(rw,r,nil)
49+
if!assert.NoError(t,err,"failed to accept websocket") {
50+
return
51+
}
52+
deferconn.Close(websocket.StatusGoingAway,"")
53+
})
54+
55+
wrappedHandler:=promMW(testHandler)
56+
57+
r:=chi.NewRouter()
58+
r.Use(tracing.StatusWriterMiddleware,promMW)
59+
r.Get("/api/v2/build/{build}/logs",func(rw http.ResponseWriter,r*http.Request) {
60+
wrappedHandler.ServeHTTP(rw,r)
61+
})
62+
63+
srv:=httptest.NewServer(r)
64+
defersrv.Close()
65+
// nolint: bodyclose
66+
conn,_,err:=websocket.Dial(ctx,srv.URL+"/api/v2/build/1/logs",nil)
67+
require.NoError(t,err,"failed to dial WebSocket")
68+
deferconn.Close(websocket.StatusNormalClosure,"")
69+
70+
metrics,err:=reg.Gather()
71+
require.NoError(t,err)
72+
require.Greater(t,len(metrics),0)
73+
metricLabels:=getMetricLabels(metrics)
74+
75+
concurrentWebsockets,ok:=metricLabels["coderd_api_concurrent_websockets"]
76+
require.True(t,ok,"coderd_api_concurrent_websockets metric not found")
77+
require.Equal(t,"/api/v2/build/{build}/logs",concurrentWebsockets["path"])
78+
})
79+
80+
t.Run("UserRoute",func(t*testing.T) {
81+
t.Parallel()
82+
reg:=prometheus.NewRegistry()
83+
promMW:=httpmw.Prometheus(reg)
84+
85+
r:=chi.NewRouter()
86+
r.With(promMW).Get("/api/v2/users/{user}",func(w http.ResponseWriter,r*http.Request) {})
87+
88+
req:=httptest.NewRequest("GET","/api/v2/users/john",nil)
89+
90+
sw:=&tracing.StatusWriter{ResponseWriter:httptest.NewRecorder()}
91+
92+
r.ServeHTTP(sw,req)
93+
94+
metrics,err:=reg.Gather()
95+
require.NoError(t,err)
96+
require.Greater(t,len(metrics),0)
97+
metricLabels:=getMetricLabels(metrics)
98+
99+
reqProcessed,ok:=metricLabels["coderd_api_requests_processed_total"]
100+
require.True(t,ok,"coderd_api_requests_processed_total metric not found")
101+
require.Equal(t,"/api/v2/users/{user}",reqProcessed["path"])
102+
require.Equal(t,"GET",reqProcessed["method"])
103+
104+
concurrentRequests,ok:=metricLabels["coderd_api_concurrent_requests"]
105+
require.True(t,ok,"coderd_api_concurrent_requests metric not found")
106+
require.Equal(t,"/api/v2/users/{user}",concurrentRequests["path"])
107+
require.Equal(t,"GET",concurrentRequests["method"])
108+
})
109+
}
110+
111+
funcgetMetricLabels(metrics []*cm.MetricFamily)map[string]map[string]string {
112+
metricLabels:=map[string]map[string]string{}
113+
for_,metricFamily:=rangemetrics {
114+
metricName:=metricFamily.GetName()
115+
metricLabels[metricName]=map[string]string{}
116+
for_,metric:=rangemetricFamily.GetMetric() {
117+
for_,labelPair:=rangemetric.GetLabel() {
118+
metricLabels[metricName][labelPair.GetName()]=labelPair.GetValue()
119+
}
120+
}
121+
}
122+
returnmetricLabels
32123
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp