diff --git a/README.md b/README.md index 22b612af93f..ec301b60313 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,36 @@ sudo cp protoc-gen-gogoslick /usr/bin/ Done +## Running p2p Prometheus dashboards +1. Start the node with `--p2p-prometheus-metrics` flag. This exposes a metrics collection at http://localhost:8080/debug/metrics/prometheus (port defined by -rest-api-interface flag, default 8080) +2. Clone libp2p repository: `git clone https://github.com/libp2p/go-libp2p` +3. `cd go-libp2p/dasboards/swarm` and under the +``` +"templating": { + "list": [ +``` +section, add the following lines: +``` +{ + "hide": 0, + "label": "datasource", + "name": "DS_PROMETHEUS", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "type": "datasource" +}, +``` +(this step will be removed once it will be fixed on libp2p) +4. `cd ..` to dashboards directory and update the port of `host.docker.internal` from `prometheus.yml` to node's Rest API port(default `8080`) +5. From this directory, run the following docker compose command: +``` +sudo docker compose -f docker-compose.base.yml -f docker-compose-linux.yml up --force-recreate +``` +**Note:** If you choose to install the new Docker version manually, please make sure that installation is done for all users of the system. Otherwise, the docker command will fail because it needs the super-user privileges. +6. The preconfigured dashboards should be now available on Grafana at http://localhost:3000/dashboards + ## Progress ### Done diff --git a/api/gin/common_test.go b/api/gin/common_test.go index 46a2492de8a..0f2c75c848d 100644 --- a/api/gin/common_test.go +++ b/api/gin/common_test.go @@ -22,7 +22,12 @@ func TestCommon_checkArgs(t *testing.T) { err := checkArgs(args) require.True(t, errors.Is(err, apiErrors.ErrCannotCreateGinWebServer)) - args.Facade, err = initial.NewInitialNodeFacade("api interface", false, &testscommon.StatusMetricsStub{}) + args.Facade, err = initial.NewInitialNodeFacade(initial.ArgInitialNodeFacade{ + ApiInterface: "api interface", + PprofEnabled: false, + P2PPrometheusMetricsEnabled: false, + StatusMetricsHandler: &testscommon.StatusMetricsStub{}, + }) require.NoError(t, err) err = checkArgs(args) require.NoError(t, err) diff --git a/api/gin/webServer.go b/api/gin/webServer.go index bfbaf5336d8..d6184179b69 100644 --- a/api/gin/webServer.go +++ b/api/gin/webServer.go @@ -19,10 +19,13 @@ import ( "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/facade" logger "github.com/multiversx/mx-chain-logger-go" + "github.com/prometheus/client_golang/prometheus/promhttp" ) var log = logger.GetOrCreate("api/gin") +const prometheusMetricsRoute = "/debug/metrics/prometheus" + // ArgsNewWebServer holds the arguments needed to create a new instance of webServer type ArgsNewWebServer struct { Facade shared.FacadeHandler @@ -227,6 +230,10 @@ func (ws *webServer) registerRoutes(ginRouter *gin.Engine) { if ws.facade.PprofEnabled() { pprof.Register(ginRouter) } + + if ws.facade.P2PPrometheusMetricsEnabled() { + ginRouter.GET(prometheusMetricsRoute, gin.WrapH(promhttp.Handler())) + } } func (ws *webServer) createMiddlewareLimiters() ([]shared.MiddlewareProcessor, error) { diff --git a/api/mock/facadeStub.go b/api/mock/facadeStub.go index 366af9dd218..8e6cae663ae 100644 --- a/api/mock/facadeStub.go +++ b/api/mock/facadeStub.go @@ -93,6 +93,7 @@ type FacadeStub struct { GetManagedKeysCalled func() []string GetEligibleManagedKeysCalled func() ([]string, error) GetWaitingManagedKeysCalled func() ([]string, error) + P2PPrometheusMetricsEnabledCalled func() bool } // GetTokenSupply - @@ -610,6 +611,14 @@ func (f *FacadeStub) GetWaitingManagedKeys() ([]string, error) { return make([]string, 0), nil } +// P2PPrometheusMetricsEnabled - +func (f *FacadeStub) P2PPrometheusMetricsEnabled() bool { + if f.P2PPrometheusMetricsEnabledCalled != nil { + return f.P2PPrometheusMetricsEnabledCalled() + } + return false +} + // Close - func (f *FacadeStub) Close() error { return nil diff --git a/api/shared/interface.go b/api/shared/interface.go index 0b199393b96..643d4c6177b 100644 --- a/api/shared/interface.go +++ b/api/shared/interface.go @@ -132,5 +132,6 @@ type FacadeHandler interface { GetManagedKeys() []string GetEligibleManagedKeys() ([]string, error) GetWaitingManagedKeys() ([]string, error) + P2PPrometheusMetricsEnabled() bool IsInterfaceNil() bool } diff --git a/cmd/node/CLI.md b/cmd/node/CLI.md index 0c05553b034..47c219ca64b 100644 --- a/cmd/node/CLI.md +++ b/cmd/node/CLI.md @@ -73,6 +73,7 @@ GLOBAL OPTIONS: --logs-path directory This flag specifies the directory where the node will store logs. --operation-mode operation mode String flag for specifying the desired operation mode(s) of the node, resulting in altering some configuration values accordingly. Possible values are: snapshotless-observer, full-archive, db-lookup-extension, historical-balances or `""` (empty). Multiple values can be separated via , --repopulate-tokens-supplies Boolean flag for repopulating the tokens supplies database. It will delete the current data, iterate over the entire trie and add he new obtained supplies + --p2p-prometheus-metrics Boolean option for enabling the /debug/metrics/prometheus route for p2p prometheus metrics --help, -h show help --version, -v print the version diff --git a/cmd/node/flags.go b/cmd/node/flags.go index db799c8ed6b..452aceecf89 100644 --- a/cmd/node/flags.go +++ b/cmd/node/flags.go @@ -407,6 +407,13 @@ var ( Name: "repopulate-tokens-supplies", Usage: "Boolean flag for repopulating the tokens supplies database. It will delete the current data, iterate over the entire trie and add he new obtained supplies", } + + // p2pPrometheusMetrics defines a flag for p2p prometheus metrics + // If enabled, it will open a new route, /debug/metrics/prometheus, where p2p prometheus metrics will be available + p2pPrometheusMetrics = cli.BoolFlag{ + Name: "p2p-prometheus-metrics", + Usage: "Boolean option for enabling the /debug/metrics/prometheus route for p2p prometheus metrics", + } ) func getFlags() []cli.Flag { @@ -469,6 +476,7 @@ func getFlags() []cli.Flag { logsDirectory, operationMode, repopulateTokensSupplies, + p2pPrometheusMetrics, } } @@ -497,6 +505,7 @@ func getFlagsConfig(ctx *cli.Context, log logger.Logger) *config.ContextFlagsCon flagsConfig.SerializeSnapshots = ctx.GlobalBool(serializeSnapshots.Name) flagsConfig.OperationMode = ctx.GlobalString(operationMode.Name) flagsConfig.RepopulateTokensSupplies = ctx.GlobalBool(repopulateTokensSupplies.Name) + flagsConfig.P2PPrometheusMetricsEnabled = ctx.GlobalBool(p2pPrometheusMetrics.Name) if ctx.GlobalBool(noKey.Name) { log.Warn("the provided -no-key option is deprecated and will soon be removed. To start a node without " + diff --git a/cmd/seednode/CLI.md b/cmd/seednode/CLI.md index f192127ac29..4a3d8af0afe 100644 --- a/cmd/seednode/CLI.md +++ b/cmd/seednode/CLI.md @@ -21,6 +21,7 @@ GLOBAL OPTIONS: --log-save Boolean option for enabling log saving. If set, it will automatically save all the logs into a file. --config [path] The [path] for the main configuration file. This TOML file contain the main configurations such as the marshalizer type (default: "./config/config.toml") --p2p-key-pem-file filepath The filepath for the PEM file which contains the secret keys for the p2p key. If this is not specified a new key will be generated (internally) by default. (default: "./config/p2pKey.pem") + --p2p-prometheus-metrics Boolean option for enabling the /debug/metrics/prometheus route for p2p prometheus metrics --help, -h show help --version, -v print the version diff --git a/cmd/seednode/api/api.go b/cmd/seednode/api/api.go index 6d9625f78f1..461f146f439 100644 --- a/cmd/seednode/api/api.go +++ b/cmd/seednode/api/api.go @@ -9,25 +9,26 @@ import ( "github.com/multiversx/mx-chain-core-go/marshal" "github.com/multiversx/mx-chain-go/api/logs" logger "github.com/multiversx/mx-chain-logger-go" + "github.com/prometheus/client_golang/prometheus/promhttp" ) var log = logger.GetOrCreate("seednode/api") // Start will boot up the api and appropriate routes, handlers and validators -func Start(restApiInterface string, marshalizer marshal.Marshalizer) error { +func Start(restApiInterface string, marshalizer marshal.Marshalizer, p2pPrometheusMetricsEnabled bool) error { ws := gin.Default() ws.Use(cors.Default()) - registerRoutes(ws, marshalizer) + registerRoutes(ws, marshalizer, p2pPrometheusMetricsEnabled) return ws.Run(restApiInterface) } -func registerRoutes(ws *gin.Engine, marshalizer marshal.Marshalizer) { - registerLoggerWsRoute(ws, marshalizer) +func registerRoutes(ws *gin.Engine, marshalizer marshal.Marshalizer, p2pPrometheusMetricsEnabled bool) { + registerLoggerWsRoute(ws, marshalizer, p2pPrometheusMetricsEnabled) } -func registerLoggerWsRoute(ws *gin.Engine, marshalizer marshal.Marshalizer) { +func registerLoggerWsRoute(ws *gin.Engine, marshalizer marshal.Marshalizer, p2pPrometheusMetricsEnabled bool) { upgrader := websocket.Upgrader{} ws.GET("/log", func(c *gin.Context) { @@ -49,4 +50,8 @@ func registerLoggerWsRoute(ws *gin.Engine, marshalizer marshal.Marshalizer) { ls.StartSendingBlocking() }) + + if p2pPrometheusMetricsEnabled { + ws.GET("/debug/metrics/prometheus", gin.WrapH(promhttp.Handler())) + } } diff --git a/cmd/seednode/main.go b/cmd/seednode/main.go index 0f10d060c87..c881fb2a752 100644 --- a/cmd/seednode/main.go +++ b/cmd/seednode/main.go @@ -98,6 +98,13 @@ VERSION: } p2pConfigurationFile = "./config/p2p.toml" + + // p2pPrometheusMetrics defines a flag for p2p prometheus metrics + // If enabled, it will open a new route, /debug/metrics/prometheus, where p2p prometheus metrics will be available + p2pPrometheusMetrics = cli.BoolFlag{ + Name: "p2p-prometheus-metrics", + Usage: "Boolean option for enabling the /debug/metrics/prometheus route for p2p prometheus metrics", + } ) var log = logger.GetOrCreate("main") @@ -114,6 +121,7 @@ func main() { logSaveFile, configurationFile, p2pKeyPemFile, + p2pPrometheusMetrics, } app.Version = "v0.0.1" app.Authors = []cli.Author{ @@ -350,14 +358,15 @@ func checkExpectedPeerCount(p2pConfig p2pConfig.P2PConfig) error { func startRestServices(ctx *cli.Context, marshalizer marshal.Marshalizer) { restApiInterface := ctx.GlobalString(restApiInterfaceFlag.Name) if restApiInterface != facade.DefaultRestPortOff { - go startGinServer(restApiInterface, marshalizer) + p2pPrometheusMetricsEnabled := ctx.GlobalBool(p2pPrometheusMetrics.Name) + go startGinServer(restApiInterface, marshalizer, p2pPrometheusMetricsEnabled) } else { log.Info("rest api is disabled") } } -func startGinServer(restApiInterface string, marshalizer marshal.Marshalizer) { - err := api.Start(restApiInterface, marshalizer) +func startGinServer(restApiInterface string, marshalizer marshal.Marshalizer, p2pPrometheusMetricsEnabled bool) { + err := api.Start(restApiInterface, marshalizer, p2pPrometheusMetricsEnabled) if err != nil { log.LogIfError(err) } diff --git a/config/config.go b/config/config.go index 8c8b6a1ecb1..5a9a3ba4cd9 100644 --- a/config/config.go +++ b/config/config.go @@ -285,8 +285,9 @@ type GeneralSettingsConfig struct { // FacadeConfig will hold different configuration option that will be passed to the node facade type FacadeConfig struct { - RestApiInterface string - PprofEnabled bool + RestApiInterface string + PprofEnabled bool + P2PPrometheusMetricsEnabled bool } // StateTriesConfig will hold information about state tries diff --git a/config/contextFlagsConfig.go b/config/contextFlagsConfig.go index 7ff956e8800..7a64c8e6d5a 100644 --- a/config/contextFlagsConfig.go +++ b/config/contextFlagsConfig.go @@ -27,6 +27,7 @@ type ContextFlagsConfig struct { SerializeSnapshots bool OperationMode string RepopulateTokensSupplies bool + P2PPrometheusMetricsEnabled bool } // ImportDbConfig will hold the import-db parameters diff --git a/facade/initial/initialNodeFacade.go b/facade/initial/initialNodeFacade.go index a8e04f2c0bd..2d70b972150 100644 --- a/facade/initial/initialNodeFacade.go +++ b/facade/initial/initialNodeFacade.go @@ -26,28 +26,38 @@ import ( var errNodeStarting = errors.New("node is starting") var emptyString = "" +// ArgInitialNodeFacade is the DTO used to create a new instance of initialNodeFacade +type ArgInitialNodeFacade struct { + ApiInterface string + PprofEnabled bool + P2PPrometheusMetricsEnabled bool + StatusMetricsHandler external.StatusMetricsHandler +} + // initialNodeFacade represents a facade with no functionality type initialNodeFacade struct { - apiInterface string - statusMetricsHandler external.StatusMetricsHandler - pprofEnabled bool + apiInterface string + statusMetricsHandler external.StatusMetricsHandler + pprofEnabled bool + p2pPrometheusMetricsEnabled bool } // NewInitialNodeFacade is the initial implementation of the facade interface -func NewInitialNodeFacade(apiInterface string, pprofEnabled bool, statusMetricsHandler external.StatusMetricsHandler) (*initialNodeFacade, error) { - if check.IfNil(statusMetricsHandler) { +func NewInitialNodeFacade(args ArgInitialNodeFacade) (*initialNodeFacade, error) { + if check.IfNil(args.StatusMetricsHandler) { return nil, facade.ErrNilStatusMetrics } - initialStatusMetrics, err := NewInitialStatusMetricsProvider(statusMetricsHandler) + initialStatusMetrics, err := NewInitialStatusMetricsProvider(args.StatusMetricsHandler) if err != nil { return nil, err } return &initialNodeFacade{ - apiInterface: apiInterface, - statusMetricsHandler: initialStatusMetrics, - pprofEnabled: pprofEnabled, + apiInterface: args.ApiInterface, + statusMetricsHandler: initialStatusMetrics, + pprofEnabled: args.PprofEnabled, + p2pPrometheusMetricsEnabled: args.P2PPrometheusMetricsEnabled, }, nil } @@ -76,7 +86,7 @@ func (inf *initialNodeFacade) SetSyncer(_ ntp.SyncTimer) { } // RestAPIServerDebugMode returns false -//TODO: remove in the future +// TODO: remove in the future func (inf *initialNodeFacade) RestAPIServerDebugMode() bool { return false } @@ -426,6 +436,11 @@ func (inf *initialNodeFacade) GetWaitingManagedKeys() ([]string, error) { return nil, errNodeStarting } +// P2PPrometheusMetricsEnabled returns either the p2p prometheus metrics are enabled or not +func (inf *initialNodeFacade) P2PPrometheusMetricsEnabled() bool { + return inf.p2pPrometheusMetricsEnabled +} + // IsInterfaceNil returns true if there is no value under the interface func (inf *initialNodeFacade) IsInterfaceNil() bool { return inf == nil diff --git a/facade/initial/initialNodeFacade_test.go b/facade/initial/initialNodeFacade_test.go index 7298b001ba3..ff71355545c 100644 --- a/facade/initial/initialNodeFacade_test.go +++ b/facade/initial/initialNodeFacade_test.go @@ -11,20 +11,31 @@ import ( "github.com/stretchr/testify/assert" ) +func createInitialNodeFacadeArgs() ArgInitialNodeFacade { + return ArgInitialNodeFacade{ + ApiInterface: "127.0.0.1:8080", + PprofEnabled: true, + P2PPrometheusMetricsEnabled: false, + StatusMetricsHandler: &testscommon.StatusMetricsStub{}, + } +} + func TestInitialNodeFacade(t *testing.T) { t.Parallel() t.Run("nil status metrics should error", func(t *testing.T) { t.Parallel() - inf, err := NewInitialNodeFacade("127.0.0.1:8080", true, nil) + args := createInitialNodeFacadeArgs() + args.StatusMetricsHandler = nil + inf, err := NewInitialNodeFacade(args) assert.Equal(t, facade.ErrNilStatusMetrics, err) assert.Nil(t, inf) }) t.Run("should work", func(t *testing.T) { t.Parallel() - inf, err := NewInitialNodeFacade("127.0.0.1:8080", true, &testscommon.StatusMetricsStub{}) + inf, err := NewInitialNodeFacade(createInitialNodeFacadeArgs()) assert.Nil(t, err) assert.NotNil(t, inf) }) @@ -40,7 +51,9 @@ func TestInitialNodeFacade_AllMethodsShouldNotPanic(t *testing.T) { }() apiInterface := "127.0.0.1:7799" - inf, err := NewInitialNodeFacade(apiInterface, true, &testscommon.StatusMetricsStub{}) + args := createInitialNodeFacadeArgs() + args.ApiInterface = apiInterface + inf, err := NewInitialNodeFacade(args) assert.Nil(t, err) inf.SetSyncer(nil) @@ -325,6 +338,6 @@ func TestInitialNodeFacade_IsInterfaceNil(t *testing.T) { var inf *initialNodeFacade assert.True(t, inf.IsInterfaceNil()) - inf, _ = NewInitialNodeFacade("127.0.0.1:7799", true, &testscommon.StatusMetricsStub{}) + inf, _ = NewInitialNodeFacade(createInitialNodeFacadeArgs()) assert.False(t, inf.IsInterfaceNil()) } diff --git a/facade/nodeFacade.go b/facade/nodeFacade.go index 77ca17669a2..39c4849fd87 100644 --- a/facade/nodeFacade.go +++ b/facade/nodeFacade.go @@ -36,7 +36,7 @@ import ( const DefaultRestInterface = "localhost:8080" // DefaultRestPortOff is the default value that should be passed if it is desired -// to start the node without a REST endpoint available +// to start the node without a REST endpoint available const DefaultRestPortOff = "off" var log = logger.GetOrCreate("facade") @@ -163,7 +163,7 @@ func (nf *nodeFacade) RestAPIServerDebugMode() bool { // RestApiInterface returns the interface on which the rest API should start on, based on the config file provided. // The API will start on the DefaultRestInterface value unless a correct value is passed or -// the value is explicitly set to off, in which case it will not start at all +// the value is explicitly set to off, in which case it will not start at all func (nf *nodeFacade) RestApiInterface() string { if nf.config.RestApiInterface == "" { return DefaultRestInterface @@ -734,6 +734,11 @@ func (nf *nodeFacade) GetGasConfigs() (map[string]map[string]uint64, error) { return gasConfigs, nil } +// P2PPrometheusMetricsEnabled returns if p2p prometheus metrics should be enabled or not on the application +func (nf *nodeFacade) P2PPrometheusMetricsEnabled() bool { + return nf.config.P2PPrometheusMetricsEnabled +} + // IsInterfaceNil returns true if there is no value under the interface func (nf *nodeFacade) IsInterfaceNil() bool { return nf == nil diff --git a/facade/nodeFacade_test.go b/facade/nodeFacade_test.go index b2f069f673b..5f7fa056be7 100644 --- a/facade/nodeFacade_test.go +++ b/facade/nodeFacade_test.go @@ -50,8 +50,9 @@ func createMockArguments() ArgNodeFacade { TrieOperationsDeadlineMilliseconds: 1, }, FacadeConfig: config.FacadeConfig{ - RestApiInterface: "127.0.0.1:8080", - PprofEnabled: false, + RestApiInterface: "127.0.0.1:8080", + PprofEnabled: false, + P2PPrometheusMetricsEnabled: false, }, ApiRoutesConfig: config.ApiRoutesConfig{APIPackages: map[string]config.APIPackageConfig{ "node": { @@ -620,6 +621,16 @@ func TestNodeFacade_PprofEnabled(t *testing.T) { require.True(t, nf.PprofEnabled()) } +func TestNodeFacade_P2PPrometheusMetricsEnabled(t *testing.T) { + t.Parallel() + + arg := createMockArguments() + arg.FacadeConfig.P2PPrometheusMetricsEnabled = true + nf, _ := NewNodeFacade(arg) + + require.True(t, nf.P2PPrometheusMetricsEnabled()) +} + func TestNodeFacade_RestAPIServerDebugMode(t *testing.T) { t.Parallel() diff --git a/go.mod b/go.mod index 2f0e414e95c..50f0c56a8b6 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/multiversx/mx-chain-vm-v1_4-go v1.4.89 github.com/pelletier/go-toml v1.9.3 github.com/pkg/errors v0.9.1 + github.com/prometheus/client_golang v1.14.0 github.com/shirou/gopsutil v3.21.11+incompatible github.com/stretchr/testify v1.8.4 github.com/urfave/cli v1.22.10 @@ -140,7 +141,6 @@ require ( github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polydawn/refmt v0.89.0 // indirect - github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect diff --git a/node/nodeRunner.go b/node/nodeRunner.go index c0212f9837b..71cdc1b1beb 100644 --- a/node/nodeRunner.go +++ b/node/nodeRunner.go @@ -736,8 +736,9 @@ func (nr *nodeRunner) createApiFacade( RestAPIServerDebugMode: flagsConfig.EnableRestAPIServerDebugMode, WsAntifloodConfig: configs.GeneralConfig.WebServerAntiflood, FacadeConfig: config.FacadeConfig{ - RestApiInterface: flagsConfig.RestApiInterface, - PprofEnabled: flagsConfig.EnablePprof, + RestApiInterface: flagsConfig.RestApiInterface, + PprofEnabled: flagsConfig.EnablePprof, + P2PPrometheusMetricsEnabled: flagsConfig.P2PPrometheusMetricsEnabled, }, ApiRoutesConfig: *configs.ApiRoutesConfig, AccountsState: currentNode.stateComponents.AccountsAdapter(), @@ -768,7 +769,14 @@ func (nr *nodeRunner) createHttpServer(managedStatusCoreComponents mainFactory.S if check.IfNil(managedStatusCoreComponents) { return nil, ErrNilStatusHandler } - initialFacade, err := initial.NewInitialNodeFacade(nr.configs.FlagsConfig.RestApiInterface, nr.configs.FlagsConfig.EnablePprof, managedStatusCoreComponents.StatusMetrics()) + + argsInitialNodeFacade := initial.ArgInitialNodeFacade{ + ApiInterface: nr.configs.FlagsConfig.RestApiInterface, + PprofEnabled: nr.configs.FlagsConfig.EnablePprof, + P2PPrometheusMetricsEnabled: nr.configs.FlagsConfig.P2PPrometheusMetricsEnabled, + StatusMetricsHandler: managedStatusCoreComponents.StatusMetrics(), + } + initialFacade, err := initial.NewInitialNodeFacade(argsInitialNodeFacade) if err != nil { return nil, err }