diff --git a/README.md b/README.md index 41111059..77e3ed1f 100644 --- a/README.md +++ b/README.md @@ -345,6 +345,10 @@ sequenceDiagram mev_boost-->>consensus: submitBlindedBlock response ``` +# Metrics + +Optionally the `-prometheus-port=9000` flag can be passed to expose prometheus metrics on specified port + # Maintainers - [@metachris](https://github.com/metachris) diff --git a/cli/flags.go b/cli/flags.go index c37cfcac..f976da48 100644 --- a/cli/flags.go +++ b/cli/flags.go @@ -34,6 +34,7 @@ var flags = []cli.Flag{ timeoutGetPayloadFlag, timeoutRegValFlag, maxRetriesFlag, + prometheusListenAddr, } var ( @@ -83,6 +84,12 @@ var ( Usage: "disables adding the version to every log entry", Category: LoggingCategory, } + prometheusListenAddr = &cli.IntFlag{ + Name: "prometheus-port", + Sources: cli.EnvVars("PROMETHEUS_PORT"), + Usage: "when set to a valid http port, will export runtime metrics to a prometheus server on that port", + Category: LoggingCategory, + } // Genesis Flags customGenesisForkFlag = &cli.StringFlag{ Name: "genesis-fork-version", diff --git a/cli/main.go b/cli/main.go index eaaa5b29..ef8e8737 100644 --- a/cli/main.go +++ b/cli/main.go @@ -5,6 +5,7 @@ import ( "errors" "flag" "fmt" + "math" "os" "strings" "time" @@ -13,6 +14,8 @@ import ( "github.com/flashbots/mev-boost/common" "github.com/flashbots/mev-boost/config" "github.com/flashbots/mev-boost/server" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/collectors" "github.com/sirupsen/logrus" "github.com/urfave/cli/v3" ) @@ -69,6 +72,13 @@ func start(_ context.Context, cmd *cli.Command) error { relays, monitors, minBid, relayCheck = setupRelays(cmd) listenAddr = cmd.String(addrFlag.Name) ) + prometheusRegistry := prometheus.NewRegistry() + if err := prometheusRegistry.Register(collectors.NewGoCollector()); err != nil { + log.WithError(err).Error("Failed to register metrics for GoCollector") + } + if err := prometheusRegistry.Register(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{})); err != nil { + log.WithError(err).Error("Failed to register ProcessCollector") + } opts := server.BoostServiceOpts{ Log: log, @@ -83,6 +93,8 @@ func start(_ context.Context, cmd *cli.Command) error { RequestTimeoutGetPayload: time.Duration(cmd.Int(timeoutGetPayloadFlag.Name)) * time.Millisecond, RequestTimeoutRegVal: time.Duration(cmd.Int(timeoutRegValFlag.Name)) * time.Millisecond, RequestMaxRetries: int(cmd.Int(maxRetriesFlag.Name)), + PrometheusListenAddr: int(cmd.Int(prometheusListenAddr.Name)), + PrometheusRegistry: prometheusRegistry, } service, err := server.NewBoostService(opts) if err != nil { @@ -93,6 +105,15 @@ func start(_ context.Context, cmd *cli.Command) error { log.Error("no relay passed the health-check!") } + if opts.PrometheusListenAddr > 0 && opts.PrometheusListenAddr <= math.MaxUint16 { + go func() { + log.Infof("Metric Server Listening on %d", opts.PrometheusListenAddr) + if err := service.StartMetricsServer(); err != nil { + log.WithError(err).Error("metrics server exited with error") + } + }() + } + log.Infof("Listening on %v", listenAddr) return service.StartHTTPServer() } diff --git a/go.mod b/go.mod index 2f75331b..e53f799e 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 github.com/holiman/uint256 v1.3.1 + github.com/prometheus/client_golang v1.16.0 github.com/prysmaticlabs/go-bitfield v0.0.0-20240328144219-a1caa50c3a1e github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.9.0 @@ -44,7 +45,6 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect - github.com/prometheus/client_golang v1.16.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect diff --git a/server/service.go b/server/service.go index bdfc929e..e20bece8 100644 --- a/server/service.go +++ b/server/service.go @@ -27,6 +27,8 @@ import ( "github.com/flashbots/mev-boost/server/types" "github.com/google/uuid" "github.com/gorilla/mux" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/sirupsen/logrus" ) @@ -37,6 +39,7 @@ var ( errInvalidPubkey = errors.New("invalid pubkey") errNoSuccessfulRelayResponse = errors.New("no successful relay response") errServerAlreadyRunning = errors.New("server already running") + errNilPrometheusRegistry = errors.New("nil prometheus registry") ) var ( @@ -69,6 +72,8 @@ type BoostServiceOpts struct { RequestTimeoutGetPayload time.Duration RequestTimeoutRegVal time.Duration RequestMaxRetries int + PrometheusListenAddr int + PrometheusRegistry *prometheus.Registry } // BoostService - the mev-boost service @@ -93,6 +98,9 @@ type BoostService struct { slotUID *slotUID slotUIDLock sync.Mutex + + prometheusListenAddr int + prometheusRegistry *prometheus.Registry } // NewBoostService created a new BoostService @@ -130,7 +138,9 @@ func NewBoostService(opts BoostServiceOpts) (*BoostService, error) { Timeout: opts.RequestTimeoutRegVal, CheckRedirect: httpClientDisallowRedirects, }, - requestMaxRetries: opts.RequestMaxRetries, + requestMaxRetries: opts.RequestMaxRetries, + prometheusListenAddr: opts.PrometheusListenAddr, + prometheusRegistry: opts.PrometheusRegistry, }, nil } @@ -194,6 +204,22 @@ func (m *BoostService) StartHTTPServer() error { return err } +// StartMetricsServer starts the HTTP server for exporting open-metrics +func (m *BoostService) StartMetricsServer() error { + if m.prometheusRegistry == nil { + return errNilPrometheusRegistry + } + serveMux := http.NewServeMux() + serveMux.Handle("/metrics", promhttp.HandlerFor(m.prometheusRegistry, promhttp.HandlerOpts{ + ErrorLog: m.log, + EnableOpenMetrics: true, + })) + return http.ListenAndServe( + fmt.Sprintf(":%d", m.prometheusListenAddr), + serveMux, + ) +} + func (m *BoostService) startBidCacheCleanupTask() { for { time.Sleep(1 * time.Minute)