blob: 9f0875bfc811d4199e968377a814d53194ce3d32 [file] [log] [blame]
khenaidooffe076b2019-01-15 16:08:08 -05001// Copyright 2014 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
14package prometheus
15
16import (
17 "bufio"
18 "compress/gzip"
19 "io"
20 "net"
21 "net/http"
22 "strconv"
23 "strings"
24 "sync"
25 "time"
26
27 "github.com/prometheus/common/expfmt"
28)
29
30// TODO(beorn7): Remove this whole file. It is a partial mirror of
31// promhttp/http.go (to avoid circular import chains) where everything HTTP
32// related should live. The functions here are just for avoiding
33// breakage. Everything is deprecated.
34
35const (
36 contentTypeHeader = "Content-Type"
37 contentLengthHeader = "Content-Length"
38 contentEncodingHeader = "Content-Encoding"
39 acceptEncodingHeader = "Accept-Encoding"
40)
41
42var gzipPool = sync.Pool{
43 New: func() interface{} {
44 return gzip.NewWriter(nil)
45 },
46}
47
48// Handler returns an HTTP handler for the DefaultGatherer. It is
49// already instrumented with InstrumentHandler (using "prometheus" as handler
50// name).
51//
52// Deprecated: Please note the issues described in the doc comment of
53// InstrumentHandler. You might want to consider using promhttp.Handler instead.
54func Handler() http.Handler {
55 return InstrumentHandler("prometheus", UninstrumentedHandler())
56}
57
58// UninstrumentedHandler returns an HTTP handler for the DefaultGatherer.
59//
60// Deprecated: Use promhttp.HandlerFor(DefaultGatherer, promhttp.HandlerOpts{})
61// instead. See there for further documentation.
62func UninstrumentedHandler() http.Handler {
63 return http.HandlerFunc(func(rsp http.ResponseWriter, req *http.Request) {
64 mfs, err := DefaultGatherer.Gather()
65 if err != nil {
66 httpError(rsp, err)
67 return
68 }
69
70 contentType := expfmt.Negotiate(req.Header)
71 header := rsp.Header()
72 header.Set(contentTypeHeader, string(contentType))
73
74 w := io.Writer(rsp)
75 if gzipAccepted(req.Header) {
76 header.Set(contentEncodingHeader, "gzip")
77 gz := gzipPool.Get().(*gzip.Writer)
78 defer gzipPool.Put(gz)
79
80 gz.Reset(w)
81 defer gz.Close()
82
83 w = gz
84 }
85
86 enc := expfmt.NewEncoder(w, contentType)
87
88 for _, mf := range mfs {
89 if err := enc.Encode(mf); err != nil {
90 httpError(rsp, err)
91 return
92 }
93 }
94 })
95}
96
97var instLabels = []string{"method", "code"}
98
99type nower interface {
100 Now() time.Time
101}
102
103type nowFunc func() time.Time
104
105func (n nowFunc) Now() time.Time {
106 return n()
107}
108
109var now nower = nowFunc(func() time.Time {
110 return time.Now()
111})
112
113// InstrumentHandler wraps the given HTTP handler for instrumentation. It
114// registers four metric collectors (if not already done) and reports HTTP
115// metrics to the (newly or already) registered collectors: http_requests_total
116// (CounterVec), http_request_duration_microseconds (Summary),
117// http_request_size_bytes (Summary), http_response_size_bytes (Summary). Each
118// has a constant label named "handler" with the provided handlerName as
119// value. http_requests_total is a metric vector partitioned by HTTP method
120// (label name "method") and HTTP status code (label name "code").
121//
122// Deprecated: InstrumentHandler has several issues. Use the tooling provided in
123// package promhttp instead. The issues are the following: (1) It uses Summaries
124// rather than Histograms. Summaries are not useful if aggregation across
125// multiple instances is required. (2) It uses microseconds as unit, which is
126// deprecated and should be replaced by seconds. (3) The size of the request is
127// calculated in a separate goroutine. Since this calculator requires access to
128// the request header, it creates a race with any writes to the header performed
129// during request handling. httputil.ReverseProxy is a prominent example for a
130// handler performing such writes. (4) It has additional issues with HTTP/2, cf.
131// https://github.com/prometheus/client_golang/issues/272.
132func InstrumentHandler(handlerName string, handler http.Handler) http.HandlerFunc {
133 return InstrumentHandlerFunc(handlerName, handler.ServeHTTP)
134}
135
136// InstrumentHandlerFunc wraps the given function for instrumentation. It
137// otherwise works in the same way as InstrumentHandler (and shares the same
138// issues).
139//
140// Deprecated: InstrumentHandlerFunc is deprecated for the same reasons as
141// InstrumentHandler is. Use the tooling provided in package promhttp instead.
142func InstrumentHandlerFunc(handlerName string, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
143 return InstrumentHandlerFuncWithOpts(
144 SummaryOpts{
145 Subsystem: "http",
146 ConstLabels: Labels{"handler": handlerName},
147 Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
148 },
149 handlerFunc,
150 )
151}
152
153// InstrumentHandlerWithOpts works like InstrumentHandler (and shares the same
154// issues) but provides more flexibility (at the cost of a more complex call
155// syntax). As InstrumentHandler, this function registers four metric
156// collectors, but it uses the provided SummaryOpts to create them. However, the
157// fields "Name" and "Help" in the SummaryOpts are ignored. "Name" is replaced
158// by "requests_total", "request_duration_microseconds", "request_size_bytes",
159// and "response_size_bytes", respectively. "Help" is replaced by an appropriate
160// help string. The names of the variable labels of the http_requests_total
161// CounterVec are "method" (get, post, etc.), and "code" (HTTP status code).
162//
163// If InstrumentHandlerWithOpts is called as follows, it mimics exactly the
164// behavior of InstrumentHandler:
165//
166// prometheus.InstrumentHandlerWithOpts(
167// prometheus.SummaryOpts{
168// Subsystem: "http",
169// ConstLabels: prometheus.Labels{"handler": handlerName},
170// },
171// handler,
172// )
173//
174// Technical detail: "requests_total" is a CounterVec, not a SummaryVec, so it
175// cannot use SummaryOpts. Instead, a CounterOpts struct is created internally,
176// and all its fields are set to the equally named fields in the provided
177// SummaryOpts.
178//
179// Deprecated: InstrumentHandlerWithOpts is deprecated for the same reasons as
180// InstrumentHandler is. Use the tooling provided in package promhttp instead.
181func InstrumentHandlerWithOpts(opts SummaryOpts, handler http.Handler) http.HandlerFunc {
182 return InstrumentHandlerFuncWithOpts(opts, handler.ServeHTTP)
183}
184
185// InstrumentHandlerFuncWithOpts works like InstrumentHandlerFunc (and shares
186// the same issues) but provides more flexibility (at the cost of a more complex
187// call syntax). See InstrumentHandlerWithOpts for details how the provided
188// SummaryOpts are used.
189//
190// Deprecated: InstrumentHandlerFuncWithOpts is deprecated for the same reasons
191// as InstrumentHandler is. Use the tooling provided in package promhttp instead.
192func InstrumentHandlerFuncWithOpts(opts SummaryOpts, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
193 reqCnt := NewCounterVec(
194 CounterOpts{
195 Namespace: opts.Namespace,
196 Subsystem: opts.Subsystem,
197 Name: "requests_total",
198 Help: "Total number of HTTP requests made.",
199 ConstLabels: opts.ConstLabels,
200 },
201 instLabels,
202 )
203 if err := Register(reqCnt); err != nil {
204 if are, ok := err.(AlreadyRegisteredError); ok {
205 reqCnt = are.ExistingCollector.(*CounterVec)
206 } else {
207 panic(err)
208 }
209 }
210
211 opts.Name = "request_duration_microseconds"
212 opts.Help = "The HTTP request latencies in microseconds."
213 reqDur := NewSummary(opts)
214 if err := Register(reqDur); err != nil {
215 if are, ok := err.(AlreadyRegisteredError); ok {
216 reqDur = are.ExistingCollector.(Summary)
217 } else {
218 panic(err)
219 }
220 }
221
222 opts.Name = "request_size_bytes"
223 opts.Help = "The HTTP request sizes in bytes."
224 reqSz := NewSummary(opts)
225 if err := Register(reqSz); err != nil {
226 if are, ok := err.(AlreadyRegisteredError); ok {
227 reqSz = are.ExistingCollector.(Summary)
228 } else {
229 panic(err)
230 }
231 }
232
233 opts.Name = "response_size_bytes"
234 opts.Help = "The HTTP response sizes in bytes."
235 resSz := NewSummary(opts)
236 if err := Register(resSz); err != nil {
237 if are, ok := err.(AlreadyRegisteredError); ok {
238 resSz = are.ExistingCollector.(Summary)
239 } else {
240 panic(err)
241 }
242 }
243
244 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
245 now := time.Now()
246
247 delegate := &responseWriterDelegator{ResponseWriter: w}
248 out := computeApproximateRequestSize(r)
249
250 _, cn := w.(http.CloseNotifier)
251 _, fl := w.(http.Flusher)
252 _, hj := w.(http.Hijacker)
253 _, rf := w.(io.ReaderFrom)
254 var rw http.ResponseWriter
255 if cn && fl && hj && rf {
256 rw = &fancyResponseWriterDelegator{delegate}
257 } else {
258 rw = delegate
259 }
260 handlerFunc(rw, r)
261
262 elapsed := float64(time.Since(now)) / float64(time.Microsecond)
263
264 method := sanitizeMethod(r.Method)
265 code := sanitizeCode(delegate.status)
266 reqCnt.WithLabelValues(method, code).Inc()
267 reqDur.Observe(elapsed)
268 resSz.Observe(float64(delegate.written))
269 reqSz.Observe(float64(<-out))
270 })
271}
272
273func computeApproximateRequestSize(r *http.Request) <-chan int {
274 // Get URL length in current goroutine for avoiding a race condition.
275 // HandlerFunc that runs in parallel may modify the URL.
276 s := 0
277 if r.URL != nil {
278 s += len(r.URL.String())
279 }
280
281 out := make(chan int, 1)
282
283 go func() {
284 s += len(r.Method)
285 s += len(r.Proto)
286 for name, values := range r.Header {
287 s += len(name)
288 for _, value := range values {
289 s += len(value)
290 }
291 }
292 s += len(r.Host)
293
294 // N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.
295
296 if r.ContentLength != -1 {
297 s += int(r.ContentLength)
298 }
299 out <- s
300 close(out)
301 }()
302
303 return out
304}
305
306type responseWriterDelegator struct {
307 http.ResponseWriter
308
309 status int
310 written int64
311 wroteHeader bool
312}
313
314func (r *responseWriterDelegator) WriteHeader(code int) {
315 r.status = code
316 r.wroteHeader = true
317 r.ResponseWriter.WriteHeader(code)
318}
319
320func (r *responseWriterDelegator) Write(b []byte) (int, error) {
321 if !r.wroteHeader {
322 r.WriteHeader(http.StatusOK)
323 }
324 n, err := r.ResponseWriter.Write(b)
325 r.written += int64(n)
326 return n, err
327}
328
329type fancyResponseWriterDelegator struct {
330 *responseWriterDelegator
331}
332
333func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool {
334 return f.ResponseWriter.(http.CloseNotifier).CloseNotify()
335}
336
337func (f *fancyResponseWriterDelegator) Flush() {
338 f.ResponseWriter.(http.Flusher).Flush()
339}
340
341func (f *fancyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
342 return f.ResponseWriter.(http.Hijacker).Hijack()
343}
344
345func (f *fancyResponseWriterDelegator) ReadFrom(r io.Reader) (int64, error) {
346 if !f.wroteHeader {
347 f.WriteHeader(http.StatusOK)
348 }
349 n, err := f.ResponseWriter.(io.ReaderFrom).ReadFrom(r)
350 f.written += n
351 return n, err
352}
353
354func sanitizeMethod(m string) string {
355 switch m {
356 case "GET", "get":
357 return "get"
358 case "PUT", "put":
359 return "put"
360 case "HEAD", "head":
361 return "head"
362 case "POST", "post":
363 return "post"
364 case "DELETE", "delete":
365 return "delete"
366 case "CONNECT", "connect":
367 return "connect"
368 case "OPTIONS", "options":
369 return "options"
370 case "NOTIFY", "notify":
371 return "notify"
372 default:
373 return strings.ToLower(m)
374 }
375}
376
377func sanitizeCode(s int) string {
378 switch s {
379 case 100:
380 return "100"
381 case 101:
382 return "101"
383
384 case 200:
385 return "200"
386 case 201:
387 return "201"
388 case 202:
389 return "202"
390 case 203:
391 return "203"
392 case 204:
393 return "204"
394 case 205:
395 return "205"
396 case 206:
397 return "206"
398
399 case 300:
400 return "300"
401 case 301:
402 return "301"
403 case 302:
404 return "302"
405 case 304:
406 return "304"
407 case 305:
408 return "305"
409 case 307:
410 return "307"
411
412 case 400:
413 return "400"
414 case 401:
415 return "401"
416 case 402:
417 return "402"
418 case 403:
419 return "403"
420 case 404:
421 return "404"
422 case 405:
423 return "405"
424 case 406:
425 return "406"
426 case 407:
427 return "407"
428 case 408:
429 return "408"
430 case 409:
431 return "409"
432 case 410:
433 return "410"
434 case 411:
435 return "411"
436 case 412:
437 return "412"
438 case 413:
439 return "413"
440 case 414:
441 return "414"
442 case 415:
443 return "415"
444 case 416:
445 return "416"
446 case 417:
447 return "417"
448 case 418:
449 return "418"
450
451 case 500:
452 return "500"
453 case 501:
454 return "501"
455 case 502:
456 return "502"
457 case 503:
458 return "503"
459 case 504:
460 return "504"
461 case 505:
462 return "505"
463
464 case 428:
465 return "428"
466 case 429:
467 return "429"
468 case 431:
469 return "431"
470 case 511:
471 return "511"
472
473 default:
474 return strconv.Itoa(s)
475 }
476}
477
478// gzipAccepted returns whether the client will accept gzip-encoded content.
479func gzipAccepted(header http.Header) bool {
480 a := header.Get(acceptEncodingHeader)
481 parts := strings.Split(a, ",")
482 for _, part := range parts {
483 part = strings.TrimSpace(part)
484 if part == "gzip" || strings.HasPrefix(part, "gzip;") {
485 return true
486 }
487 }
488 return false
489}
490
491// httpError removes any content-encoding header and then calls http.Error with
492// the provided error and http.StatusInternalServerErrer. Error contents is
493// supposed to be uncompressed plain text. However, same as with a plain
494// http.Error, any header settings will be void if the header has already been
495// sent. The error message will still be written to the writer, but it will
496// probably be of limited use.
497func httpError(rsp http.ResponseWriter, err error) {
498 rsp.Header().Del(contentEncodingHeader)
499 http.Error(
500 rsp,
501 "An error has occurred while serving metrics:\n\n"+err.Error(),
502 http.StatusInternalServerError,
503 )
504}