Merge pull request #3454 from LeoHChen/prometheus

[prometheus] initial support of prometheus metrics
pull/3455/head
Leo Chen 4 years ago committed by GitHub
commit 8ce29ed100
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .gitignore
  2. 101
      api/service/prometheus/service.go
  3. 13
      cmd/harmony/config.go
  4. 9
      cmd/harmony/config_test.go
  5. 15
      cmd/harmony/default.go
  6. 38
      cmd/harmony/flags.go
  7. 104
      cmd/harmony/flags_test.go
  8. 13
      cmd/harmony/main.go
  9. 20
      consensus/consensus_service.go
  10. 4
      go.mod
  11. 38
      internal/configs/node/config.go
  12. 10
      internal/configs/node/network.go
  13. 12
      node/api.go

3
.gitignore vendored

@ -88,3 +88,6 @@ coverage.txt
# testdata directory
harmony_db_*
explorer_storage_*
# local blskeys for testing
.hmy/blskeys

@ -0,0 +1,101 @@
// Package prometheus defines a service which is used for metrics collection
// and health of a node in Harmony.
package prometheus
import (
"context"
"fmt"
"net/http"
"runtime/debug"
"runtime/pprof"
"time"
nodeconfig "github.com/harmony-one/harmony/internal/configs/node"
"github.com/harmony-one/harmony/internal/utils"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// Service provides Prometheus metrics via the /metrics route. This route will
// show all the metrics registered with the Prometheus DefaultRegisterer.
type Service struct {
server *http.Server
failStatus error
}
// Handler represents a path and handler func to serve on the same port as /metrics, /healthz, /goroutinez, etc.
type Handler struct {
Path string
Handler func(http.ResponseWriter, *http.Request)
}
var (
svc = &Service{}
)
// NewService sets up a new instance for a given address host:port.
// An empty host will match with any IP so an address like ":19000" is perfectly acceptable.
func NewService(config nodeconfig.PrometheusServerConfig, additionalHandlers ...Handler) {
if !config.HTTPEnabled {
utils.Logger().Info().Msg("Prometheus http server disabled...")
return
}
mux := http.NewServeMux()
mux.Handle("/metrics", promhttp.Handler())
mux.HandleFunc("/goroutinez", svc.goroutinezHandler)
// Register additional handlers.
for _, h := range additionalHandlers {
mux.HandleFunc(h.Path, h.Handler)
}
utils.Logger().Debug().Int("port", config.HTTPPort).
Str("ip", config.HTTPIp).
Msg("Starting Prometheus server")
endpoint := fmt.Sprintf("%s:%d", config.HTTPIp, config.HTTPPort)
svc.server = &http.Server{Addr: endpoint, Handler: mux}
svc.Start()
}
// StopService stop the Prometheus service
func StopService() error {
return svc.Stop()
}
func (s *Service) goroutinezHandler(w http.ResponseWriter, _ *http.Request) {
stack := debug.Stack()
if _, err := w.Write(stack); err != nil {
utils.Logger().Error().Err(err).Msg("Failed to write goroutines stack")
}
if err := pprof.Lookup("goroutine").WriteTo(w, 2); err != nil {
utils.Logger().Error().Err(err).Msg("Failed to write pprof goroutines")
}
}
// Start the prometheus service.
func (s *Service) Start() {
go func() {
utils.Logger().Info().Str("address", s.server.Addr).Msg("Starting prometheus service")
err := s.server.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
utils.Logger().Error().Msgf("Could not listen to host:port :%s: %v", s.server.Addr, err)
s.failStatus = err
}
}()
}
// Stop the service gracefully.
func (s *Service) Stop() error {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
return s.server.Shutdown(ctx)
}
// Status checks for any service failure conditions.
func (s *Service) Status() error {
if s.failStatus != nil {
return s.failStatus
}
return nil
}

