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  }