123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447 |
- // Copyright 2017 The Prometheus Authors
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package promhttp
- import (
- "errors"
- "net/http"
- "strconv"
- "strings"
- "time"
- dto "github.com/prometheus/client_model/go"
- "github.com/prometheus/client_golang/prometheus"
- )
- // magicString is used for the hacky label test in checkLabels. Remove once fixed.
- const magicString = "zZgWfBxLqvG8kc8IMv3POi2Bb0tZI3vAnBx+gBaFi9FyPzB/CzKUer1yufDa"
- // InstrumentHandlerInFlight is a middleware that wraps the provided
- // http.Handler. It sets the provided prometheus.Gauge to the number of
- // requests currently handled by the wrapped http.Handler.
- //
- // See the example for InstrumentHandlerDuration for example usage.
- func InstrumentHandlerInFlight(g prometheus.Gauge, next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- g.Inc()
- defer g.Dec()
- next.ServeHTTP(w, r)
- })
- }
- // InstrumentHandlerDuration is a middleware that wraps the provided
- // http.Handler to observe the request duration with the provided ObserverVec.
- // The ObserverVec must have zero, one, or two non-const non-curried labels. For
- // those, the only allowed label names are "code" and "method". The function
- // panics otherwise. The Observe method of the Observer in the ObserverVec is
- // called with the request duration in seconds. Partitioning happens by HTTP
- // status code and/or HTTP method if the respective instance label names are
- // present in the ObserverVec. For unpartitioned observations, use an
- // ObserverVec with zero labels. Note that partitioning of Histograms is
- // expensive and should be used judiciously.
- //
- // If the wrapped Handler does not set a status code, a status code of 200 is assumed.
- //
- // If the wrapped Handler panics, no values are reported.
- //
- // Note that this method is only guaranteed to never observe negative durations
- // if used with Go1.9+.
- func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc {
- code, method := checkLabels(obs)
- if code {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- now := time.Now()
- d := newDelegator(w, nil)
- next.ServeHTTP(d, r)
- obs.With(labels(code, method, r.Method, d.Status())).Observe(time.Since(now).Seconds())
- })
- }
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- now := time.Now()
- next.ServeHTTP(w, r)
- obs.With(labels(code, method, r.Method, 0)).Observe(time.Since(now).Seconds())
- })
- }
- // InstrumentHandlerCounter is a middleware that wraps the provided http.Handler
- // to observe the request result with the provided CounterVec. The CounterVec
- // must have zero, one, or two non-const non-curried labels. For those, the only
- // allowed label names are "code" and "method". The function panics
- // otherwise. Partitioning of the CounterVec happens by HTTP status code and/or
- // HTTP method if the respective instance label names are present in the
- // CounterVec. For unpartitioned counting, use a CounterVec with zero labels.
- //
- // If the wrapped Handler does not set a status code, a status code of 200 is assumed.
- //
- // If the wrapped Handler panics, the Counter is not incremented.
- //
- // See the example for InstrumentHandlerDuration for example usage.
- func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler) http.HandlerFunc {
- code, method := checkLabels(counter)
- if code {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- d := newDelegator(w, nil)
- next.ServeHTTP(d, r)
- counter.With(labels(code, method, r.Method, d.Status())).Inc()
- })
- }
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- next.ServeHTTP(w, r)
- counter.With(labels(code, method, r.Method, 0)).Inc()
- })
- }
- // InstrumentHandlerTimeToWriteHeader is a middleware that wraps the provided
- // http.Handler to observe with the provided ObserverVec the request duration
- // until the response headers are written. The ObserverVec must have zero, one,
- // or two non-const non-curried labels. For those, the only allowed label names
- // are "code" and "method". The function panics otherwise. The Observe method of
- // the Observer in the ObserverVec is called with the request duration in
- // seconds. Partitioning happens by HTTP status code and/or HTTP method if the
- // respective instance label names are present in the ObserverVec. For
- // unpartitioned observations, use an ObserverVec with zero labels. Note that
- // partitioning of Histograms is expensive and should be used judiciously.
- //
- // If the wrapped Handler panics before calling WriteHeader, no value is
- // reported.
- //
- // Note that this method is only guaranteed to never observe negative durations
- // if used with Go1.9+.
- //
- // See the example for InstrumentHandlerDuration for example usage.
- func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc {
- code, method := checkLabels(obs)
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- now := time.Now()
- d := newDelegator(w, func(status int) {
- obs.With(labels(code, method, r.Method, status)).Observe(time.Since(now).Seconds())
- })
- next.ServeHTTP(d, r)
- })
- }
- // InstrumentHandlerRequestSize is a middleware that wraps the provided
- // http.Handler to observe the request size with the provided ObserverVec. The
- // ObserverVec must have zero, one, or two non-const non-curried labels. For
- // those, the only allowed label names are "code" and "method". The function
- // panics otherwise. The Observe method of the Observer in the ObserverVec is
- // called with the request size in bytes. Partitioning happens by HTTP status
- // code and/or HTTP method if the respective instance label names are present in
- // the ObserverVec. For unpartitioned observations, use an ObserverVec with zero
- // labels. Note that partitioning of Histograms is expensive and should be used
- // judiciously.
- //
- // If the wrapped Handler does not set a status code, a status code of 200 is assumed.
- //
- // If the wrapped Handler panics, no values are reported.
- //
- // See the example for InstrumentHandlerDuration for example usage.
- func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc {
- code, method := checkLabels(obs)
- if code {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- d := newDelegator(w, nil)
- next.ServeHTTP(d, r)
- size := computeApproximateRequestSize(r)
- obs.With(labels(code, method, r.Method, d.Status())).Observe(float64(size))
- })
- }
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- next.ServeHTTP(w, r)
- size := computeApproximateRequestSize(r)
- obs.With(labels(code, method, r.Method, 0)).Observe(float64(size))
- })
- }
- // InstrumentHandlerResponseSize is a middleware that wraps the provided
- // http.Handler to observe the response size with the provided ObserverVec. The
- // ObserverVec must have zero, one, or two non-const non-curried labels. For
- // those, the only allowed label names are "code" and "method". The function
- // panics otherwise. The Observe method of the Observer in the ObserverVec is
- // called with the response size in bytes. Partitioning happens by HTTP status
- // code and/or HTTP method if the respective instance label names are present in
- // the ObserverVec. For unpartitioned observations, use an ObserverVec with zero
- // labels. Note that partitioning of Histograms is expensive and should be used
- // judiciously.
- //
- // If the wrapped Handler does not set a status code, a status code of 200 is assumed.
- //
- // If the wrapped Handler panics, no values are reported.
- //
- // See the example for InstrumentHandlerDuration for example usage.
- func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler) http.Handler {
- code, method := checkLabels(obs)
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- d := newDelegator(w, nil)
- next.ServeHTTP(d, r)
- obs.With(labels(code, method, r.Method, d.Status())).Observe(float64(d.Written()))
- })
- }
- func checkLabels(c prometheus.Collector) (code bool, method bool) {
- // TODO(beorn7): Remove this hacky way to check for instance labels
- // once Descriptors can have their dimensionality queried.
- var (
- desc *prometheus.Desc
- m prometheus.Metric
- pm dto.Metric
- lvs []string
- )
- // Get the Desc from the Collector.
- descc := make(chan *prometheus.Desc, 1)
- c.Describe(descc)
- select {
- case desc = <-descc:
- default:
- panic("no description provided by collector")
- }
- select {
- case <-descc:
- panic("more than one description provided by collector")
- default:
- }
- close(descc)
- // Create a ConstMetric with the Desc. Since we don't know how many
- // variable labels there are, try for as long as it needs.
- for err := errors.New("dummy"); err != nil; lvs = append(lvs, magicString) {
- m, err = prometheus.NewConstMetric(desc, prometheus.UntypedValue, 0, lvs...)
- }
- // Write out the metric into a proto message and look at the labels.
- // If the value is not the magicString, it is a constLabel, which doesn't interest us.
- // If the label is curried, it doesn't interest us.
- // In all other cases, only "code" or "method" is allowed.
- if err := m.Write(&pm); err != nil {
- panic("error checking metric for labels")
- }
- for _, label := range pm.Label {
- name, value := label.GetName(), label.GetValue()
- if value != magicString || isLabelCurried(c, name) {
- continue
- }
- switch name {
- case "code":
- code = true
- case "method":
- method = true
- default:
- panic("metric partitioned with non-supported labels")
- }
- }
- return
- }
- func isLabelCurried(c prometheus.Collector, label string) bool {
- // This is even hackier than the label test above.
- // We essentially try to curry again and see if it works.
- // But for that, we need to type-convert to the two
- // types we use here, ObserverVec or *CounterVec.
- switch v := c.(type) {
- case *prometheus.CounterVec:
- if _, err := v.CurryWith(prometheus.Labels{label: "dummy"}); err == nil {
- return false
- }
- case prometheus.ObserverVec:
- if _, err := v.CurryWith(prometheus.Labels{label: "dummy"}); err == nil {
- return false
- }
- default:
- panic("unsupported metric vec type")
- }
- return true
- }
- // emptyLabels is a one-time allocation for non-partitioned metrics to avoid
- // unnecessary allocations on each request.
- var emptyLabels = prometheus.Labels{}
- func labels(code, method bool, reqMethod string, status int) prometheus.Labels {
- if !(code || method) {
- return emptyLabels
- }
- labels := prometheus.Labels{}
- if code {
- labels["code"] = sanitizeCode(status)
- }
- if method {
- labels["method"] = sanitizeMethod(reqMethod)
- }
- return labels
- }
- func computeApproximateRequestSize(r *http.Request) int {
- s := 0
- if r.URL != nil {
- s += len(r.URL.String())
- }
- s += len(r.Method)
- s += len(r.Proto)
- for name, values := range r.Header {
- s += len(name)
- for _, value := range values {
- s += len(value)
- }
- }
- s += len(r.Host)
- // N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.
- if r.ContentLength != -1 {
- s += int(r.ContentLength)
- }
- return s
- }
- func sanitizeMethod(m string) string {
- switch m {
- case "GET", "get":
- return "get"
- case "PUT", "put":
- return "put"
- case "HEAD", "head":
- return "head"
- case "POST", "post":
- return "post"
- case "DELETE", "delete":
- return "delete"
- case "CONNECT", "connect":
- return "connect"
- case "OPTIONS", "options":
- return "options"
- case "NOTIFY", "notify":
- return "notify"
- default:
- return strings.ToLower(m)
- }
- }
- // If the wrapped http.Handler has not set a status code, i.e. the value is
- // currently 0, santizeCode will return 200, for consistency with behavior in
- // the stdlib.
- func sanitizeCode(s int) string {
- switch s {
- case 100:
- return "100"
- case 101:
- return "101"
- case 200, 0:
- return "200"
- case 201:
- return "201"
- case 202:
- return "202"
- case 203:
- return "203"
- case 204:
- return "204"
- case 205:
- return "205"
- case 206:
- return "206"
- case 300:
- return "300"
- case 301:
- return "301"
- case 302:
- return "302"
- case 304:
- return "304"
- case 305:
- return "305"
- case 307:
- return "307"
- case 400:
- return "400"
- case 401:
- return "401"
- case 402:
- return "402"
- case 403:
- return "403"
- case 404:
- return "404"
- case 405:
- return "405"
- case 406:
- return "406"
- case 407:
- return "407"
- case 408:
- return "408"
- case 409:
- return "409"
- case 410:
- return "410"
- case 411:
- return "411"
- case 412:
- return "412"
- case 413:
- return "413"
- case 414:
- return "414"
- case 415:
- return "415"
- case 416:
- return "416"
- case 417:
- return "417"
- case 418:
- return "418"
- case 500:
- return "500"
- case 501:
- return "501"
- case 502:
- return "502"
- case 503:
- return "503"
- case 504:
- return "504"
- case 505:
- return "505"
- case 428:
- return "428"
- case 429:
- return "429"
- case 431:
- return "431"
- case 511:
- return "511"
- default:
- return strconv.Itoa(s)
- }
- }
|