github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/metrics/rest_api.go (about) 1 package metrics 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 "github.com/prometheus/client_golang/prometheus" 9 httpmetrics "github.com/slok/go-http-metrics/metrics" 10 11 "github.com/onflow/flow-go/module" 12 ) 13 14 type RestCollector struct { 15 httpRequestDurHistogram *prometheus.HistogramVec 16 httpResponseSizeHistogram *prometheus.HistogramVec 17 httpRequestsInflight *prometheus.GaugeVec 18 httpRequestsTotal *prometheus.GaugeVec 19 20 // urlToRouteMapper is a callback that converts a URL to a route name 21 urlToRouteMapper func(string) (string, error) 22 } 23 24 var _ module.RestMetrics = (*RestCollector)(nil) 25 26 // NewRestCollector returns a new metrics RestCollector that implements the RestCollector 27 // using Prometheus as the backend. 28 func NewRestCollector(urlToRouteMapper func(string) (string, error), registerer prometheus.Registerer) (*RestCollector, error) { 29 if urlToRouteMapper == nil { 30 return nil, fmt.Errorf("urlToRouteMapper cannot be nil") 31 } 32 33 r := &RestCollector{ 34 urlToRouteMapper: urlToRouteMapper, 35 httpRequestDurHistogram: prometheus.NewHistogramVec(prometheus.HistogramOpts{ 36 Namespace: namespaceRestAPI, 37 Subsystem: subsystemHTTP, 38 Name: "request_duration_seconds", 39 Help: "The latency of the HTTP requests.", 40 Buckets: prometheus.DefBuckets, 41 }, []string{LabelService, LabelHandler, LabelMethod, LabelStatusCode}), 42 43 httpResponseSizeHistogram: prometheus.NewHistogramVec(prometheus.HistogramOpts{ 44 Namespace: namespaceRestAPI, 45 Subsystem: subsystemHTTP, 46 Name: "response_size_bytes", 47 Help: "The size of the HTTP responses.", 48 Buckets: prometheus.ExponentialBuckets(100, 10, 8), 49 }, []string{LabelService, LabelHandler, LabelMethod, LabelStatusCode}), 50 51 httpRequestsInflight: prometheus.NewGaugeVec(prometheus.GaugeOpts{ 52 Namespace: namespaceRestAPI, 53 Subsystem: subsystemHTTP, 54 Name: "requests_inflight", 55 Help: "The number of inflight requests being handled at the same time.", 56 }, []string{LabelService, LabelHandler}), 57 58 httpRequestsTotal: prometheus.NewGaugeVec(prometheus.GaugeOpts{ 59 Namespace: namespaceRestAPI, 60 Subsystem: subsystemHTTP, 61 Name: "requests_total", 62 Help: "The number of requests handled over time.", 63 }, []string{LabelMethod, LabelHandler}), 64 } 65 66 registerer.MustRegister( 67 r.httpRequestDurHistogram, 68 r.httpResponseSizeHistogram, 69 r.httpRequestsInflight, 70 r.httpRequestsTotal, 71 ) 72 73 return r, nil 74 } 75 76 // ObserveHTTPRequestDuration records the duration of the REST request. 77 // This method is called automatically by go-http-metrics/middleware 78 func (r *RestCollector) ObserveHTTPRequestDuration(_ context.Context, p httpmetrics.HTTPReqProperties, duration time.Duration) { 79 handler := r.mapURLToRoute(p.ID) 80 r.httpRequestDurHistogram.WithLabelValues(p.Service, handler, p.Method, p.Code).Observe(duration.Seconds()) 81 } 82 83 // ObserveHTTPResponseSize records the response size of the REST request. 84 // This method is called automatically by go-http-metrics/middleware 85 func (r *RestCollector) ObserveHTTPResponseSize(_ context.Context, p httpmetrics.HTTPReqProperties, sizeBytes int64) { 86 handler := r.mapURLToRoute(p.ID) 87 r.httpResponseSizeHistogram.WithLabelValues(p.Service, handler, p.Method, p.Code).Observe(float64(sizeBytes)) 88 } 89 90 // AddInflightRequests increments and decrements the number of inflight request being processed. 91 // This method is called automatically by go-http-metrics/middleware 92 func (r *RestCollector) AddInflightRequests(_ context.Context, p httpmetrics.HTTPProperties, quantity int) { 93 handler := r.mapURLToRoute(p.ID) 94 r.httpRequestsInflight.WithLabelValues(p.Service, handler).Add(float64(quantity)) 95 } 96 97 // AddTotalRequests records all REST requests 98 // This is a custom method called by the REST handler 99 func (r *RestCollector) AddTotalRequests(_ context.Context, method, path string) { 100 handler := r.mapURLToRoute(path) 101 r.httpRequestsTotal.WithLabelValues(method, handler).Inc() 102 } 103 104 // mapURLToRoute uses the urlToRouteMapper callback to convert a URL to a route name 105 // This normalizes the URL, removing dynamic information converting it to a static string 106 func (r *RestCollector) mapURLToRoute(url string) string { 107 route, err := r.urlToRouteMapper(url) 108 if err != nil { 109 return "unknown" 110 } 111 112 return route 113 }