fork of indigo with slightly nicer lexgen

refactor metrics middleware in to `util/svcutil` (#1003)

The main motivation here is to not have `splitter` import from `bgs`; it
was only doing so for MetricsMiddleware.

This moves MetricsMiddleware out to new service-oriented util package
(not directly in `util/` because it depends on prometheus and echo), and
updates the trivial usage to that.

Not touching `cmd/relay/` because that is getting refactored in a
separate branch; and not touching `search/metrics.go` because it adds
"extra" labels.

authored by bnewbold.net and committed by GitHub bfc7f487 66fd40eb

Changed files
+105 -149
bgs
cmd
collectiondir
splitter
util
+2 -1
bgs/bgs.go
··· 24 24 "github.com/bluesky-social/indigo/indexer" 25 25 "github.com/bluesky-social/indigo/models" 26 26 "github.com/bluesky-social/indigo/repomgr" 27 + "github.com/bluesky-social/indigo/util/svcutil" 27 28 "github.com/bluesky-social/indigo/xrpc" 28 29 lru "github.com/hashicorp/golang-lru/v2" 29 30 "golang.org/x/sync/semaphore" ··· 237 238 e.File("/dash/*", "public/index.html") 238 239 e.Static("/assets", "public/assets") 239 240 240 - e.Use(MetricsMiddleware) 241 + e.Use(svcutil.MetricsMiddleware) 241 242 242 243 e.HTTPErrorHandler = func(err error, ctx echo.Context) { 243 244 switch err := err.(type) {
-93
bgs/metrics.go
··· 1 1 package bgs 2 2 3 3 import ( 4 - "errors" 5 - "net/http" 6 - "strconv" 7 - "time" 8 - 9 - "github.com/labstack/echo/v4" 10 4 "github.com/prometheus/client_golang/prometheus" 11 5 "github.com/prometheus/client_golang/prometheus/promauto" 12 6 ) ··· 68 62 Help: "The total number of new users discovered directly from the firehose (not from refs)", 69 63 }) 70 64 71 - var reqSz = promauto.NewHistogramVec(prometheus.HistogramOpts{ 72 - Name: "http_request_size_bytes", 73 - Help: "A histogram of request sizes for requests.", 74 - Buckets: prometheus.ExponentialBuckets(100, 10, 8), 75 - }, []string{"code", "method", "path"}) 76 - 77 - var reqDur = promauto.NewHistogramVec(prometheus.HistogramOpts{ 78 - Name: "http_request_duration_seconds", 79 - Help: "A histogram of latencies for requests.", 80 - Buckets: prometheus.ExponentialBuckets(0.001, 2, 15), 81 - }, []string{"code", "method", "path"}) 82 - 83 - var reqCnt = promauto.NewCounterVec(prometheus.CounterOpts{ 84 - Name: "http_requests_total", 85 - Help: "A counter for requests to the wrapped handler.", 86 - }, []string{"code", "method", "path"}) 87 - 88 - var resSz = promauto.NewHistogramVec(prometheus.HistogramOpts{ 89 - Name: "http_response_size_bytes", 90 - Help: "A histogram of response sizes for requests.", 91 - Buckets: prometheus.ExponentialBuckets(100, 10, 8), 92 - }, []string{"code", "method", "path"}) 93 - 94 65 var userLookupDuration = promauto.NewHistogram(prometheus.HistogramOpts{ 95 66 Name: "relay_user_lookup_duration", 96 67 Help: "A histogram of user lookup latencies", ··· 102 73 Help: "A histogram of new user discovery latencies", 103 74 Buckets: prometheus.ExponentialBuckets(0.001, 2, 15), 104 75 }) 105 - 106 - // MetricsMiddleware defines handler function for metrics middleware 107 - func MetricsMiddleware(next echo.HandlerFunc) echo.HandlerFunc { 108 - return func(c echo.Context) error { 109 - path := c.Path() 110 - if path == "/metrics" || path == "/_health" { 111 - return next(c) 112 - } 113 - 114 - start := time.Now() 115 - requestSize := computeApproximateRequestSize(c.Request()) 116 - 117 - err := next(c) 118 - 119 - status := c.Response().Status 120 - if err != nil { 121 - var httpError *echo.HTTPError 122 - if errors.As(err, &httpError) { 123 - status = httpError.Code 124 - } 125 - if status == 0 || status == http.StatusOK { 126 - status = http.StatusInternalServerError 127 - } 128 - } 129 - 130 - elapsed := float64(time.Since(start)) / float64(time.Second) 131 - 132 - statusStr := strconv.Itoa(status) 133 - method := c.Request().Method 134 - 135 - responseSize := float64(c.Response().Size) 136 - 137 - reqDur.WithLabelValues(statusStr, method, path).Observe(elapsed) 138 - reqCnt.WithLabelValues(statusStr, method, path).Inc() 139 - reqSz.WithLabelValues(statusStr, method, path).Observe(float64(requestSize)) 140 - resSz.WithLabelValues(statusStr, method, path).Observe(responseSize) 141 - 142 - return err 143 - } 144 - } 145 - 146 - func computeApproximateRequestSize(r *http.Request) int { 147 - s := 0 148 - if r.URL != nil { 149 - s = len(r.URL.Path) 150 - } 151 - 152 - s += len(r.Method) 153 - s += len(r.Proto) 154 - for name, values := range r.Header { 155 - s += len(name) 156 - for _, value := range values { 157 - s += len(value) 158 - } 159 - } 160 - s += len(r.Host) 161 - 162 - // N.B. r.Form and r.MultipartForm are assumed to be included in r.URL. 163 - 164 - if r.ContentLength != -1 { 165 - s += int(r.ContentLength) 166 - } 167 - return s 168 - }
-52
cmd/collectiondir/metrics.go
··· 1 1 package main 2 2 3 3 import ( 4 - "errors" 5 - "github.com/labstack/echo/v4" 6 4 "github.com/prometheus/client_golang/prometheus" 7 5 "github.com/prometheus/client_golang/prometheus/promauto" 8 - "net/http" 9 - "strconv" 10 - "time" 11 6 ) 12 7 13 8 var firehoseReceivedCounter = promauto.NewCounter(prometheus.CounterOpts{ ··· 52 47 Help: "how long it takes to calculate total stats", 53 48 Buckets: prometheus.ExponentialBuckets(0.01, 2, 13), 54 49 }) 55 - 56 - var reqDur = promauto.NewHistogramVec(prometheus.HistogramOpts{ 57 - Name: "http_request_duration_seconds", 58 - Help: "A histogram of latencies for requests.", 59 - Buckets: prometheus.ExponentialBuckets(0.001, 2, 15), 60 - }, []string{"code", "method", "path"}) 61 - 62 - var reqCnt = promauto.NewCounterVec(prometheus.CounterOpts{ 63 - Name: "http_requests_total", 64 - Help: "A counter for requests to the wrapped handler.", 65 - }, []string{"code", "method", "path"}) 66 - 67 - // MetricsMiddleware defines handler function for metrics middleware 68 - // TODO: reunify with bgs/metrics.go ? 69 - func MetricsMiddleware(next echo.HandlerFunc) echo.HandlerFunc { 70 - return func(c echo.Context) error { 71 - path := c.Path() 72 - if path == "/metrics" || path == "/_health" { 73 - return next(c) 74 - } 75 - 76 - start := time.Now() 77 - 78 - err := next(c) 79 - 80 - status := c.Response().Status 81 - if err != nil { 82 - var httpError *echo.HTTPError 83 - if errors.As(err, &httpError) { 84 - status = httpError.Code 85 - } 86 - if status == 0 || status == http.StatusOK { 87 - status = http.StatusInternalServerError 88 - } 89 - } 90 - 91 - elapsed := float64(time.Since(start)) / float64(time.Second) 92 - 93 - statusStr := strconv.Itoa(status) 94 - method := c.Request().Method 95 - 96 - reqDur.WithLabelValues(statusStr, method, path).Observe(elapsed) 97 - reqCnt.WithLabelValues(statusStr, method, path).Inc() 98 - 99 - return err 100 - } 101 - }
+2 -1
cmd/collectiondir/serve.go
··· 24 24 comatproto "github.com/bluesky-social/indigo/api/atproto" 25 25 "github.com/bluesky-social/indigo/atproto/syntax" 26 26 "github.com/bluesky-social/indigo/events" 27 + "github.com/bluesky-social/indigo/util/svcutil" 27 28 "github.com/bluesky-social/indigo/xrpc" 28 29 29 30 "github.com/hashicorp/golang-lru/v2" ··· 405 406 e := echo.New() 406 407 e.HideBanner = true 407 408 408 - e.Use(MetricsMiddleware) 409 + e.Use(svcutil.MetricsMiddleware) 409 410 e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ 410 411 AllowOrigins: []string{"*"}, 411 412 AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, echo.HeaderAuthorization},
+2 -2
splitter/splitter.go
··· 20 20 21 21 "github.com/bluesky-social/indigo/api/atproto" 22 22 comatproto "github.com/bluesky-social/indigo/api/atproto" 23 - "github.com/bluesky-social/indigo/bgs" 24 23 "github.com/bluesky-social/indigo/events" 25 24 "github.com/bluesky-social/indigo/events/pebblepersist" 26 25 "github.com/bluesky-social/indigo/events/schedulers/sequential" 27 26 "github.com/bluesky-social/indigo/util" 27 + "github.com/bluesky-social/indigo/util/svcutil" 28 28 "github.com/bluesky-social/indigo/xrpc" 29 29 30 30 "github.com/gorilla/websocket" ··· 212 212 } 213 213 */ 214 214 215 - e.Use(bgs.MetricsMiddleware) 215 + e.Use(svcutil.MetricsMiddleware) 216 216 217 217 e.HTTPErrorHandler = func(err error, ctx echo.Context) { 218 218 switch err := err.(type) {
+99
util/svcutil/metrics_middleware.go
··· 1 + package svcutil 2 + 3 + import ( 4 + "errors" 5 + "net/http" 6 + "strconv" 7 + "time" 8 + 9 + "github.com/labstack/echo/v4" 10 + "github.com/prometheus/client_golang/prometheus" 11 + "github.com/prometheus/client_golang/prometheus/promauto" 12 + ) 13 + 14 + var reqSz = promauto.NewHistogramVec(prometheus.HistogramOpts{ 15 + Name: "http_request_size_bytes", 16 + Help: "A histogram of request sizes for requests.", 17 + Buckets: prometheus.ExponentialBuckets(100, 10, 8), 18 + }, []string{"code", "method", "path"}) 19 + 20 + var reqDur = promauto.NewHistogramVec(prometheus.HistogramOpts{ 21 + Name: "http_request_duration_seconds", 22 + Help: "A histogram of latencies for requests.", 23 + Buckets: prometheus.ExponentialBuckets(0.001, 2, 15), 24 + }, []string{"code", "method", "path"}) 25 + 26 + var reqCnt = promauto.NewCounterVec(prometheus.CounterOpts{ 27 + Name: "http_requests_total", 28 + Help: "A counter for requests to the wrapped handler.", 29 + }, []string{"code", "method", "path"}) 30 + 31 + var resSz = promauto.NewHistogramVec(prometheus.HistogramOpts{ 32 + Name: "http_response_size_bytes", 33 + Help: "A histogram of response sizes for requests.", 34 + Buckets: prometheus.ExponentialBuckets(100, 10, 8), 35 + }, []string{"code", "method", "path"}) 36 + 37 + // MetricsMiddleware defines handler function for metrics middleware 38 + func MetricsMiddleware(next echo.HandlerFunc) echo.HandlerFunc { 39 + return func(c echo.Context) error { 40 + path := c.Path() 41 + if path == "/metrics" || path == "/_health" { 42 + return next(c) 43 + } 44 + 45 + start := time.Now() 46 + requestSize := computeApproximateRequestSize(c.Request()) 47 + 48 + err := next(c) 49 + 50 + status := c.Response().Status 51 + if err != nil { 52 + var httpError *echo.HTTPError 53 + if errors.As(err, &httpError) { 54 + status = httpError.Code 55 + } 56 + if status == 0 || status == http.StatusOK { 57 + status = http.StatusInternalServerError 58 + } 59 + } 60 + 61 + elapsed := float64(time.Since(start)) / float64(time.Second) 62 + 63 + statusStr := strconv.Itoa(status) 64 + method := c.Request().Method 65 + 66 + responseSize := float64(c.Response().Size) 67 + 68 + reqDur.WithLabelValues(statusStr, method, path).Observe(elapsed) 69 + reqCnt.WithLabelValues(statusStr, method, path).Inc() 70 + reqSz.WithLabelValues(statusStr, method, path).Observe(float64(requestSize)) 71 + resSz.WithLabelValues(statusStr, method, path).Observe(responseSize) 72 + 73 + return err 74 + } 75 + } 76 + 77 + func computeApproximateRequestSize(r *http.Request) int { 78 + s := 0 79 + if r.URL != nil { 80 + s = len(r.URL.Path) 81 + } 82 + 83 + s += len(r.Method) 84 + s += len(r.Proto) 85 + for name, values := range r.Header { 86 + s += len(name) 87 + for _, value := range values { 88 + s += len(value) 89 + } 90 + } 91 + s += len(r.Host) 92 + 93 + // N.B. r.Form and r.MultipartForm are assumed to be included in r.URL. 94 + 95 + if r.ContentLength != -1 { 96 + s += int(r.ContentLength) 97 + } 98 + return s 99 + }