github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/prometheus/promhttp/instrument_server.go (about)

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