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.

instrument_client.go 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  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 RoundTripperFunc(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. Partitioning of the CounterVec happens by HTTP status code
  46. // and/or HTTP method if the respective instance label names are present in the
  47. // CounterVec. For unpartitioned counting, use a CounterVec with zero labels.
  48. //
  49. // If the wrapped RoundTripper panics or returns a non-nil error, the Counter
  50. // is not incremented.
  51. //
  52. // See the example for ExampleInstrumentRoundTripperDuration for example usage.
  53. func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.RoundTripper) RoundTripperFunc {
  54. code, method := checkLabels(counter)
  55. return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
  56. resp, err := next.RoundTrip(r)
  57. if err == nil {
  58. counter.With(labels(code, method, r.Method, resp.StatusCode)).Inc()
  59. }
  60. return resp, err
  61. })
  62. }
  63. // InstrumentRoundTripperDuration is a middleware that wraps the provided
  64. // http.RoundTripper to observe the request duration with the provided
  65. // ObserverVec. The ObserverVec must have zero, one, or two non-const
  66. // non-curried labels. For those, the only allowed label names are "code" and
  67. // "method". The function panics otherwise. The Observe method of the Observer
  68. // in the ObserverVec is called with the request duration in
  69. // seconds. Partitioning happens by HTTP status code and/or HTTP method if the
  70. // respective instance label names are present in the ObserverVec. For
  71. // unpartitioned observations, use an ObserverVec with zero labels. Note that
  72. // partitioning of Histograms is expensive and should be used judiciously.
  73. //
  74. // If the wrapped RoundTripper panics or returns a non-nil error, no values are
  75. // reported.
  76. //
  77. // Note that this method is only guaranteed to never observe negative durations
  78. // if used with Go1.9+.
  79. func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundTripper) RoundTripperFunc {
  80. code, method := checkLabels(obs)
  81. return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
  82. start := time.Now()
  83. resp, err := next.RoundTrip(r)
  84. if err == nil {
  85. obs.With(labels(code, method, r.Method, resp.StatusCode)).Observe(time.Since(start).Seconds())
  86. }
  87. return resp, err
  88. })
  89. }
  90. // InstrumentTrace is used to offer flexibility in instrumenting the available
  91. // httptrace.ClientTrace hook functions. Each function is passed a float64
  92. // representing the time in seconds since the start of the http request. A user
  93. // may choose to use separately buckets Histograms, or implement custom
  94. // instance labels on a per function basis.
  95. type InstrumentTrace struct {
  96. GotConn func(float64)
  97. PutIdleConn func(float64)
  98. GotFirstResponseByte func(float64)
  99. Got100Continue func(float64)
  100. DNSStart func(float64)
  101. DNSDone func(float64)
  102. ConnectStart func(float64)
  103. ConnectDone func(float64)
  104. TLSHandshakeStart func(float64)
  105. TLSHandshakeDone func(float64)
  106. WroteHeaders func(float64)
  107. Wait100Continue func(float64)
  108. WroteRequest func(float64)
  109. }
  110. // InstrumentRoundTripperTrace is a middleware that wraps the provided
  111. // RoundTripper and reports times to hook functions provided in the
  112. // InstrumentTrace struct. Hook functions that are not present in the provided
  113. // InstrumentTrace struct are ignored. Times reported to the hook functions are
  114. // time since the start of the request. Only with Go1.9+, those times are
  115. // guaranteed to never be negative. (Earlier Go versions are not using a
  116. // monotonic clock.) Note that partitioning of Histograms is expensive and
  117. // should be used judiciously.
  118. //
  119. // For hook functions that receive an error as an argument, no observations are
  120. // made in the event of a non-nil error value.
  121. //
  122. // See the example for ExampleInstrumentRoundTripperDuration for example usage.
  123. func InstrumentRoundTripperTrace(it *InstrumentTrace, next http.RoundTripper) RoundTripperFunc {
  124. return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
  125. start := time.Now()
  126. trace := &httptrace.ClientTrace{
  127. GotConn: func(_ httptrace.GotConnInfo) {
  128. if it.GotConn != nil {
  129. it.GotConn(time.Since(start).Seconds())
  130. }
  131. },
  132. PutIdleConn: func(err error) {
  133. if err != nil {
  134. return
  135. }
  136. if it.PutIdleConn != nil {
  137. it.PutIdleConn(time.Since(start).Seconds())
  138. }
  139. },
  140. DNSStart: func(_ httptrace.DNSStartInfo) {
  141. if it.DNSStart != nil {
  142. it.DNSStart(time.Since(start).Seconds())
  143. }
  144. },
  145. DNSDone: func(_ httptrace.DNSDoneInfo) {
  146. if it.DNSDone != nil {
  147. it.DNSDone(time.Since(start).Seconds())
  148. }
  149. },
  150. ConnectStart: func(_, _ string) {
  151. if it.ConnectStart != nil {
  152. it.ConnectStart(time.Since(start).Seconds())
  153. }
  154. },
  155. ConnectDone: func(_, _ string, err error) {
  156. if err != nil {
  157. return
  158. }
  159. if it.ConnectDone != nil {
  160. it.ConnectDone(time.Since(start).Seconds())
  161. }
  162. },
  163. GotFirstResponseByte: func() {
  164. if it.GotFirstResponseByte != nil {
  165. it.GotFirstResponseByte(time.Since(start).Seconds())
  166. }
  167. },
  168. Got100Continue: func() {
  169. if it.Got100Continue != nil {
  170. it.Got100Continue(time.Since(start).Seconds())
  171. }
  172. },
  173. TLSHandshakeStart: func() {
  174. if it.TLSHandshakeStart != nil {
  175. it.TLSHandshakeStart(time.Since(start).Seconds())
  176. }
  177. },
  178. TLSHandshakeDone: func(_ tls.ConnectionState, err error) {
  179. if err != nil {
  180. return
  181. }
  182. if it.TLSHandshakeDone != nil {
  183. it.TLSHandshakeDone(time.Since(start).Seconds())
  184. }
  185. },
  186. WroteHeaders: func() {
  187. if it.WroteHeaders != nil {
  188. it.WroteHeaders(time.Since(start).Seconds())
  189. }
  190. },
  191. Wait100Continue: func() {
  192. if it.Wait100Continue != nil {
  193. it.Wait100Continue(time.Since(start).Seconds())
  194. }
  195. },
  196. WroteRequest: func(_ httptrace.WroteRequestInfo) {
  197. if it.WroteRequest != nil {
  198. it.WroteRequest(time.Since(start).Seconds())
  199. }
  200. },
  201. }
  202. r = r.WithContext(httptrace.WithClientTrace(r.Context(), trace))
  203. return next.RoundTrip(r)
  204. })
  205. }