Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Access] Fixed REST API metrics #4288

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 5 additions & 9 deletions engine/access/rest/middleware/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,20 @@ package middleware
import (
"net/http"

"github.com/onflow/flow-go/module/metrics"

"github.com/slok/go-http-metrics/middleware"
"github.com/slok/go-http-metrics/middleware/std"

metricsProm "github.com/slok/go-http-metrics/metrics/prometheus"

"github.com/gorilla/mux"
)

func MetricsMiddleware() mux.MiddlewareFunc {
r := metrics.NewRestCollector(metricsProm.Config{Prefix: "access_rest_api"})
metricsMiddleware := middleware.New(middleware.Config{Recorder: r})
"github.com/onflow/flow-go/module"
)

func MetricsMiddleware(restCollector module.RestMetrics) mux.MiddlewareFunc {
metricsMiddleware := middleware.New(middleware.Config{Recorder: restCollector})
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// This is a custom metric being called on every http request
r.AddTotalRequests(req.Context(), req.Method, req.URL.Path)
restCollector.AddTotalRequests(req.Context(), req.Method, req.URL.Path)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to create a unique value of handler for every single collection, causing too. much metrics to be collected

image


// Modify the writer
respWriter := &responseWriter{w, http.StatusOK}
Expand Down
5 changes: 3 additions & 2 deletions engine/access/rest/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,18 @@ import (
"github.com/onflow/flow-go/engine/access/rest/middleware"
"github.com/onflow/flow-go/engine/access/rest/models"
"github.com/onflow/flow-go/model/flow"
"github.com/onflow/flow-go/module"
)

func newRouter(backend access.API, logger zerolog.Logger, chain flow.Chain) (*mux.Router, error) {
func newRouter(backend access.API, logger zerolog.Logger, chain flow.Chain, restCollector module.RestMetrics) (*mux.Router, error) {
router := mux.NewRouter().StrictSlash(true)
v1SubRouter := router.PathPrefix("/v1").Subrouter()

// common middleware for all request
v1SubRouter.Use(middleware.LoggingMiddleware(logger))
v1SubRouter.Use(middleware.QueryExpandable())
v1SubRouter.Use(middleware.QuerySelect())
v1SubRouter.Use(middleware.MetricsMiddleware())
v1SubRouter.Use(middleware.MetricsMiddleware(restCollector))

linkGenerator := models.NewLinkGeneratorImpl(v1SubRouter)

Expand Down
5 changes: 3 additions & 2 deletions engine/access/rest/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import (

"github.com/onflow/flow-go/access"
"github.com/onflow/flow-go/model/flow"
"github.com/onflow/flow-go/module"
)

// NewServer returns an HTTP server initialized with the REST API handler
func NewServer(backend access.API, listenAddress string, logger zerolog.Logger, chain flow.Chain) (*http.Server, error) {
func NewServer(backend access.API, listenAddress string, logger zerolog.Logger, chain flow.Chain, restCollector module.RestMetrics) (*http.Server, error) {

router, err := newRouter(backend, logger, chain)
router, err := newRouter(backend, logger, chain, restCollector)
if err != nil {
return nil, err
}
Expand Down
4 changes: 3 additions & 1 deletion engine/access/rest/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

"github.com/onflow/flow-go/access/mock"
"github.com/onflow/flow-go/model/flow"
"github.com/onflow/flow-go/module/metrics"
)

const (
Expand All @@ -28,7 +29,8 @@ const (
func executeRequest(req *http.Request, backend *mock.API) (*httptest.ResponseRecorder, error) {
var b bytes.Buffer
logger := zerolog.New(&b)
router, err := newRouter(backend, logger, flow.Testnet.Chain())
restCollector := metrics.NewNoopCollector()
router, err := newRouter(backend, logger, flow.Testnet.Chain(), restCollector)
if err != nil {
return nil, err
}
Expand Down
4 changes: 3 additions & 1 deletion engine/access/rpc/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type Engine struct {
finalizedHeaderCache *events.FinalizedHeaderCache

log zerolog.Logger
restCollector module.RestMetrics
backend *backend.Backend // the gRPC service implementation
unsecureGrpcServer *grpc.Server // the unsecure gRPC server
secureGrpcServer *grpc.Server // the secure gRPC server
Expand Down Expand Up @@ -209,6 +210,7 @@ func NewBuilder(log zerolog.Logger,
httpServer: httpServer,
config: config,
chain: chainID.Chain(),
restCollector: accessMetrics,
}
backendNotifierActor, backendNotifierWorker := events.NewFinalizationActor(eng.notifyBackendOnBlockFinalized)
eng.backendNotifierActor = backendNotifierActor
Expand Down Expand Up @@ -383,7 +385,7 @@ func (e *Engine) serveREST(ctx irrecoverable.SignalerContext, ready component.Re

e.log.Info().Str("rest_api_address", e.config.RESTListenAddr).Msg("starting REST server on address")

r, err := rest.NewServer(e.backend, e.config.RESTListenAddr, e.log, e.chain)
r, err := rest.NewServer(e.backend, e.config.RESTListenAddr, e.log, e.chain, e.restCollector)
if err != nil {
e.log.Err(err).Msg("failed to initialize the REST server")
ctx.Throw(err)
Expand Down
10 changes: 10 additions & 0 deletions module/metrics.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package module

import (
"context"
"time"

"github.com/libp2p/go-libp2p/core/peer"
rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
httpmetrics "github.com/slok/go-http-metrics/metrics"

"github.com/onflow/flow-go/model/chainsync"
"github.com/onflow/flow-go/model/cluster"
Expand Down Expand Up @@ -584,7 +586,15 @@ type ExecutionDataPrunerMetrics interface {
Pruned(height uint64, duration time.Duration)
}

// Example recorder taken from:
// https://github.com/slok/go-http-metrics/blob/master/metrics/prometheus/prometheus.go
type RestMetrics interface {
httpmetrics.Recorder
AddTotalRequests(ctx context.Context, service string, id string)
}

type AccessMetrics interface {
RestMetrics
// TotalConnectionsInPool updates the number connections to collection/execution nodes stored in the pool, and the size of the pool
TotalConnectionsInPool(connectionCount uint, connectionPoolSize uint)

Expand Down
8 changes: 8 additions & 0 deletions module/metrics/access.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package metrics

import (
"github.com/onflow/flow-go/module"

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"

metricsProm "github.com/slok/go-http-metrics/metrics/prometheus"
)

type AccessCollector struct {
module.RestMetrics
connectionReused prometheus.Counter
connectionsInPool *prometheus.GaugeVec
connectionAdded prometheus.Counter
Expand All @@ -15,6 +20,8 @@ type AccessCollector struct {
connectionEvicted prometheus.Counter
}

var _ module.AccessMetrics = (*AccessCollector)(nil)

func NewAccessCollector() *AccessCollector {
ac := &AccessCollector{
connectionReused: promauto.NewCounter(prometheus.CounterOpts{
Expand Down Expand Up @@ -60,6 +67,7 @@ func NewAccessCollector() *AccessCollector {
Help: "counter for the number of times a cached connection is evicted from the connection pool",
}),
}
ac.RestMetrics = NewRestCollector(metricsProm.Config{Prefix: "access_rest_api"})

return ac
}
Expand Down
34 changes: 19 additions & 15 deletions module/metrics/rest_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,24 @@ import (

"github.com/prometheus/client_golang/prometheus"

"github.com/onflow/flow-go/module"

httpmetrics "github.com/slok/go-http-metrics/metrics"
metricsProm "github.com/slok/go-http-metrics/metrics/prometheus"
)

// Example recorder taken from:
// https://github.com/slok/go-http-metrics/blob/master/metrics/prometheus/prometheus.go
type RestCollector interface {
httpmetrics.Recorder
AddTotalRequests(ctx context.Context, service string, id string)
}

type recorder struct {
type RestCollector struct {
httpRequestDurHistogram *prometheus.HistogramVec
httpResponseSizeHistogram *prometheus.HistogramVec
httpRequestsInflight *prometheus.GaugeVec
httpRequestsTotal *prometheus.GaugeVec
}

// NewRestCollector returns a new metrics recorder that implements the recorder
var _ module.RestMetrics = (*RestCollector)(nil)

// NewRestCollector returns a new metrics RestCollector that implements the RestCollector
// using Prometheus as the backend.
func NewRestCollector(cfg metricsProm.Config) RestCollector {
func NewRestCollector(cfg metricsProm.Config) module.RestMetrics {
if len(cfg.DurationBuckets) == 0 {
cfg.DurationBuckets = prometheus.DefBuckets
}
Expand Down Expand Up @@ -55,7 +52,7 @@ func NewRestCollector(cfg metricsProm.Config) RestCollector {
cfg.ServiceLabel = "service"
}

r := &recorder{
r := &RestCollector{
httpRequestDurHistogram: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Namespace: cfg.Prefix,
Subsystem: "http",
Expand Down Expand Up @@ -87,23 +84,30 @@ func NewRestCollector(cfg metricsProm.Config) RestCollector {
}, []string{cfg.ServiceLabel, cfg.HandlerIDLabel}),
}

cfg.Registry.MustRegister(
r.httpRequestDurHistogram,
r.httpResponseSizeHistogram,
r.httpRequestsInflight,
r.httpRequestsTotal,
)

return r
}

// These methods are called automatically by go-http-metrics/middleware
func (r recorder) ObserveHTTPRequestDuration(_ context.Context, p httpmetrics.HTTPReqProperties, duration time.Duration) {
func (r *RestCollector) ObserveHTTPRequestDuration(_ context.Context, p httpmetrics.HTTPReqProperties, duration time.Duration) {
r.httpRequestDurHistogram.WithLabelValues(p.Service, p.ID, p.Method, p.Code).Observe(duration.Seconds())
}

func (r recorder) ObserveHTTPResponseSize(_ context.Context, p httpmetrics.HTTPReqProperties, sizeBytes int64) {
func (r *RestCollector) ObserveHTTPResponseSize(_ context.Context, p httpmetrics.HTTPReqProperties, sizeBytes int64) {
r.httpResponseSizeHistogram.WithLabelValues(p.Service, p.ID, p.Method, p.Code).Observe(float64(sizeBytes))
}

func (r recorder) AddInflightRequests(_ context.Context, p httpmetrics.HTTPProperties, quantity int) {
func (r *RestCollector) AddInflightRequests(_ context.Context, p httpmetrics.HTTPProperties, quantity int) {
r.httpRequestsInflight.WithLabelValues(p.Service, p.ID).Add(float64(quantity))
}

// New custom method to track all requests made for every REST API request
func (r recorder) AddTotalRequests(_ context.Context, method string, id string) {
func (r *RestCollector) AddTotalRequests(_ context.Context, method string, id string) {
r.httpRequestsTotal.WithLabelValues(method, id).Inc()
}
29 changes: 28 additions & 1 deletion module/mock/access_metrics.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 44 additions & 0 deletions module/mock/finalized_header_cache.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 52 additions & 0 deletions module/mock/rest_metrics.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.