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 }