From bcffe1ec160168c8933cfb90e381467f52975a42 Mon Sep 17 00:00:00 2001 From: Jerome Date: Sun, 19 Jun 2022 21:42:48 +1000 Subject: [PATCH] Give the XDC option to use emit metrics (#89) --- cmd/XDC/chaincmd.go | 7 +++++++ cmd/XDC/main.go | 7 +++++++ cmd/utils/flags.go | 27 ++++++++++++++++++++++++++ internal/debug/flags.go | 6 +++--- metrics/config.go | 33 +++++++++++++++++++++++++++++++ metrics/exp/exp.go | 43 +++++++++++++++++++++++++++++++++-------- 6 files changed, 112 insertions(+), 11 deletions(-) create mode 100644 metrics/config.go diff --git a/cmd/XDC/chaincmd.go b/cmd/XDC/chaincmd.go index 4bf9e27198..025eb19f42 100644 --- a/cmd/XDC/chaincmd.go +++ b/cmd/XDC/chaincmd.go @@ -26,6 +26,7 @@ import ( "time" "github.com/XinFinOrg/XDPoSChain/core/rawdb" + "github.com/XinFinOrg/XDPoSChain/metrics" "github.com/XinFinOrg/XDPoSChain/cmd/utils" "github.com/XinFinOrg/XDPoSChain/common" @@ -208,6 +209,12 @@ func importChain(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } + + // Start metrics export if enabled + utils.SetupMetrics(ctx) + // Start system runtime metrics collection + go metrics.CollectProcessMetrics(3 * time.Second) + stack, _ := makeFullNode(ctx) chain, chainDb := utils.MakeChain(ctx, stack) defer chainDb.Close() diff --git a/cmd/XDC/main.go b/cmd/XDC/main.go index 14ca0d1e6d..6e92867e54 100644 --- a/cmd/XDC/main.go +++ b/cmd/XDC/main.go @@ -119,6 +119,8 @@ var ( utils.RPCVirtualHostsFlag, utils.EthStatsURLFlag, utils.MetricsEnabledFlag, + utils.MetricsHTTPFlag, + utils.MetricsPortFlag, //utils.FakePoWFlag, //utils.NoCompactionFlag, //utils.GpoBlocksFlag, @@ -290,6 +292,11 @@ func startNode(ctx *cli.Context, stack *node.Node, cfg XDCConfig) { if ctx.GlobalBool(utils.LightModeFlag.Name) || ctx.GlobalString(utils.SyncModeFlag.Name) == "light" { utils.Fatalf("Light clients do not support staking") } + // Start metrics export if enabled + utils.SetupMetrics(ctx) + // Start system runtime metrics collection + go metrics.CollectProcessMetrics(3 * time.Second) + var ethereum *eth.Ethereum if err := stack.Service(ðereum); err != nil { utils.Fatalf("Ethereum service not running: %v", err) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index e1a49d48e2..179d351404 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -45,6 +45,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/metrics" + "github.com/XinFinOrg/XDPoSChain/metrics/exp" "github.com/XinFinOrg/XDPoSChain/node" "github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p/discover" @@ -355,6 +356,20 @@ var ( Name: "ethstats", Usage: "Reporting URL of a ethstats service (nodename:secret@host:port)", } + // MetricsHTTPFlag defines the endpoint for a stand-alone metrics HTTP endpoint. + // Since the pprof service enables sensitive/vulnerable behavior, this allows a user + // to enable a public-OK metrics endpoint without having to worry about ALSO exposing + // other profiling behavior or information. + MetricsHTTPFlag = cli.StringFlag{ + Name: "metrics.addr", + Usage: "Enable stand-alone metrics HTTP server listening interface", + Value: metrics.DefaultConfig.HTTP, + } + MetricsPortFlag = cli.IntFlag{ + Name: "metrics.port", + Usage: "Metrics HTTP server listening port", + Value: metrics.DefaultConfig.Port, + } MetricsEnabledFlag = cli.BoolFlag{ Name: metrics.MetricsEnabledFlag, Usage: "Enable metrics collection and reporting", @@ -1330,3 +1345,15 @@ func WalkMatch(root, pattern string) ([]string, error) { } return matches, nil } + +func SetupMetrics(ctx *cli.Context) { + if metrics.Enabled { + log.Info("Enabling metrics collection") + + if ctx.GlobalIsSet(MetricsHTTPFlag.Name) { + address := fmt.Sprintf("%s:%d", ctx.GlobalString(MetricsHTTPFlag.Name), ctx.GlobalInt(MetricsPortFlag.Name)) + log.Info("Enabling stand-alone metrics HTTP endpoint", "address", address) + exp.Setup(address) + } + } +} diff --git a/internal/debug/flags.go b/internal/debug/flags.go index 060ace6202..ecf1a918f8 100644 --- a/internal/debug/flags.go +++ b/internal/debug/flags.go @@ -91,9 +91,9 @@ var Flags = []cli.Flag{ //vmoduleFlag, //backtraceAtFlag, //debugFlag, - //pprofFlag, - //pprofAddrFlag, - //pprofPortFlag, + // pprofFlag, + // pprofAddrFlag, + // pprofPortFlag, //memprofilerateFlag, //blockprofilerateFlag, //cpuprofileFlag, diff --git a/metrics/config.go b/metrics/config.go new file mode 100644 index 0000000000..169c683a97 --- /dev/null +++ b/metrics/config.go @@ -0,0 +1,33 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of go-ethereum. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package metrics + +// Config contains the configuration for the metric collection. +type Config struct { + Enabled bool `toml:",omitempty"` + EnabledExpensive bool `toml:",omitempty"` + HTTP string `toml:",omitempty"` + Port int `toml:",omitempty"` +} + +// DefaultConfig is the default config for metrics used in go-ethereum. +var DefaultConfig = Config{ + Enabled: false, + EnabledExpensive: false, + HTTP: "127.0.0.1", + Port: 6060, +} diff --git a/metrics/exp/exp.go b/metrics/exp/exp.go index 32945dd466..253b4e9490 100644 --- a/metrics/exp/exp.go +++ b/metrics/exp/exp.go @@ -8,6 +8,7 @@ import ( "net/http" "sync" + "github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/metrics" ) @@ -50,6 +51,19 @@ func ExpHandler(r metrics.Registry) http.Handler { return http.HandlerFunc(e.expHandler) } +// Setup starts a dedicated metrics server at the given address. +// This function enables metrics reporting separate from pprof. +func Setup(address string) { + m := http.NewServeMux() + m.Handle("/debug/metrics", ExpHandler(metrics.DefaultRegistry)) + log.Info("Starting metrics server", "addr", fmt.Sprintf("http://%s/debug/metrics", address)) + go func() { + if err := http.ListenAndServe(address, m); err != nil { + log.Error("Failure in running metrics server", "err", err) + } + }() +} + func (exp *exp) getInt(name string) *expvar.Int { var v *expvar.Int exp.expvarLock.Lock() @@ -111,7 +125,7 @@ func (exp *exp) publishMeter(name string, metric metrics.Meter) { exp.getInt(name + ".count").Set(m.Count()) exp.getFloat(name + ".one-minute").Set(m.Rate1()) exp.getFloat(name + ".five-minute").Set(m.Rate5()) - exp.getFloat(name + ".fifteen-minute").Set((m.Rate15())) + exp.getFloat(name + ".fifteen-minute").Set(m.Rate15()) exp.getFloat(name + ".mean").Set(m.RateMean()) } @@ -134,21 +148,34 @@ func (exp *exp) publishTimer(name string, metric metrics.Timer) { exp.getFloat(name + ".mean-rate").Set(t.RateMean()) } +func (exp *exp) publishResettingTimer(name string, metric metrics.ResettingTimer) { + t := metric.Snapshot() + ps := t.Percentiles([]float64{50, 75, 95, 99}) + exp.getInt(name + ".count").Set(int64(len(t.Values()))) + exp.getFloat(name + ".mean").Set(t.Mean()) + exp.getInt(name + ".50-percentile").Set(ps[0]) + exp.getInt(name + ".75-percentile").Set(ps[1]) + exp.getInt(name + ".95-percentile").Set(ps[2]) + exp.getInt(name + ".99-percentile").Set(ps[3]) +} + func (exp *exp) syncToExpvar() { exp.registry.Each(func(name string, i interface{}) { - switch i.(type) { + switch i := i.(type) { case metrics.Counter: - exp.publishCounter(name, i.(metrics.Counter)) + exp.publishCounter(name, i) case metrics.Gauge: - exp.publishGauge(name, i.(metrics.Gauge)) + exp.publishGauge(name, i) case metrics.GaugeFloat64: - exp.publishGaugeFloat64(name, i.(metrics.GaugeFloat64)) + exp.publishGaugeFloat64(name, i) case metrics.Histogram: - exp.publishHistogram(name, i.(metrics.Histogram)) + exp.publishHistogram(name, i) case metrics.Meter: - exp.publishMeter(name, i.(metrics.Meter)) + exp.publishMeter(name, i) case metrics.Timer: - exp.publishTimer(name, i.(metrics.Timer)) + exp.publishTimer(name, i) + case metrics.ResettingTimer: + exp.publishResettingTimer(name, i) default: panic(fmt.Sprintf("unsupported type for '%s': %T", name, i)) }