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
6
// http://www.apache.org/licenses/LICENSE-2.0
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.
29
"github.com/prometheus/common/expfmt"
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.
38
contentTypeHeader = "Content-Type"
39
contentLengthHeader = "Content-Length"
40
contentEncodingHeader = "Content-Encoding"
41
acceptEncodingHeader = "Accept-Encoding"
46
func getBuf() *bytes.Buffer {
49
return &bytes.Buffer{}
51
return buf.(*bytes.Buffer)
54
func giveBuf(buf *bytes.Buffer) {
59
// Handler returns an HTTP handler for the DefaultGatherer. It is
60
// already instrumented with InstrumentHandler (using "prometheus" as handler
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())
70
// UninstrumentedHandler returns an HTTP handler for the DefaultGatherer.
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()
77
http.Error(w, "An error has occurred during metrics collection:\n\n"+err.Error(), http.StatusInternalServerError)
81
contentType := expfmt.Negotiate(req.Header)
84
writer, encoding := decorateWriter(req, buf)
85
enc := expfmt.NewEncoder(writer, contentType)
87
for _, mf := range mfs {
88
if err := enc.Encode(mf); err != nil {
90
http.Error(w, "An error has occurred during metrics encoding:\n\n"+err.Error(), http.StatusInternalServerError)
94
if closer, ok := writer.(io.Closer); ok {
97
if lastErr != nil && buf.Len() == 0 {
98
http.Error(w, "No metrics encoded, last error:\n\n"+err.Error(), http.StatusInternalServerError)
102
header.Set(contentTypeHeader, string(contentType))
103
header.Set(contentLengthHeader, fmt.Sprint(buf.Len()))
105
header.Set(contentEncodingHeader, encoding)
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"
126
var instLabels = []string{"method", "code"}
128
type nower interface {
132
type nowFunc func() time.Time
134
func (n nowFunc) Now() time.Time {
138
var now nower = nowFunc(func() time.Time {
142
func nowSeries(t ...time.Time) nower {
143
return nowFunc(func() time.Time {
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").
161
// Deprecated: InstrumentHandler has several issues:
163
// - It uses Summaries rather than Histograms. Summaries are not useful if
164
// aggregation across multiple instances is required.
166
// - It uses microseconds as unit, which is deprecated and should be replaced by
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.
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)
182
// InstrumentHandlerFunc wraps the given function for instrumentation. It
183
// otherwise works in the same way as InstrumentHandler (and shares the same
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(
192
ConstLabels: Labels{"handler": handlerName},
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).
208
// If InstrumentHandlerWithOpts is called as follows, it mimics exactly the
209
// behavior of InstrumentHandler:
211
// prometheus.InstrumentHandlerWithOpts(
212
// prometheus.SummaryOpts{
213
// Subsystem: "http",
214
// ConstLabels: prometheus.Labels{"handler": handlerName},
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
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)
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.
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(
240
Namespace: opts.Namespace,
241
Subsystem: opts.Subsystem,
242
Name: "requests_total",
243
Help: "Total number of HTTP requests made.",
244
ConstLabels: opts.ConstLabels,
249
opts.Name = "request_duration_microseconds"
250
opts.Help = "The HTTP request latencies in microseconds."
251
reqDur := NewSummary(opts)
253
opts.Name = "request_size_bytes"
254
opts.Help = "The HTTP request sizes in bytes."
255
reqSz := NewSummary(opts)
257
opts.Name = "response_size_bytes"
258
opts.Help = "The HTTP response sizes in bytes."
259
resSz := NewSummary(opts)
261
regReqCnt := MustRegisterOrGet(reqCnt).(*CounterVec)
262
regReqDur := MustRegisterOrGet(reqDur).(Summary)
263
regReqSz := MustRegisterOrGet(reqSz).(Summary)
264
regResSz := MustRegisterOrGet(resSz).(Summary)
266
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
269
delegate := &responseWriterDelegator{ResponseWriter: w}
270
out := make(chan int)
273
urlLen = len(r.URL.String())
275
go computeApproximateRequestSize(r, out, urlLen)
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}
289
elapsed := float64(time.Since(now)) / float64(time.Microsecond)
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))
300
func computeApproximateRequestSize(r *http.Request, out chan int, s int) {
303
for name, values := range r.Header {
305
for _, value := range values {
311
// N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.
313
if r.ContentLength != -1 {
314
s += int(r.ContentLength)
319
type responseWriterDelegator struct {
322
handler, method string
328
func (r *responseWriterDelegator) WriteHeader(code int) {
331
r.ResponseWriter.WriteHeader(code)
334
func (r *responseWriterDelegator) Write(b []byte) (int, error) {
336
r.WriteHeader(http.StatusOK)
338
n, err := r.ResponseWriter.Write(b)
339
r.written += int64(n)
343
type fancyResponseWriterDelegator struct {
344
*responseWriterDelegator
347
func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool {
348
return f.ResponseWriter.(http.CloseNotifier).CloseNotify()
351
func (f *fancyResponseWriterDelegator) Flush() {
352
f.ResponseWriter.(http.Flusher).Flush()
355
func (f *fancyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
356
return f.ResponseWriter.(http.Hijacker).Hijack()
359
func (f *fancyResponseWriterDelegator) ReadFrom(r io.Reader) (int64, error) {
361
f.WriteHeader(http.StatusOK)
363
n, err := f.ResponseWriter.(io.ReaderFrom).ReadFrom(r)
368
func sanitizeMethod(m string) string {
378
case "DELETE", "delete":
380
case "CONNECT", "connect":
382
case "OPTIONS", "options":
384
case "NOTIFY", "notify":
387
return strings.ToLower(m)
391
func sanitizeCode(s int) string {
488
return strconv.Itoa(s)