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 14package prometheus 15 16import ( 17 "bufio" 18 "io" 19 "net" 20 "net/http" 21 "strconv" 22 "strings" 23 "time" 24) 25 26var instLabels = []string{"method", "code"} 27 28type nower interface { 29 Now() time.Time 30} 31 32type nowFunc func() time.Time 33 34func (n nowFunc) Now() time.Time { 35 return n() 36} 37 38var now nower = nowFunc(func() time.Time { 39 return time.Now() 40}) 41 42func nowSeries(t ...time.Time) nower { 43 return nowFunc(func() time.Time { 44 defer func() { 45 t = t[1:] 46 }() 47 48 return t[0] 49 }) 50} 51 52// InstrumentHandler wraps the given HTTP handler for instrumentation. It 53// registers four metric collectors (if not already done) and reports HTTP 54// metrics to the (newly or already) registered collectors: http_requests_total 55// (CounterVec), http_request_duration_microseconds (Summary), 56// http_request_size_bytes (Summary), http_response_size_bytes (Summary). Each 57// has a constant label named "handler" with the provided handlerName as 58// value. http_requests_total is a metric vector partitioned by HTTP method 59// (label name "method") and HTTP status code (label name "code"). 60func InstrumentHandler(handlerName string, handler http.Handler) http.HandlerFunc { 61 return InstrumentHandlerFunc(handlerName, handler.ServeHTTP) 62} 63 64// InstrumentHandlerFunc wraps the given function for instrumentation. It 65// otherwise works in the same way as InstrumentHandler. 66func InstrumentHandlerFunc(handlerName string, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc { 67 return InstrumentHandlerFuncWithOpts( 68 SummaryOpts{ 69 Subsystem: "http", 70 ConstLabels: Labels{"handler": handlerName}, 71 }, 72 handlerFunc, 73 ) 74} 75 76// InstrumentHandlerWithOpts works like InstrumentHandler but provides more 77// flexibility (at the cost of a more complex call syntax). As 78// InstrumentHandler, this function registers four metric collectors, but it 79// uses the provided SummaryOpts to create them. However, the fields "Name" and 80// "Help" in the SummaryOpts are ignored. "Name" is replaced by 81// "requests_total", "request_duration_microseconds", "request_size_bytes", and 82// "response_size_bytes", respectively. "Help" is replaced by an appropriate 83// help string. The names of the variable labels of the http_requests_total 84// CounterVec are "method" (get, post, etc.), and "code" (HTTP status code). 85// 86// If InstrumentHandlerWithOpts is called as follows, it mimics exactly the 87// behavior of InstrumentHandler: 88// 89// prometheus.InstrumentHandlerWithOpts( 90// prometheus.SummaryOpts{ 91// Subsystem: "http", 92// ConstLabels: prometheus.Labels{"handler": handlerName}, 93// }, 94// handler, 95// ) 96// 97// Technical detail: "requests_total" is a CounterVec, not a SummaryVec, so it 98// cannot use SummaryOpts. Instead, a CounterOpts struct is created internally, 99// and all its fields are set to the equally named fields in the provided 100// SummaryOpts. 101func InstrumentHandlerWithOpts(opts SummaryOpts, handler http.Handler) http.HandlerFunc { 102 return InstrumentHandlerFuncWithOpts(opts, handler.ServeHTTP) 103} 104 105// InstrumentHandlerFuncWithOpts works like InstrumentHandlerFunc but provides 106// more flexibility (at the cost of a more complex call syntax). See 107// InstrumentHandlerWithOpts for details how the provided SummaryOpts are used. 108func InstrumentHandlerFuncWithOpts(opts SummaryOpts, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc { 109 reqCnt := NewCounterVec( 110 CounterOpts{ 111 Namespace: opts.Namespace, 112 Subsystem: opts.Subsystem, 113 Name: "requests_total", 114 Help: "Total number of HTTP requests made.", 115 ConstLabels: opts.ConstLabels, 116 }, 117 instLabels, 118 ) 119 120 opts.Name = "request_duration_microseconds" 121 opts.Help = "The HTTP request latencies in microseconds." 122 reqDur := NewSummary(opts) 123 124 opts.Name = "request_size_bytes" 125 opts.Help = "The HTTP request sizes in bytes." 126 reqSz := NewSummary(opts) 127 128 opts.Name = "response_size_bytes" 129 opts.Help = "The HTTP response sizes in bytes." 130 resSz := NewSummary(opts) 131 132 regReqCnt := MustRegisterOrGet(reqCnt).(*CounterVec) 133 regReqDur := MustRegisterOrGet(reqDur).(Summary) 134 regReqSz := MustRegisterOrGet(reqSz).(Summary) 135 regResSz := MustRegisterOrGet(resSz).(Summary) 136 137 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 138 now := time.Now() 139 140 delegate := &responseWriterDelegator{ResponseWriter: w} 141 out := make(chan int) 142 urlLen := 0 143 if r.URL != nil { 144 urlLen = len(r.URL.String()) 145 } 146 go computeApproximateRequestSize(r, out, urlLen) 147 148 _, cn := w.(http.CloseNotifier) 149 _, fl := w.(http.Flusher) 150 _, hj := w.(http.Hijacker) 151 _, rf := w.(io.ReaderFrom) 152 var rw http.ResponseWriter 153 if cn && fl && hj && rf { 154 rw = &fancyResponseWriterDelegator{delegate} 155 } else { 156 rw = delegate 157 } 158 handlerFunc(rw, r) 159 160 elapsed := float64(time.Since(now)) / float64(time.Microsecond) 161 162 method := sanitizeMethod(r.Method) 163 code := sanitizeCode(delegate.status) 164 regReqCnt.WithLabelValues(method, code).Inc() 165 regReqDur.Observe(elapsed) 166 regResSz.Observe(float64(delegate.written)) 167 regReqSz.Observe(float64(<-out)) 168 }) 169} 170 171func computeApproximateRequestSize(r *http.Request, out chan int, s int) { 172 s += len(r.Method) 173 s += len(r.Proto) 174 for name, values := range r.Header { 175 s += len(name) 176 for _, value := range values { 177 s += len(value) 178 } 179 } 180 s += len(r.Host) 181 182 // N.B. r.Form and r.MultipartForm are assumed to be included in r.URL. 183 184 if r.ContentLength != -1 { 185 s += int(r.ContentLength) 186 } 187 out <- s 188} 189 190type responseWriterDelegator struct { 191 http.ResponseWriter 192 193 handler, method string 194 status int 195 written int64 196 wroteHeader bool 197} 198 199func (r *responseWriterDelegator) WriteHeader(code int) { 200 r.status = code 201 r.wroteHeader = true 202 r.ResponseWriter.WriteHeader(code) 203} 204 205func (r *responseWriterDelegator) Write(b []byte) (int, error) { 206 if !r.wroteHeader { 207 r.WriteHeader(http.StatusOK) 208 } 209 n, err := r.ResponseWriter.Write(b) 210 r.written += int64(n) 211 return n, err 212} 213 214type fancyResponseWriterDelegator struct { 215 *responseWriterDelegator 216} 217 218func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool { 219 return f.ResponseWriter.(http.CloseNotifier).CloseNotify() 220} 221 222func (f *fancyResponseWriterDelegator) Flush() { 223 f.ResponseWriter.(http.Flusher).Flush() 224} 225 226func (f *fancyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) { 227 return f.ResponseWriter.(http.Hijacker).Hijack() 228} 229 230func (f *fancyResponseWriterDelegator) ReadFrom(r io.Reader) (int64, error) { 231 if !f.wroteHeader { 232 f.WriteHeader(http.StatusOK) 233 } 234 n, err := f.ResponseWriter.(io.ReaderFrom).ReadFrom(r) 235 f.written += n 236 return n, err 237} 238 239func sanitizeMethod(m string) string { 240 switch m { 241 case "GET", "get": 242 return "get" 243 case "PUT", "put": 244 return "put" 245 case "HEAD", "head": 246 return "head" 247 case "POST", "post": 248 return "post" 249 case "DELETE", "delete": 250 return "delete" 251 case "CONNECT", "connect": 252 return "connect" 253 case "OPTIONS", "options": 254 return "options" 255 case "NOTIFY", "notify": 256 return "notify" 257 default: 258 return strings.ToLower(m) 259 } 260} 261 262func sanitizeCode(s int) string { 263 switch s { 264 case 100: 265 return "100" 266 case 101: 267 return "101" 268 269 case 200: 270 return "200" 271 case 201: 272 return "201" 273 case 202: 274 return "202" 275 case 203: 276 return "203" 277 case 204: 278 return "204" 279 case 205: 280 return "205" 281 case 206: 282 return "206" 283 284 case 300: 285 return "300" 286 case 301: 287 return "301" 288 case 302: 289 return "302" 290 case 304: 291 return "304" 292 case 305: 293 return "305" 294 case 307: 295 return "307" 296 297 case 400: 298 return "400" 299 case 401: 300 return "401" 301 case 402: 302 return "402" 303 case 403: 304 return "403" 305 case 404: 306 return "404" 307 case 405: 308 return "405" 309 case 406: 310 return "406" 311 case 407: 312 return "407" 313 case 408: 314 return "408" 315 case 409: 316 return "409" 317 case 410: 318 return "410" 319 case 411: 320 return "411" 321 case 412: 322 return "412" 323 case 413: 324 return "413" 325 case 414: 326 return "414" 327 case 415: 328 return "415" 329 case 416: 330 return "416" 331 case 417: 332 return "417" 333 case 418: 334 return "418" 335 336 case 500: 337 return "500" 338 case 501: 339 return "501" 340 case 502: 341 return "502" 342 case 503: 343 return "503" 344 case 504: 345 return "504" 346 case 505: 347 return "505" 348 349 case 428: 350 return "428" 351 case 429: 352 return "429" 353 case 431: 354 return "431" 355 case 511: 356 return "511" 357 358 default: 359 return strconv.Itoa(s) 360 } 361} 362