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  }