~ubuntu-branches/debian/sid/golang-github-prometheus-client-golang/sid

« back to all changes in this revision

Viewing changes to prometheus/http.go

  • Committer: Package Import Robot
  • Author(s): Martín Ferrari
  • Date: 2016-08-18 12:06:03 UTC
  • Revision ID: package-import@ubuntu.com-20160818120603-xevgulhsaf9vktsr
Tags: upstream-0.8.0
Import upstream version 0.8.0

Show diffs side-by-side

added added

removed removed

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