You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

250 lines
9.2 KiB

  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. package promhttp
  14. import (
  15. "crypto/tls"
  16. "net/http"
  17. "net/http/httptrace"
  18. "time"
  19. "github.com/prometheus/client_golang/prometheus"
  20. )
  21. // The RoundTripperFunc type is an adapter to allow the use of ordinary
  22. // functions as RoundTrippers. If f is a function with the appropriate
  23. // signature, RountTripperFunc(f) is a RoundTripper that calls f.
  24. type RoundTripperFunc func(req *http.Request) (*http.Response, error)
  25. // RoundTrip implements the RoundTripper interface.
  26. func (rt RoundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) {
  27. return rt(r)
  28. }
  29. // InstrumentRoundTripperInFlight is a middleware that wraps the provided
  30. // http.RoundTripper. It sets the provided prometheus.Gauge to the number of
  31. // requests currently handled by the wrapped http.RoundTripper.
  32. //
  33. // See the example for ExampleInstrumentRoundTripperDuration for example usage.
  34. func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripper) RoundTripperFunc {
  35. return func(r *http.Request) (*http.Response, error) {
  36. gauge.Inc()
  37. defer gauge.Dec()
  38. return next.RoundTrip(r)
  39. }
  40. }
  41. // InstrumentRoundTripperCounter is a middleware that wraps the provided
  42. // http.RoundTripper to observe the request result with the provided CounterVec.
  43. // The CounterVec must have zero, one, or two non-const non-curried labels. For
  44. // those, the only allowed label names are "code" and "method". The function
  45. // panics otherwise. For the "method" label a predefined default label value set
  46. // is used to filter given values. Values besides predefined values will count
  47. // as `unknown` method.`WithExtraMethods` can be used to add more
  48. // methods to the set. Partitioning of the CounterVec happens by HTTP status code
  49. // and/or HTTP method if the respective instance label names are present in the
  50. // CounterVec. For unpartitioned counting, use a CounterVec with zero labels.
  51. //
  52. // If the wrapped RoundTripper panics or returns a non-nil error, the Counter
  53. // is not incremented.
  54. //
  55. // Use with WithExemplarFromContext to instrument the exemplars on the counter of requests.
  56. //
  57. // See the example for ExampleInstrumentRoundTripperDuration for example usage.
  58. func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.RoundTripper, opts ...Option) RoundTripperFunc {
  59. rtOpts := defaultOptions()
  60. for _, o := range opts {
  61. o.apply(rtOpts)
  62. }
  63. // Curry the counter with dynamic labels before checking the remaining labels.
  64. code, method := checkLabels(counter.MustCurryWith(rtOpts.emptyDynamicLabels()))
  65. return func(r *http.Request) (*http.Response, error) {
  66. resp, err := next.RoundTrip(r)
  67. if err == nil {
  68. l := labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)
  69. for label, resolve := range rtOpts.extraLabelsFromCtx {
  70. l[label] = resolve(resp.Request.Context())
  71. }
  72. addWithExemplar(counter.With(l), 1, rtOpts.getExemplarFn(r.Context()))
  73. }
  74. return resp, err
  75. }
  76. }
  77. // InstrumentRoundTripperDuration is a middleware that wraps the provided
  78. // http.RoundTripper to observe the request duration with the provided
  79. // ObserverVec. The ObserverVec must have zero, one, or two non-const
  80. // non-curried labels. For those, the only allowed label names are "code" and
  81. // "method". The function panics otherwise. For the "method" label a predefined
  82. // default label value set is used to filter given values. Values besides
  83. // predefined values will count as `unknown` method. `WithExtraMethods`
  84. // can be used to add more methods to the set. The Observe method of the Observer
  85. // in the ObserverVec is called with the request duration in
  86. // seconds. Partitioning happens by HTTP status code and/or HTTP method if the
  87. // respective instance label names are present in the ObserverVec. For
  88. // unpartitioned observations, use an ObserverVec with zero labels. Note that
  89. // partitioning of Histograms is expensive and should be used judiciously.
  90. //
  91. // If the wrapped RoundTripper panics or returns a non-nil error, no values are
  92. // reported.
  93. //
  94. // Use with WithExemplarFromContext to instrument the exemplars on the duration histograms.
  95. //
  96. // Note that this method is only guaranteed to never observe negative durations
  97. // if used with Go1.9+.
  98. func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundTripper, opts ...Option) RoundTripperFunc {
  99. rtOpts := defaultOptions()
  100. for _, o := range opts {
  101. o.apply(rtOpts)
  102. }
  103. // Curry the observer with dynamic labels before checking the remaining labels.
  104. code, method := checkLabels(obs.MustCurryWith(rtOpts.emptyDynamicLabels()))
  105. return func(r *http.Request) (*http.Response, error) {
  106. start := time.Now()
  107. resp, err := next.RoundTrip(r)
  108. if err == nil {
  109. l := labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)
  110. for label, resolve := range rtOpts.extraLabelsFromCtx {
  111. l[label] = resolve(resp.Request.Context())
  112. }
  113. observeWithExemplar(obs.With(l), time.Since(start).Seconds(), rtOpts.getExemplarFn(r.Context()))
  114. }
  115. return resp, err
  116. }
  117. }
  118. // InstrumentTrace is used to offer flexibility in instrumenting the available
  119. // httptrace.ClientTrace hook functions. Each function is passed a float64
  120. // representing the time in seconds since the start of the http request. A user
  121. // may choose to use separately buckets Histograms, or implement custom
  122. // instance labels on a per function basis.
  123. type InstrumentTrace struct {
  124. GotConn func(float64)
  125. PutIdleConn func(float64)
  126. GotFirstResponseByte func(float64)
  127. Got100Continue func(float64)
  128. DNSStart func(float64)
  129. DNSDone func(float64)
  130. ConnectStart func(float64)
  131. ConnectDone func(float64)
  132. TLSHandshakeStart func(float64)
  133. TLSHandshakeDone func(float64)
  134. WroteHeaders func(float64)
  135. Wait100Continue func(float64)
  136. WroteRequest func(float64)
  137. }
  138. // InstrumentRoundTripperTrace is a middleware that wraps the provided
  139. // RoundTripper and reports times to hook functions provided in the
  140. // InstrumentTrace struct. Hook functions that are not present in the provided
  141. // InstrumentTrace struct are ignored. Times reported to the hook functions are
  142. // time since the start of the request. Only with Go1.9+, those times are
  143. // guaranteed to never be negative. (Earlier Go versions are not using a
  144. // monotonic clock.) Note that partitioning of Histograms is expensive and
  145. // should be used judiciously.
  146. //
  147. // For hook functions that receive an error as an argument, no observations are
  148. // made in the event of a non-nil error value.
  149. //
  150. // See the example for ExampleInstrumentRoundTripperDuration for example usage.
  151. func InstrumentRoundTripperTrace(it *InstrumentTrace, next http.RoundTripper) RoundTripperFunc {
  152. return func(r *http.Request) (*http.Response, error) {
  153. start := time.Now()
  154. trace := &httptrace.ClientTrace{
  155. GotConn: func(_ httptrace.GotConnInfo) {
  156. if it.GotConn != nil {
  157. it.GotConn(time.Since(start).Seconds())
  158. }
  159. },
  160. PutIdleConn: func(err error) {
  161. if err != nil {
  162. return
  163. }
  164. if it.PutIdleConn != nil {
  165. it.PutIdleConn(time.Since(start).Seconds())
  166. }
  167. },
  168. DNSStart: func(_ httptrace.DNSStartInfo) {
  169. if it.DNSStart != nil {
  170. it.DNSStart(time.Since(start).Seconds())
  171. }
  172. },
  173. DNSDone: func(_ httptrace.DNSDoneInfo) {
  174. if it.DNSDone != nil {
  175. it.DNSDone(time.Since(start).Seconds())
  176. }
  177. },
  178. ConnectStart: func(_, _ string) {
  179. if it.ConnectStart != nil {
  180. it.ConnectStart(time.Since(start).Seconds())
  181. }
  182. },
  183. ConnectDone: func(_, _ string, err error) {
  184. if err != nil {
  185. return
  186. }
  187. if it.ConnectDone != nil {
  188. it.ConnectDone(time.Since(start).Seconds())
  189. }
  190. },
  191. GotFirstResponseByte: func() {
  192. if it.GotFirstResponseByte != nil {
  193. it.GotFirstResponseByte(time.Since(start).Seconds())
  194. }
  195. },
  196. Got100Continue: func() {
  197. if it.Got100Continue != nil {
  198. it.Got100Continue(time.Since(start).Seconds())
  199. }
  200. },
  201. TLSHandshakeStart: func() {
  202. if it.TLSHandshakeStart != nil {
  203. it.TLSHandshakeStart(time.Since(start).Seconds())
  204. }
  205. },
  206. TLSHandshakeDone: func(_ tls.ConnectionState, err error) {
  207. if err != nil {
  208. return
  209. }
  210. if it.TLSHandshakeDone != nil {
  211. it.TLSHandshakeDone(time.Since(start).Seconds())
  212. }
  213. },
  214. WroteHeaders: func() {
  215. if it.WroteHeaders != nil {
  216. it.WroteHeaders(time.Since(start).Seconds())
  217. }
  218. },
  219. Wait100Continue: func() {
  220. if it.Wait100Continue != nil {
  221. it.Wait100Continue(time.Since(start).Seconds())
  222. }
  223. },
  224. WroteRequest: func(_ httptrace.WroteRequestInfo) {
  225. if it.WroteRequest != nil {
  226. it.WroteRequest(time.Since(start).Seconds())
  227. }
  228. },
  229. }
  230. r = r.WithContext(httptrace.WithClientTrace(r.Context(), trace))
  231. return next.RoundTrip(r)
  232. }
  233. }