sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/metrics/http.go (about) 1 /* 2 Copyright 2019 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package metrics 18 19 import ( 20 "math" 21 "net/http" 22 "strconv" 23 "time" 24 25 "github.com/prometheus/client_golang/prometheus" 26 27 "sigs.k8s.io/prow/pkg/simplifypath" 28 ) 29 30 // HttpRequestDuration returns a histogram vector with relevant fields set 31 func HttpRequestDuration(prefix string, min, max float64) *prometheus.HistogramVec { 32 return histogram( 33 prefix+"_http_request_duration_seconds", 34 "http request duration in seconds", 35 powersOfTwoBetween(min, max), 36 ) 37 } 38 39 // HttpResponseSize returns a histogram vector with relevant fields set 40 func HttpResponseSize(prefix string, min, max int) *prometheus.HistogramVec { 41 return histogram( 42 prefix+"_http_response_size_bytes", 43 "http response size in bytes", 44 powersOfTwoBetween(float64(min), float64(max)), 45 ) 46 } 47 48 // ErrorRate returns a counter vector with relevant fields set 49 func ErrorRate(prefix string) *prometheus.CounterVec { 50 return prometheus.NewCounterVec( 51 prometheus.CounterOpts{ 52 Name: prefix + "_error_rate", 53 Help: "number of errors, sorted by label/type.", 54 }, 55 []string{"error"}, 56 ) 57 } 58 59 // powersOfTwoBetween returns a set containing min, max and all the integer powers 60 // of two between them, including negative integers if either the min or max is <1 61 func powersOfTwoBetween(min, max float64) []float64 { 62 var powers []float64 63 floor, ceiling := math.Ceil(math.Log2(min)), math.Floor(math.Log2(max)) 64 if math.Pow(2, floor) != min { 65 powers = append(powers, min) 66 } 67 for i := floor; i <= ceiling; i++ { 68 powers = append(powers, math.Pow(2, i)) 69 } 70 if math.Pow(2, ceiling) != max { 71 powers = append(powers, max) 72 } 73 return powers 74 } 75 76 func histogram(name, help string, buckets []float64) *prometheus.HistogramVec { 77 return prometheus.NewHistogramVec( 78 prometheus.HistogramOpts{ 79 Name: name, 80 Help: help, 81 Buckets: buckets, 82 }, 83 []string{"path", "method", "status", "user_agent"}, 84 ) 85 } 86 87 type traceResponseWriter struct { 88 http.ResponseWriter 89 statusCode int 90 size int 91 } 92 93 func (trw *traceResponseWriter) WriteHeader(code int) { 94 trw.statusCode = code 95 trw.ResponseWriter.WriteHeader(code) 96 } 97 98 func (trw *traceResponseWriter) Write(data []byte) (int, error) { 99 size, err := trw.ResponseWriter.Write(data) 100 trw.size += size 101 return size, err 102 } 103 104 // Metrics holds the metrics for Prometheus 105 type Metrics struct { 106 HTTPRequestDuration *prometheus.HistogramVec 107 HTTPResponseSize *prometheus.HistogramVec 108 ErrorRate *prometheus.CounterVec 109 } 110 111 // NewMetrics is a constructor for Metrics 112 func NewMetrics(namespace string) *Metrics { 113 m := &Metrics{ 114 HTTPRequestDuration: HttpRequestDuration(namespace, 0.0001, 2), 115 HTTPResponseSize: HttpResponseSize(namespace, 256, 65536), 116 ErrorRate: ErrorRate(namespace), 117 } 118 prometheus.MustRegister(m.HTTPRequestDuration) 119 prometheus.MustRegister(m.HTTPResponseSize) 120 prometheus.MustRegister(m.ErrorRate) 121 return m 122 } 123 124 // RecordError records the error to prometheus 125 func RecordError(label string, errorRate *prometheus.CounterVec) { 126 labels := prometheus.Labels{"error": label} 127 errorRate.With(labels).Inc() 128 } 129 130 // TraceHandler allows the for a custom timer to be used to log metrics for Handler functions 131 // It is an abstraction to allow testing of HandleWithMetrics 132 func TraceHandler(simplifier simplifypath.Simplifier, httpRequestDuration, httpResponseSize *prometheus.HistogramVec) func(h http.Handler) http.Handler { 133 return traceHandlerWithCustomTimer(simplifier, httpRequestDuration, httpResponseSize, time.Since) 134 } 135 136 // Using a custom timer allows for proper testing of the latency functionality 137 func traceHandlerWithCustomTimer(simplifier simplifypath.Simplifier, httpRequestDuration *prometheus.HistogramVec, httpResponseSize *prometheus.HistogramVec, timeSince func(time.Time) time.Duration) func(h http.Handler) http.Handler { 138 return func(h http.Handler) http.Handler { 139 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 140 t := time.Now() 141 // Initialize the status to 200 in case WriteHeader is not called 142 trw := &traceResponseWriter{ResponseWriter: w, statusCode: http.StatusOK} 143 h.ServeHTTP(trw, r) 144 latency := timeSince(t) 145 labels := prometheus.Labels{"path": simplifier.Simplify(r.URL.Path), "method": r.Method, "status": strconv.Itoa(trw.statusCode), "user_agent": r.Header.Get("User-Agent")} 146 httpRequestDuration.With(labels).Observe(latency.Seconds()) 147 httpResponseSize.With(labels).Observe(float64(trw.size)) 148 }) 149 } 150 }