instrument_server.go 13 KB

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