diff --git a/README.md b/README.md index 41111059..66324795 100644 --- a/README.md +++ b/README.md @@ -250,6 +250,10 @@ Usage of mev-boost: add a 'service=...' tag to all log messages -loglevel string minimum loglevel: trace, debug, info, warn/warning, error, fatal, panic (default "info") + -metrics + enables a metrics server (default: false) + -metrics-addr string + listening address for the metrics server (default: "localhost:18551") -mainnet use Mainnet (default true) -min-bid float @@ -313,6 +317,10 @@ Example for setting a minimum bid value of 0.06 ETH: -relay $YOUR_RELAY_CHOICE_C ``` +### Enabling metrics + +Optionally, the `-metrics` flag can be provided to expose a prometheus metrics server. The metrics server address/port can be changed with the `-metrics-addr` (e.g., `-metrics-addr localhost:9009`) flag. + --- # API diff --git a/cli/flags.go b/cli/flags.go index c37cfcac..e70fd448 100644 --- a/cli/flags.go +++ b/cli/flags.go @@ -34,6 +34,8 @@ var flags = []cli.Flag{ timeoutGetPayloadFlag, timeoutRegValFlag, maxRetriesFlag, + metricsFlag, + metricsAddrFlag, } var ( @@ -83,6 +85,19 @@ var ( Usage: "disables adding the version to every log entry", Category: LoggingCategory, } + metricsFlag = &cli.BoolFlag{ + Name: "metrics", + Sources: cli.EnvVars("METRICS_ENABLED"), + Usage: "enables a metrics server", + Category: LoggingCategory, + } + metricsAddrFlag = &cli.StringFlag{ + Name: "metrics-addr", + Sources: cli.EnvVars("METRICS_ADDR"), + Value: "localhost:18551", + Usage: "listening address for the metrics server", + Category: LoggingCategory, + } // Genesis Flags customGenesisForkFlag = &cli.StringFlag{ Name: "genesis-fork-version", diff --git a/cli/main.go b/cli/main.go index 108c56e4..efcfe88e 100644 --- a/cli/main.go +++ b/cli/main.go @@ -68,6 +68,8 @@ func start(_ context.Context, cmd *cli.Command) error { genesisForkVersion, genesisTime = setupGenesis(cmd) relays, monitors, minBid, relayCheck = setupRelays(cmd) listenAddr = cmd.String(addrFlag.Name) + metricsEnabled = cmd.Bool(metricsFlag.Name) + metricsAddr = cmd.String(metricsAddrFlag.Name) ) opts := server.BoostServiceOpts{ @@ -83,6 +85,7 @@ 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)), + MetricsAddr: metricsAddr, } service, err := server.NewBoostService(opts) if err != nil { @@ -93,7 +96,16 @@ func start(_ context.Context, cmd *cli.Command) error { log.Error("no relay passed the health-check!") } - log.Infof("Listening on %v", listenAddr) + if metricsEnabled { + go func() { + log.Infof("metrics server listening on %v", opts.MetricsAddr) + if err := service.StartMetricsServer(); err != nil { + log.WithError(err).Error("metrics server exited with error") + } + }() + } + + log.Infof("listening on %v", listenAddr) return service.StartHTTPServer() } @@ -146,7 +158,7 @@ func setupRelays(cmd *cli.Command) (relayList, relayMonitorList, types.U256Str, log.WithError(err).Fatal("Failed sanitizing min bid") } if relayMinBidWei.BigInt().Sign() > 0 { - log.Infof("Min bid set to %v eth (%v wei)", cmd.Float(minBidFlag.Name), relayMinBidWei) + log.Infof("min bid set to %v eth (%v wei)", cmd.Float(minBidFlag.Name), relayMinBidWei) } return relays, monitors, *relayMinBidWei, cmd.Bool(relayCheckFlag.Name) } diff --git a/go.mod b/go.mod index 88fbcb14..2dfc39f3 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.2 + github.com/prometheus/client_golang v1.16.0 github.com/prysmaticlabs/go-bitfield v0.0.0-20240618144021-706c95b2dd15 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.10.0 @@ -16,7 +17,9 @@ require ( ) require ( + github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.13.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/consensys/bavard v0.1.13 // indirect github.com/consensys/gnark-crypto v0.12.1 // indirect github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c // indirect @@ -27,13 +30,19 @@ require ( github.com/go-playground/validator/v10 v10.11.1 // indirect github.com/goccy/go-yaml v1.11.3 // indirect github.com/gofrs/flock v0.8.1 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/mattn/go-colorable v0.1.13 // indirect 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_model v0.3.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/procfs v0.10.1 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/supranational/blst v0.3.13 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + google.golang.org/protobuf v1.34.2 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/go.sum b/go.sum index fa44e0fe..828fa9bb 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/attestantio/go-eth2-client v0.22.1-0.20250106164842-07b6ce39bb43 h1:l github.com/attestantio/go-eth2-client v0.22.1-0.20250106164842-07b6ce39bb43/go.mod h1:vy5jU/uDZ2+RcVzq5BfnG+bQ3/6uu9DGwCrGsPtjJ1A= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -56,6 +58,10 @@ github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I= github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= @@ -96,6 +102,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -110,6 +118,14 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= +github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= +github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/prysmaticlabs/go-bitfield v0.0.0-20240618144021-706c95b2dd15 h1:lC8kiphgdOBTcbTvo8MwkvpKjO0SlAgjv4xIK5FGJ94= github.com/prysmaticlabs/go-bitfield v0.0.0-20240618144021-706c95b2dd15/go.mod h1:8svFBIKKu31YriBG/pNizo9N0Jr9i5PQ+dFkxWg3x5k= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= @@ -151,6 +167,7 @@ golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ss golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -175,6 +192,8 @@ golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/server/service.go b/server/service.go index 7b0757d6..de31602a 100644 --- a/server/service.go +++ b/server/service.go @@ -27,6 +27,9 @@ 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/collectors" + "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/sirupsen/logrus" ) @@ -69,6 +72,8 @@ type BoostServiceOpts struct { RequestTimeoutGetPayload time.Duration RequestTimeoutRegVal time.Duration RequestMaxRetries int + + MetricsAddr string } // BoostService - the mev-boost service @@ -93,6 +98,8 @@ type BoostService struct { slotUID *slotUID slotUIDLock sync.Mutex + + metricsAddr string } // NewBoostService created a new BoostService @@ -131,6 +138,7 @@ func NewBoostService(opts BoostServiceOpts) (*BoostService, error) { CheckRedirect: httpClientDisallowRedirects, }, requestMaxRetries: opts.RequestMaxRetries, + metricsAddr: opts.MetricsAddr, }, nil } @@ -194,6 +202,24 @@ func (m *BoostService) StartHTTPServer() error { return err } +// StartMetricsServer starts the HTTP server for exporting open-metrics +func (m *BoostService) StartMetricsServer() error { + prometheusRegistry := prometheus.NewRegistry() + if err := prometheusRegistry.Register(collectors.NewGoCollector()); err != nil { + m.log.WithError(err).Error("failed to register metrics for GoCollector") + } + if err := prometheusRegistry.Register(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{})); err != nil { + m.log.WithError(err).Error("failed to register ProcessCollector") + } + + serveMux := http.NewServeMux() + serveMux.Handle("/metrics", promhttp.HandlerFor(prometheusRegistry, promhttp.HandlerOpts{ + ErrorLog: m.log, + EnableOpenMetrics: true, + })) + return http.ListenAndServe(m.metricsAddr, serveMux) +} + func (m *BoostService) startBidCacheCleanupTask() { for { time.Sleep(1 * time.Minute)