blob: ab037db861986bd70de421104c04b6137408e6a6 [file] [log] [blame]
khenaidooab1f7bd2019-11-14 14:00:27 -05001// Copyright 2017 The Prometheus Authors
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package promhttp
15
16import (
17 "errors"
18 "net/http"
19 "strconv"
20 "strings"
21 "time"
22
23 dto "github.com/prometheus/client_model/go"
24
25 "github.com/prometheus/client_golang/prometheus"
26)
27
28// magicString is used for the hacky label test in checkLabels. Remove once fixed.
29const magicString = "zZgWfBxLqvG8kc8IMv3POi2Bb0tZI3vAnBx+gBaFi9FyPzB/CzKUer1yufDa"
30
31// InstrumentHandlerInFlight is a middleware that wraps the provided
32// http.Handler. It sets the provided prometheus.Gauge to the number of
33// requests currently handled by the wrapped http.Handler.
34//
35// See the example for InstrumentHandlerDuration for example usage.
36func InstrumentHandlerInFlight(g prometheus.Gauge, next http.Handler) http.Handler {
37 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
38 g.Inc()
39 defer g.Dec()
40 next.ServeHTTP(w, r)
41 })
42}
43
44// InstrumentHandlerDuration is a middleware that wraps the provided
45// http.Handler to observe the request duration with the provided ObserverVec.
khenaidood948f772021-08-11 17:49:24 -040046// The ObserverVec must have valid metric and label names and must have zero,
47// one, or two non-const non-curried labels. For those, the only allowed label
48// names are "code" and "method". The function panics otherwise. The Observe
49// method of the Observer in the ObserverVec is called with the request duration
50// in seconds. Partitioning happens by HTTP status code and/or HTTP method if
51// the respective instance label names are present in the ObserverVec. For
52// unpartitioned observations, use an ObserverVec with zero labels. Note that
53// partitioning of Histograms is expensive and should be used judiciously.
khenaidooab1f7bd2019-11-14 14:00:27 -050054//
55// If the wrapped Handler does not set a status code, a status code of 200 is assumed.
56//
57// If the wrapped Handler panics, no values are reported.
58//
59// Note that this method is only guaranteed to never observe negative durations
60// if used with Go1.9+.
61func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc {
62 code, method := checkLabels(obs)
63
64 if code {
65 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
66 now := time.Now()
67 d := newDelegator(w, nil)
68 next.ServeHTTP(d, r)
69
70 obs.With(labels(code, method, r.Method, d.Status())).Observe(time.Since(now).Seconds())
71 })
72 }
73
74 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
75 now := time.Now()
76 next.ServeHTTP(w, r)
77 obs.With(labels(code, method, r.Method, 0)).Observe(time.Since(now).Seconds())
78 })
79}
80
81// InstrumentHandlerCounter is a middleware that wraps the provided http.Handler
khenaidood948f772021-08-11 17:49:24 -040082// to observe the request result with the provided CounterVec. The CounterVec
83// must have valid metric and label names and must have zero, one, or two
84// non-const non-curried labels. For those, the only allowed label names are
85// "code" and "method". The function panics otherwise. Partitioning of the
86// CounterVec happens by HTTP status code and/or HTTP method if the respective
87// instance label names are present in the CounterVec. For unpartitioned
88// counting, use a CounterVec with zero labels.
khenaidooab1f7bd2019-11-14 14:00:27 -050089//
90// If the wrapped Handler does not set a status code, a status code of 200 is assumed.
91//
92// If the wrapped Handler panics, the Counter is not incremented.
93//
94// See the example for InstrumentHandlerDuration for example usage.
95func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler) http.HandlerFunc {
96 code, method := checkLabels(counter)
97
98 if code {
99 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
100 d := newDelegator(w, nil)
101 next.ServeHTTP(d, r)
102 counter.With(labels(code, method, r.Method, d.Status())).Inc()
103 })
104 }
105
106 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
107 next.ServeHTTP(w, r)
108 counter.With(labels(code, method, r.Method, 0)).Inc()
109 })
110}
111
112// InstrumentHandlerTimeToWriteHeader is a middleware that wraps the provided
113// http.Handler to observe with the provided ObserverVec the request duration
khenaidood948f772021-08-11 17:49:24 -0400114// until the response headers are written. The ObserverVec must have valid
115// metric and label names and must have zero, one, or two non-const non-curried
116// labels. For those, the only allowed label names are "code" and "method". The
117// function panics otherwise. The Observe method of the Observer in the
118// ObserverVec is called with the request duration in seconds. Partitioning
119// happens by HTTP status code and/or HTTP method if the respective instance
120// label names are present in the ObserverVec. For unpartitioned observations,
121// use an ObserverVec with zero labels. Note that partitioning of Histograms is
122// expensive and should be used judiciously.
khenaidooab1f7bd2019-11-14 14:00:27 -0500123//
124// If the wrapped Handler panics before calling WriteHeader, no value is
125// reported.
126//
127// Note that this method is only guaranteed to never observe negative durations
128// if used with Go1.9+.
129//
130// See the example for InstrumentHandlerDuration for example usage.
131func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc {
132 code, method := checkLabels(obs)
133
134 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
135 now := time.Now()
136 d := newDelegator(w, func(status int) {
137 obs.With(labels(code, method, r.Method, status)).Observe(time.Since(now).Seconds())
138 })
139 next.ServeHTTP(d, r)
140 })
141}
142
143// InstrumentHandlerRequestSize is a middleware that wraps the provided
khenaidood948f772021-08-11 17:49:24 -0400144// http.Handler to observe the request size with the provided ObserverVec. The
145// ObserverVec must have valid metric and label names and must have zero, one,
146// or two non-const non-curried labels. For those, the only allowed label names
147// are "code" and "method". The function panics otherwise. The Observe method of
148// the Observer in the ObserverVec is called with the request size in
149// bytes. Partitioning happens by HTTP status code and/or HTTP method if the
150// respective instance label names are present in the ObserverVec. For
151// unpartitioned observations, use an ObserverVec with zero labels. Note that
152// partitioning of Histograms is expensive and should be used judiciously.
khenaidooab1f7bd2019-11-14 14:00:27 -0500153//
154// If the wrapped Handler does not set a status code, a status code of 200 is assumed.
155//
156// If the wrapped Handler panics, no values are reported.
157//
158// See the example for InstrumentHandlerDuration for example usage.
159func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc {
160 code, method := checkLabels(obs)
161
162 if code {
163 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
164 d := newDelegator(w, nil)
165 next.ServeHTTP(d, r)
166 size := computeApproximateRequestSize(r)
167 obs.With(labels(code, method, r.Method, d.Status())).Observe(float64(size))
168 })
169 }
170
171 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
172 next.ServeHTTP(w, r)
173 size := computeApproximateRequestSize(r)
174 obs.With(labels(code, method, r.Method, 0)).Observe(float64(size))
175 })
176}
177
178// InstrumentHandlerResponseSize is a middleware that wraps the provided
khenaidood948f772021-08-11 17:49:24 -0400179// http.Handler to observe the response size with the provided ObserverVec. The
180// ObserverVec must have valid metric and label names and must have zero, one,
181// or two non-const non-curried labels. For those, the only allowed label names
182// are "code" and "method". The function panics otherwise. The Observe method of
183// the Observer in the ObserverVec is called with the response size in
184// bytes. Partitioning happens by HTTP status code and/or HTTP method if the
185// respective instance label names are present in the ObserverVec. For
186// unpartitioned observations, use an ObserverVec with zero labels. Note that
187// partitioning of Histograms is expensive and should be used judiciously.
khenaidooab1f7bd2019-11-14 14:00:27 -0500188//
189// If the wrapped Handler does not set a status code, a status code of 200 is assumed.
190//
191// If the wrapped Handler panics, no values are reported.
192//
193// See the example for InstrumentHandlerDuration for example usage.
194func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler) http.Handler {
195 code, method := checkLabels(obs)
196 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
197 d := newDelegator(w, nil)
198 next.ServeHTTP(d, r)
199 obs.With(labels(code, method, r.Method, d.Status())).Observe(float64(d.Written()))
200 })
201}
202
khenaidood948f772021-08-11 17:49:24 -0400203// checkLabels returns whether the provided Collector has a non-const,
204// non-curried label named "code" and/or "method". It panics if the provided
205// Collector does not have a Desc or has more than one Desc or its Desc is
206// invalid. It also panics if the Collector has any non-const, non-curried
207// labels that are not named "code" or "method".
khenaidooab1f7bd2019-11-14 14:00:27 -0500208func checkLabels(c prometheus.Collector) (code bool, method bool) {
209 // TODO(beorn7): Remove this hacky way to check for instance labels
210 // once Descriptors can have their dimensionality queried.
211 var (
212 desc *prometheus.Desc
213 m prometheus.Metric
214 pm dto.Metric
215 lvs []string
216 )
217
218 // Get the Desc from the Collector.
219 descc := make(chan *prometheus.Desc, 1)
220 c.Describe(descc)
221
222 select {
223 case desc = <-descc:
224 default:
225 panic("no description provided by collector")
226 }
227 select {
228 case <-descc:
229 panic("more than one description provided by collector")
230 default:
231 }
232
233 close(descc)
234
khenaidood948f772021-08-11 17:49:24 -0400235 // Make sure the Collector has a valid Desc by registering it with a
236 // temporary registry.
237 prometheus.NewRegistry().MustRegister(c)
238
khenaidooab1f7bd2019-11-14 14:00:27 -0500239 // Create a ConstMetric with the Desc. Since we don't know how many
240 // variable labels there are, try for as long as it needs.
241 for err := errors.New("dummy"); err != nil; lvs = append(lvs, magicString) {
242 m, err = prometheus.NewConstMetric(desc, prometheus.UntypedValue, 0, lvs...)
243 }
244
245 // Write out the metric into a proto message and look at the labels.
246 // If the value is not the magicString, it is a constLabel, which doesn't interest us.
247 // If the label is curried, it doesn't interest us.
248 // In all other cases, only "code" or "method" is allowed.
249 if err := m.Write(&pm); err != nil {
250 panic("error checking metric for labels")
251 }
252 for _, label := range pm.Label {
253 name, value := label.GetName(), label.GetValue()
254 if value != magicString || isLabelCurried(c, name) {
255 continue
256 }
257 switch name {
258 case "code":
259 code = true
260 case "method":
261 method = true
262 default:
263 panic("metric partitioned with non-supported labels")
264 }
265 }
266 return
267}
268
269func isLabelCurried(c prometheus.Collector, label string) bool {
270 // This is even hackier than the label test above.
271 // We essentially try to curry again and see if it works.
272 // But for that, we need to type-convert to the two
273 // types we use here, ObserverVec or *CounterVec.
274 switch v := c.(type) {
275 case *prometheus.CounterVec:
276 if _, err := v.CurryWith(prometheus.Labels{label: "dummy"}); err == nil {
277 return false
278 }
279 case prometheus.ObserverVec:
280 if _, err := v.CurryWith(prometheus.Labels{label: "dummy"}); err == nil {
281 return false
282 }
283 default:
284 panic("unsupported metric vec type")
285 }
286 return true
287}
288
289// emptyLabels is a one-time allocation for non-partitioned metrics to avoid
290// unnecessary allocations on each request.
291var emptyLabels = prometheus.Labels{}
292
293func labels(code, method bool, reqMethod string, status int) prometheus.Labels {
294 if !(code || method) {
295 return emptyLabels
296 }
297 labels := prometheus.Labels{}
298
299 if code {
300 labels["code"] = sanitizeCode(status)
301 }
302 if method {
303 labels["method"] = sanitizeMethod(reqMethod)
304 }
305
306 return labels
307}
308
309func computeApproximateRequestSize(r *http.Request) int {
310 s := 0
311 if r.URL != nil {
312 s += len(r.URL.String())
313 }
314
315 s += len(r.Method)
316 s += len(r.Proto)
317 for name, values := range r.Header {
318 s += len(name)
319 for _, value := range values {
320 s += len(value)
321 }
322 }
323 s += len(r.Host)
324
325 // N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.
326
327 if r.ContentLength != -1 {
328 s += int(r.ContentLength)
329 }
330 return s
331}
332
333func sanitizeMethod(m string) string {
334 switch m {
335 case "GET", "get":
336 return "get"
337 case "PUT", "put":
338 return "put"
339 case "HEAD", "head":
340 return "head"
341 case "POST", "post":
342 return "post"
343 case "DELETE", "delete":
344 return "delete"
345 case "CONNECT", "connect":
346 return "connect"
347 case "OPTIONS", "options":
348 return "options"
349 case "NOTIFY", "notify":
350 return "notify"
351 default:
352 return strings.ToLower(m)
353 }
354}
355
356// If the wrapped http.Handler has not set a status code, i.e. the value is
357// currently 0, santizeCode will return 200, for consistency with behavior in
358// the stdlib.
359func sanitizeCode(s int) string {
360 switch s {
361 case 100:
362 return "100"
363 case 101:
364 return "101"
365
366 case 200, 0:
367 return "200"
368 case 201:
369 return "201"
370 case 202:
371 return "202"
372 case 203:
373 return "203"
374 case 204:
375 return "204"
376 case 205:
377 return "205"
378 case 206:
379 return "206"
380
381 case 300:
382 return "300"
383 case 301:
384 return "301"
385 case 302:
386 return "302"
387 case 304:
388 return "304"
389 case 305:
390 return "305"
391 case 307:
392 return "307"
393
394 case 400:
395 return "400"
396 case 401:
397 return "401"
398 case 402:
399 return "402"
400 case 403:
401 return "403"
402 case 404:
403 return "404"
404 case 405:
405 return "405"
406 case 406:
407 return "406"
408 case 407:
409 return "407"
410 case 408:
411 return "408"
412 case 409:
413 return "409"
414 case 410:
415 return "410"
416 case 411:
417 return "411"
418 case 412:
419 return "412"
420 case 413:
421 return "413"
422 case 414:
423 return "414"
424 case 415:
425 return "415"
426 case 416:
427 return "416"
428 case 417:
429 return "417"
430 case 418:
431 return "418"
432
433 case 500:
434 return "500"
435 case 501:
436 return "501"
437 case 502:
438 return "502"
439 case 503:
440 return "503"
441 case 504:
442 return "504"
443 case 505:
444 return "505"
445
446 case 428:
447 return "428"
448 case 429:
449 return "429"
450 case 431:
451 return "431"
452 case 511:
453 return "511"
454
455 default:
456 return strconv.Itoa(s)
457 }
458}