github.com/MetalBlockchain/metalgo@v1.11.9/utils/metric/api_interceptor.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package metric
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"net/http"
    10  	"time"
    11  
    12  	"github.com/gorilla/rpc/v2"
    13  	"github.com/prometheus/client_golang/prometheus"
    14  )
    15  
    16  type APIInterceptor interface {
    17  	InterceptRequest(i *rpc.RequestInfo) *http.Request
    18  	AfterRequest(i *rpc.RequestInfo)
    19  }
    20  
    21  type contextKey int
    22  
    23  const requestTimestampKey contextKey = iota
    24  
    25  type apiInterceptor struct {
    26  	requestDurationCount *prometheus.CounterVec
    27  	requestDurationSum   *prometheus.GaugeVec
    28  	requestErrors        *prometheus.CounterVec
    29  }
    30  
    31  func NewAPIInterceptor(registerer prometheus.Registerer) (APIInterceptor, error) {
    32  	requestDurationCount := prometheus.NewCounterVec(
    33  		prometheus.CounterOpts{
    34  			Name: "request_duration_count",
    35  			Help: "Number of times this type of request was made",
    36  		},
    37  		[]string{"method"},
    38  	)
    39  	requestDurationSum := prometheus.NewGaugeVec(
    40  		prometheus.GaugeOpts{
    41  			Name: "request_duration_sum",
    42  			Help: "Amount of time in nanoseconds that has been spent handling this type of request",
    43  		},
    44  		[]string{"method"},
    45  	)
    46  	requestErrors := prometheus.NewCounterVec(
    47  		prometheus.CounterOpts{
    48  			Name: "request_error_count",
    49  		},
    50  		[]string{"method"},
    51  	)
    52  
    53  	err := errors.Join(
    54  		registerer.Register(requestDurationCount),
    55  		registerer.Register(requestDurationSum),
    56  		registerer.Register(requestErrors),
    57  	)
    58  	return &apiInterceptor{
    59  		requestDurationCount: requestDurationCount,
    60  		requestDurationSum:   requestDurationSum,
    61  		requestErrors:        requestErrors,
    62  	}, err
    63  }
    64  
    65  func (*apiInterceptor) InterceptRequest(i *rpc.RequestInfo) *http.Request {
    66  	ctx := i.Request.Context()
    67  	ctx = context.WithValue(ctx, requestTimestampKey, time.Now())
    68  	return i.Request.WithContext(ctx)
    69  }
    70  
    71  func (apr *apiInterceptor) AfterRequest(i *rpc.RequestInfo) {
    72  	timestampIntf := i.Request.Context().Value(requestTimestampKey)
    73  	timestamp, ok := timestampIntf.(time.Time)
    74  	if !ok {
    75  		return
    76  	}
    77  
    78  	durationMetricCount := apr.requestDurationCount.With(prometheus.Labels{
    79  		"method": i.Method,
    80  	})
    81  	durationMetricCount.Inc()
    82  
    83  	duration := time.Since(timestamp)
    84  	durationMetricSum := apr.requestDurationSum.With(prometheus.Labels{
    85  		"method": i.Method,
    86  	})
    87  	durationMetricSum.Add(float64(duration))
    88  
    89  	if i.Error != nil {
    90  		errMetric := apr.requestErrors.With(prometheus.Labels{
    91  			"method": i.Method,
    92  		})
    93  		errMetric.Inc()
    94  	}
    95  }