github.com/ethersphere/bee/v2@v2.2.0/pkg/api/metrics.go (about)

     1  // Copyright 2020 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package api
     6  
     7  import (
     8  	"net/http"
     9  	"strconv"
    10  	"time"
    11  
    12  	"github.com/ethersphere/bee/v2"
    13  	m "github.com/ethersphere/bee/v2/pkg/metrics"
    14  	"github.com/prometheus/client_golang/prometheus"
    15  	"github.com/prometheus/client_golang/prometheus/collectors"
    16  )
    17  
    18  const bytesInKB = 1000
    19  
    20  var fileSizeBucketsKBytes = []int64{100, 500, 2500, 4999, 5000, 10000}
    21  
    22  type metrics struct {
    23  	// all metrics fields must be exported
    24  	// to be able to return them by Metrics()
    25  	// using reflection
    26  	RequestCount       prometheus.Counter
    27  	ResponseDuration   prometheus.Histogram
    28  	PingRequestCount   prometheus.Counter
    29  	ResponseCodeCounts *prometheus.CounterVec
    30  
    31  	ContentApiDuration prometheus.HistogramVec
    32  }
    33  
    34  func newMetrics() metrics {
    35  	subsystem := "api"
    36  
    37  	return metrics{
    38  		RequestCount: prometheus.NewCounter(prometheus.CounterOpts{
    39  			Namespace: m.Namespace,
    40  			Subsystem: subsystem,
    41  			Name:      "request_count",
    42  			Help:      "Number of API requests.",
    43  		}),
    44  		ResponseDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
    45  			Namespace: m.Namespace,
    46  			Subsystem: subsystem,
    47  			Name:      "response_duration_seconds",
    48  			Help:      "Histogram of API response durations.",
    49  			Buckets:   []float64{0.01, 0.1, 0.25, 0.5, 1, 2.5, 5, 10},
    50  		}),
    51  		ResponseCodeCounts: prometheus.NewCounterVec(
    52  			prometheus.CounterOpts{
    53  				Namespace: m.Namespace,
    54  				Subsystem: subsystem,
    55  				Name:      "response_code_count",
    56  				Help:      "Response count grouped by status code",
    57  			},
    58  			[]string{"code", "method"},
    59  		),
    60  		ContentApiDuration: *prometheus.NewHistogramVec(prometheus.HistogramOpts{
    61  			Namespace: m.Namespace,
    62  			Subsystem: subsystem,
    63  			Name:      "content_api_duration",
    64  			Help:      "Histogram of file upload API response durations.",
    65  			Buckets:   []float64{0.5, 1, 2.5, 5, 10, 30, 60},
    66  		}, []string{"filesize", "method"}),
    67  	}
    68  }
    69  
    70  func toFileSizeBucket(bytes int64) int64 {
    71  
    72  	for _, s := range fileSizeBucketsKBytes {
    73  		if (s * bytesInKB) >= bytes {
    74  			return s * bytesInKB
    75  		}
    76  	}
    77  
    78  	return fileSizeBucketsKBytes[len(fileSizeBucketsKBytes)-1] * bytesInKB
    79  }
    80  
    81  func (s *Service) Metrics() []prometheus.Collector {
    82  	return m.PrometheusCollectorsFromFields(s.metrics)
    83  }
    84  
    85  func (s *Service) pageviewMetricsHandler(h http.Handler) http.Handler {
    86  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    87  		start := time.Now()
    88  		s.metrics.RequestCount.Inc()
    89  		h.ServeHTTP(w, r)
    90  		s.metrics.ResponseDuration.Observe(time.Since(start).Seconds())
    91  	})
    92  }
    93  
    94  func (s *Service) responseCodeMetricsHandler(h http.Handler) http.Handler {
    95  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    96  		wrapper := newResponseWriter(w)
    97  		h.ServeHTTP(wrapper, r)
    98  		s.metrics.ResponseCodeCounts.WithLabelValues(
    99  			strconv.Itoa(wrapper.statusCode),
   100  			r.Method,
   101  		).Inc()
   102  	})
   103  }
   104  
   105  // UpgradedResponseWriter adds more functionality on top of ResponseWriter
   106  type UpgradedResponseWriter interface {
   107  	http.ResponseWriter
   108  	http.Pusher
   109  	http.Hijacker
   110  	http.Flusher
   111  }
   112  
   113  type responseWriter struct {
   114  	UpgradedResponseWriter
   115  	statusCode  int
   116  	wroteHeader bool
   117  }
   118  
   119  func newResponseWriter(w http.ResponseWriter) *responseWriter {
   120  	// StatusOK is called by default if nothing else is called
   121  	uw := w.(UpgradedResponseWriter)
   122  	return &responseWriter{uw, http.StatusOK, false}
   123  }
   124  
   125  func (rw *responseWriter) Status() int {
   126  	return rw.statusCode
   127  }
   128  
   129  func (rw *responseWriter) WriteHeader(code int) {
   130  	if rw.wroteHeader {
   131  		return
   132  	}
   133  	rw.statusCode = code
   134  	rw.UpgradedResponseWriter.WriteHeader(code)
   135  	rw.wroteHeader = true
   136  }
   137  
   138  func newDebugMetrics() (r *prometheus.Registry) {
   139  	r = prometheus.NewRegistry()
   140  
   141  	// register standard metrics
   142  	r.MustRegister(
   143  		collectors.NewProcessCollector(collectors.ProcessCollectorOpts{
   144  			Namespace: m.Namespace,
   145  		}),
   146  		collectors.NewGoCollector(),
   147  		prometheus.NewGauge(prometheus.GaugeOpts{
   148  			Namespace: m.Namespace,
   149  			Name:      "info",
   150  			Help:      "Bee information.",
   151  			ConstLabels: prometheus.Labels{
   152  				"version": bee.Version,
   153  			},
   154  		}),
   155  	)
   156  
   157  	return r
   158  }
   159  
   160  func (s *Service) MetricsRegistry() *prometheus.Registry {
   161  	return s.metricsRegistry
   162  }
   163  
   164  func (s *Service) MustRegisterMetrics(cs ...prometheus.Collector) {
   165  	s.metricsRegistry.MustRegister(cs...)
   166  }