@ -106,11 +106,14 @@ type sysConfig struct {
}
type httpConfig struct {
Enabled bool
IP string
Port int
RosettaEnabled bool
RosettaPort int
Enabled bool
IP string
Port int
RosettaEnabled bool
RosettaPort int
PrometheusEnabled bool
PrometheusIP string
PrometheusPort int
}
type wsConfig struct {

@ -30,7 +30,7 @@ func init() {
}
func TestV1_0_0Config(t *testing.T) {
testConfig := `Version = "1.0.0"
testConfig := `Version = "1.0.3"
[BLSKeys]
KMSConfigFile = ""
@ -55,6 +55,9 @@ func TestV1_0_0Config(t *testing.T) {
Enabled = true
IP = "127.0.0.1"
Port = 9500
PrometheusEnabled = true
PrometheusIP = "0.0.0.0"
PrometheusPort = 9900
[Log]
FileName = "harmony.log"
@ -105,8 +108,8 @@ func TestV1_0_0Config(t *testing.T) {
if config.P2P.IP != defaultConfig.P2P.IP {
t.Errorf("Expect default p2p IP if old config is provided")
}
if config.Version != "1.0.0" {
t.Errorf("Expected config version: 1.0.0, not %v", config.Version)
if config.Version != "1.0.3" {
t.Errorf("Expected config version: 1.0.3, not %v", config.Version)
}
config.Version = defaultConfig.Version // Shortcut for testing, value checked above
if !reflect.DeepEqual(config, defaultConfig) {

@ -2,7 +2,7 @@ package main
import nodeconfig "github.com/harmony-one/harmony/internal/configs/node"
const tomlConfigVersion = "1.0.2"
const tomlConfigVersion = "1.0.3"
const (
defNetworkType = nodeconfig.Mainnet
@ -25,11 +25,14 @@ var defaultConfig = harmonyConfig{
KeyFile: "./.hmykey",
},
HTTP: httpConfig{
Enabled: true,
RosettaEnabled: false,
IP: "127.0.0.1",
Port: nodeconfig.DefaultRPCPort,
RosettaPort: nodeconfig.DefaultRosettaPort,
Enabled: true,
RosettaEnabled: false,
IP: "127.0.0.1",
Port: nodeconfig.DefaultRPCPort,
RosettaPort: nodeconfig.DefaultRosettaPort,
PrometheusEnabled: true,
PrometheusIP: "0.0.0.0",
PrometheusPort: nodeconfig.DefaultPrometheusPort,
},
WS: wsConfig{
Enabled: true,

@ -51,6 +51,9 @@ var (
httpIPFlag,
httpPortFlag,
httpRosettaPortFlag,
httpPrometheusEnabledFlag,
httpPrometheusIPFlag,
httpPrometheusPortFlag,
}
wsFlags = []cli.Flag{
@ -445,10 +448,25 @@ var (
Usage: "rosetta port to listen for HTTP requests",
DefValue: defaultConfig.HTTP.RosettaPort,
}
httpPrometheusEnabledFlag = cli.BoolFlag{
Name: "http.prometheus",
Usage: "enable HTTP / Prometheus requests",
DefValue: defaultConfig.HTTP.PrometheusEnabled,
}
httpPrometheusIPFlag = cli.StringFlag{
Name: "http.prometheus.ip",
Usage: "ip address to listen for prometheus service",
DefValue: defaultConfig.HTTP.PrometheusIP,
}
httpPrometheusPortFlag = cli.IntFlag{
Name: "http.prometheus.port",
Usage: "prometheus port to listen for HTTP requests",
DefValue: defaultConfig.HTTP.PrometheusPort,
}
)
func applyHTTPFlags(cmd *cobra.Command, config *harmonyConfig) {
var isRPCSpecified, isRosettaSpecified bool
var isRPCSpecified, isRosettaSpecified, isPrometheusSpecified bool
if cli.IsFlagChanged(cmd, httpIPFlag) {
config.HTTP.IP = cli.GetStringFlagValue(cmd, httpIPFlag)
@ -465,6 +483,16 @@ func applyHTTPFlags(cmd *cobra.Command, config *harmonyConfig) {
isRosettaSpecified = true
}
if cli.IsFlagChanged(cmd, httpPrometheusIPFlag) {
config.HTTP.PrometheusIP = cli.GetStringFlagValue(cmd, httpPrometheusIPFlag)
isPrometheusSpecified = true
}
if cli.IsFlagChanged(cmd, httpPrometheusPortFlag) {
config.HTTP.PrometheusPort = cli.GetIntFlagValue(cmd, httpPrometheusPortFlag)
isPrometheusSpecified = true
}
if cli.IsFlagChanged(cmd, httpRosettaEnabledFlag) {
config.HTTP.RosettaEnabled = cli.GetBoolFlagValue(cmd, httpRosettaEnabledFlag)
} else if isRosettaSpecified {
@ -476,6 +504,13 @@ func applyHTTPFlags(cmd *cobra.Command, config *harmonyConfig) {
} else if isRPCSpecified {
config.HTTP.Enabled = true
}
if cli.IsFlagChanged(cmd, httpPrometheusEnabledFlag) {
config.HTTP.PrometheusEnabled = cli.GetBoolFlagValue(cmd, httpPrometheusEnabledFlag)
} else if isPrometheusSpecified {
config.HTTP.PrometheusEnabled = true
}
}
// ws flags
@ -1135,6 +1170,7 @@ func applyLegacyMiscFlags(cmd *cobra.Command, config *harmonyConfig) {
config.P2P.Port = legacyPort
config.HTTP.Port = nodeconfig.GetRPCHTTPPortFromBase(legacyPort)
config.HTTP.RosettaPort = nodeconfig.GetRosettaHTTPPortFromBase(legacyPort)
config.HTTP.PrometheusPort = nodeconfig.GetPrometheusHTTPPortFromBase(legacyPort)
config.WS.Port = nodeconfig.GetWSPortFromBase(legacyPort)
}

@ -55,11 +55,14 @@ func TestHarmonyFlags(t *testing.T) {
KeyFile: defaultConfig.P2P.KeyFile,
},
HTTP: httpConfig{
Enabled: true,
IP: "127.0.0.1",
Port: 9500,
RosettaEnabled: false,
RosettaPort: 9700,
Enabled: true,
IP: "127.0.0.1",
Port: 9500,
RosettaEnabled: false,
RosettaPort: 9700,
PrometheusEnabled: true,
PrometheusIP: "0.0.0.0",
PrometheusPort: 9900,
},
WS: wsConfig{
Enabled: true,
@ -353,51 +356,92 @@ func TestRPCFlags(t *testing.T) {
{
args: []string{"--http=false"},
expConfig: httpConfig{
Enabled: false,
RosettaEnabled: false,
IP: defaultConfig.HTTP.IP,
Port: defaultConfig.HTTP.Port,
RosettaPort: defaultConfig.HTTP.RosettaPort,
Enabled: false,
RosettaEnabled: false,
IP: defaultConfig.HTTP.IP,
Port: defaultConfig.HTTP.Port,
RosettaPort: defaultConfig.HTTP.RosettaPort,
PrometheusEnabled: true,
PrometheusIP: defaultConfig.HTTP.PrometheusIP,
PrometheusPort: defaultConfig.HTTP.PrometheusPort,
},
},
{
args: []string{"--http.ip", "8.8.8.8", "--http.port", "9001"},
expConfig: httpConfig{
Enabled: true,
RosettaEnabled: false,
IP: "8.8.8.8",
Port: 9001,
RosettaPort: defaultConfig.HTTP.RosettaPort,
Enabled: true,
RosettaEnabled: false,
IP: "8.8.8.8",
Port: 9001,
RosettaPort: defaultConfig.HTTP.RosettaPort,
PrometheusEnabled: true,
PrometheusIP: defaultConfig.HTTP.PrometheusIP,
PrometheusPort: defaultConfig.HTTP.PrometheusPort,
},
},
{
args: []string{"--http.ip", "8.8.8.8", "--http.port", "9001", "--http.rosetta.port", "10001"},
expConfig: httpConfig{
Enabled: true,
RosettaEnabled: true,
IP: "8.8.8.8",
Port: 9001,
RosettaPort: 10001,
Enabled: true,
RosettaEnabled: true,
IP: "8.8.8.8",
Port: 9001,
RosettaPort: 10001,
PrometheusEnabled: true,
PrometheusIP: defaultConfig.HTTP.PrometheusIP,
PrometheusPort: defaultConfig.HTTP.PrometheusPort,
},
},
{
args: []string{"--http.ip", "8.8.8.8", "--http.rosetta.port", "10001"},
expConfig: httpConfig{
Enabled: true,
RosettaEnabled: true,
IP: "8.8.8.8",
Port: defaultConfig.HTTP.Port,
RosettaPort: 10001,
Enabled: true,
RosettaEnabled: true,
IP: "8.8.8.8",
Port: defaultConfig.HTTP.Port,
RosettaPort: 10001,
PrometheusEnabled: true,
PrometheusIP: defaultConfig.HTTP.PrometheusIP,
PrometheusPort: defaultConfig.HTTP.PrometheusPort,
},
},
{
args: []string{"--ip", "8.8.8.8", "--port", "9001", "--public_rpc"},
expConfig: httpConfig{
Enabled: true,
RosettaEnabled: false,
IP: nodeconfig.DefaultPublicListenIP,
Port: 9501,
RosettaPort: 9701,
Enabled: true,
RosettaEnabled: false,
IP: nodeconfig.DefaultPublicListenIP,
Port: 9501,
RosettaPort: 9701,
PrometheusEnabled: true,
PrometheusIP: defaultConfig.HTTP.PrometheusIP,
PrometheusPort: defaultConfig.HTTP.PrometheusPort + 1,
},
},
{
args: []string{"--http.ip", "8.8.8.8", "--http.prometheus.port", "20001"},
expConfig: httpConfig{
Enabled: true,
RosettaEnabled: false,
IP: "8.8.8.8",
Port: defaultConfig.HTTP.Port,
RosettaPort: defaultConfig.HTTP.RosettaPort,
PrometheusEnabled: true,
PrometheusIP: defaultConfig.HTTP.PrometheusIP,
PrometheusPort: 20001,
},
},
{
args: []string{"--http.prometheus.ip", "8.8.8.8", "--http.prometheus.port", "20001"},
expConfig: httpConfig{
Enabled: true,
RosettaEnabled: false,
IP: defaultConfig.HTTP.IP,
Port: defaultConfig.HTTP.Port,
RosettaPort: defaultConfig.HTTP.RosettaPort,
PrometheusEnabled: true,
PrometheusIP: "8.8.8.8",
PrometheusPort: 20001,
},
},
}

@ -329,6 +329,13 @@ func setupNodeAndRun(hc harmonyConfig) {
HTTPPort: hc.HTTP.RosettaPort,
}
// Pares Prometheus config
nodeConfig.PrometheusServer = nodeconfig.PrometheusServerConfig{
HTTPEnabled: hc.HTTP.PrometheusEnabled,
HTTPIp: hc.HTTP.PrometheusIP,
HTTPPort: hc.HTTP.PrometheusPort,
}
if hc.Revert != nil && hc.Revert.RevertBefore != 0 && hc.Revert.RevertTo != 0 {
chain := currentNode.Blockchain()
if hc.Revert.RevertBeacon {
@ -387,6 +394,12 @@ func setupNodeAndRun(hc harmonyConfig) {
Msg("Start Rosetta failed")
}
if err := currentNode.StartPrometheus(); err != nil {
utils.Logger().Warn().
Err(err).
Msg("Start Prometheus failed")
}
if err := currentNode.BootstrapConsensus(); err != nil {
fmt.Println("could not bootstrap consensus", err.Error())
if !currentNode.NodeConfig.IsOffline {

@ -2,6 +2,7 @@ package consensus
import (
"math/big"
"sync"
"sync/atomic"
"time"
@ -28,6 +29,11 @@ import (
"github.com/rs/zerolog"
)
var (
logOnce sync.Once
logger zerolog.Logger
)
// WaitForNewRandomness listens to the RndChannel to receive new VDF randomness.
func (consensus *Consensus) WaitForNewRandomness() {
go func() {
@ -592,11 +598,13 @@ func (consensus *Consensus) selfCommit(payload []byte) error {
// getLogger returns logger for consensus contexts added
func (consensus *Consensus) getLogger() *zerolog.Logger {
logger := utils.Logger().With().
Uint64("myBlock", consensus.blockNum).
Uint64("myViewID", consensus.GetCurBlockViewID()).
Str("phase", consensus.phase.String()).
Str("mode", consensus.current.Mode().String()).
Logger()
logOnce.Do(func() {
logger = utils.Logger().With().
Uint64("myBlock", consensus.blockNum).
Uint64("myViewID", consensus.GetCurBlockViewID()).
Str("phase", consensus.phase.String()).
Str("mode", consensus.current.Mode().String()).
Logger()
})
return &logger
}

@ -18,8 +18,9 @@ require (
github.com/ethereum/go-ethereum v1.9.21
github.com/fjl/memsize v0.0.0-20180929194037-2a09253e352a // indirect
github.com/garslo/gogen v0.0.0-20170307003452-d6ebae628c7c // indirect
github.com/golang/gddo v0.0.0-20200831202555-721e228c7686
github.com/golang/mock v1.4.0
github.com/golang/protobuf v1.4.2
github.com/golang/protobuf v1.4.3
github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26 // indirect
github.com/golangci/golangci-lint v1.22.2
github.com/gorilla/mux v1.8.0
@ -42,6 +43,7 @@ require (
github.com/pborman/uuid v1.2.0
github.com/pelletier/go-toml v1.2.0
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.8.0
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0
github.com/rjeczalik/notify v0.9.2
github.com/rs/cors v1.7.0 // indirect

@ -68,21 +68,22 @@ var peerID peer.ID // PeerID of the node
// ConfigType is the structure of all node related configuration variables
type ConfigType struct {
// The three groupID design, please refer to https://github.com/harmony-one/harmony/blob/master/node/node.md#libp2p-integration
beacon GroupID // the beacon group ID
group GroupID // the group ID of the shard (note: for beacon chain node, the beacon and shard group are the same)
client GroupID // the client group ID of the shard
isClient bool // whether this node is a client node, such as wallet
ShardID uint32 // ShardID of this node; TODO ek – revisit when resharding
role Role // Role of the node
Port string // Port of the node.
IP string // IP of the node.
RPCServer RPCServerConfig // RPC server port and ip
RosettaServer RosettaServerConfig // rosetta server port and ip
IsOffline bool
NtpServer string
StringRole string
P2PPriKey p2p_crypto.PrivKey
ConsensusPriKey multibls.PrivateKeys
beacon GroupID // the beacon group ID
group GroupID // the group ID of the shard (note: for beacon chain node, the beacon and shard group are the same)
client GroupID // the client group ID of the shard
isClient bool // whether this node is a client node, such as wallet
ShardID uint32 // ShardID of this node; TODO ek – revisit when resharding
role Role // Role of the node
Port string // Port of the node.
IP string // IP of the node.
RPCServer RPCServerConfig // RPC server port and ip
RosettaServer RosettaServerConfig // rosetta server port and ip
PrometheusServer PrometheusServerConfig // prometheus server port and ip
IsOffline bool
NtpServer string
StringRole string
P2PPriKey p2p_crypto.PrivKey
ConsensusPriKey multibls.PrivateKeys
// Database directory
DBDir string
networkType NetworkType
@ -114,6 +115,13 @@ type RosettaServerConfig struct {
HTTPPort int
}
// PrometheusServerConfig is the config for the prometheus server
type PrometheusServerConfig struct {
HTTPEnabled bool
HTTPIp string
HTTPPort int
}
// configs is a list of node configuration.
// It has at least one configuration.
// The first one is the default, global node configuration

@ -55,6 +55,8 @@ const (
DefaultRosettaPort = 9700
// DefaultWSPort is the default port for web socket endpoint. The actual port used is
DefaultWSPort = 9800
// DefaultPrometheusPort is the default prometheus port. The actual port used is 9000+900
DefaultPrometheusPort = 9900
)
const (
@ -66,6 +68,9 @@ const (
// rpcWSPortOffSet is the port offset for RPC websocket requests
rpcWSPortOffSet = 800
// prometheusHTTPPortOffset is the port offset for prometheus HTTP requests
prometheusHTTPPortOffset = 900
)
// GetDefaultBootNodes get the default bootnode with the given network type
@ -123,3 +128,8 @@ func GetRosettaHTTPPortFromBase(basePort int) int {
func GetWSPortFromBase(basePort int) int {
return basePort + rpcWSPortOffSet
}
// GetPrometheusHTTPPortFromBase return the prometheus HTTP port from base port
func GetPrometheusHTTPPortFromBase(basePort int) int {
return basePort + prometheusHTTPPortOffset
}

@ -2,6 +2,7 @@ package node
import (
"github.com/ethereum/go-ethereum/rpc"
"github.com/harmony-one/harmony/api/service/prometheus"
"github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/hmy"
"github.com/harmony-one/harmony/rosetta"
@ -81,6 +82,17 @@ func (node *Node) StopRPC() error {
return hmy_rpc.StopServers()
}
// StartPrometheus start promtheus metrics service
func (node *Node) StartPrometheus() error {
prometheus.NewService(node.NodeConfig.PrometheusServer)
return nil
}
// StopPrometheus stop prometheus metrics service
func (node *Node) StopPrometheus() error {
return prometheus.StopService()
}
// StartRosetta start rosetta service
func (node *Node) StartRosetta() error {
harmony := hmy.New(node, node.TxPool, node.CxPool, node.Consensus.ShardID)

Loading…
Cancel
Save