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