Skip to content

Commit

Permalink
Merge pull request #497 from c98tristan/feat/metrics
Browse files Browse the repository at this point in the history
metrics, core: add Prometheus metrics collector, add some metrics support
  • Loading branch information
tungng98 authored Jan 7, 2025
2 parents 926bb45 + 5e3a8b6 commit 78e8ce0
Show file tree
Hide file tree
Showing 15 changed files with 684 additions and 43 deletions.
12 changes: 11 additions & 1 deletion cmd/tomo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ var (
utils.RPCCORSDomainFlag,
utils.RPCVirtualHostsFlag,
utils.EthStatsURLFlag,
utils.MetricsEnabledFlag,
//utils.FakePoWFlag,
//utils.NoCompactionFlag,
//utils.GpoBlocksFlag,
Expand Down Expand Up @@ -149,6 +148,12 @@ var (
utils.WhisperMaxMessageSizeFlag,
utils.WhisperMinPOWFlag,
}
metricsFlags = []cli.Flag{
utils.MetricsEnabledFlag,
utils.MetricsEnabledExpensiveFlag,
utils.MetricsHTTPFlag,
utils.MetricsPortFlag,
}
)

func init() {
Expand Down Expand Up @@ -182,12 +187,17 @@ func init() {
app.Flags = append(app.Flags, consoleFlags...)
app.Flags = append(app.Flags, debug.Flags...)
app.Flags = append(app.Flags, whisperFlags...)
app.Flags = append(app.Flags, metricsFlags...)

app.Before = func(ctx *cli.Context) error {
runtime.GOMAXPROCS(runtime.NumCPU())
if err := debug.Setup(ctx); err != nil {
return err
}

// Start metrics export if enabled
utils.SetupMetrics(ctx)

// Start system runtime metrics collection
go metrics.CollectProcessMetrics(3 * time.Second)

Expand Down
6 changes: 6 additions & 0 deletions cmd/tomo/usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,12 @@ var AppHelpFlagGroups = []flagGroup{
//utils.NoCompactionFlag,
}, debug.Flags...),
},

{
Name: "METRICS AND STATS",
Flags: metricsFlags,
},

//{
// Name: "WHISPER (EXPERIMENTAL)",
// Flags: whisperFlags,
Expand Down
33 changes: 32 additions & 1 deletion cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import (
"github.com/tomochain/tomochain/ethdb"
"github.com/tomochain/tomochain/log"
"github.com/tomochain/tomochain/metrics"
"github.com/tomochain/tomochain/metrics/exp"
"github.com/tomochain/tomochain/node"
"github.com/tomochain/tomochain/p2p"
"github.com/tomochain/tomochain/p2p/discover"
Expand Down Expand Up @@ -356,9 +357,27 @@ var (
Usage: "Reporting URL of a ethstats service (nodename:secret@host:port)",
}
MetricsEnabledFlag = cli.BoolFlag{
Name: metrics.MetricsEnabledFlag,
Name: "metrics",
Usage: "Enable metrics collection and reporting",
}
MetricsEnabledExpensiveFlag = &cli.BoolFlag{
Name: "metrics.expensive",
Usage: "Enable expensive metrics collection and reporting",
}
// 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: "127.0.0.1",
}
MetricsPortFlag = cli.IntFlag{
Name: "metrics.port",
Usage: "Metrics HTTP server listening port",
Value: 6060,
}
FakePoWFlag = cli.BoolFlag{
Name: "fakepow",
Usage: "Disables proof-of-work verification",
Expand Down Expand Up @@ -1192,6 +1211,18 @@ func SetupNetwork(ctx *cli.Context) {
params.TargetGasLimit = ctx.GlobalUint64(TargetGasLimitFlag.Name)
}

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)
}
}
}

// MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails.
func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database {
var (
Expand Down
24 changes: 24 additions & 0 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ import (
)

var (
accountReadTimer = metrics.NewRegisteredTimer("chain/account/reads", nil)
accountHashTimer = metrics.NewRegisteredTimer("chain/account/hashes", nil)
accountUpdateTimer = metrics.NewRegisteredTimer("chain/account/updates", nil)
accountCommitTimer = metrics.NewRegisteredTimer("chain/account/commits", nil)

storageReadTimer = metrics.NewRegisteredTimer("chain/storage/reads", nil)
storageHashTimer = metrics.NewRegisteredTimer("chain/storage/hashes", nil)
storageUpdateTimer = metrics.NewRegisteredTimer("chain/storage/updates", nil)
storageCommitTimer = metrics.NewRegisteredTimer("chain/storage/commits", nil)

blockInsertTimer = metrics.NewRegisteredTimer("chain/inserts", nil)
CheckpointCh = make(chan int)
ErrNoGenesis = errors.New("Genesis not found in chain")
Expand Down Expand Up @@ -1651,6 +1661,20 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty
if err != nil {
return i, events, coalescedLogs, err
}
// Update the metrics subsystem with all the measurements
accountReadTimer.Update(statedb.AccountReads)
accountHashTimer.Update(statedb.AccountHashes)
accountUpdateTimer.Update(statedb.AccountUpdates)
accountCommitTimer.Update(statedb.AccountCommits)

storageReadTimer.Update(statedb.StorageReads)
storageHashTimer.Update(statedb.StorageHashes)
storageUpdateTimer.Update(statedb.StorageUpdates)
storageCommitTimer.Update(statedb.StorageCommits)

trieAccess := statedb.AccountReads + statedb.AccountHashes + statedb.AccountUpdates + statedb.AccountCommits
trieAccess += statedb.StorageReads + statedb.StorageHashes + statedb.StorageUpdates + statedb.StorageCommits

if bc.chainConfig.Posv != nil {
c := bc.engine.(*posv.Posv)
coinbase := c.Signer()
Expand Down
24 changes: 24 additions & 0 deletions core/state/state_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import (
"fmt"
"io"
"math/big"
"time"

"github.com/tomochain/tomochain/common"
"github.com/tomochain/tomochain/crypto"
"github.com/tomochain/tomochain/metrics"
"github.com/tomochain/tomochain/rlp"
)

Expand Down Expand Up @@ -170,6 +172,10 @@ func (c *stateObject) getTrie(db Database) Trie {

func (self *stateObject) GetCommittedState(db Database, key common.Hash) common.Hash {
value := common.Hash{}
// Track the amount of time wasted on reading the storage trie
if metrics.EnabledExpensive {
defer func(start time.Time) { self.db.StorageReads += time.Since(start) }(time.Now())
}
// Load from DB in case it is missing.
enc, err := self.getTrie(db).TryGet(key[:])
if err != nil {
Expand Down Expand Up @@ -232,6 +238,12 @@ func (self *stateObject) setState(key, value common.Hash) {

// updateTrie writes cached storage modifications into the object's storage trie.
func (self *stateObject) updateTrie(db Database) Trie {
// Track the amount of time wasted on updating the storage trie
if metrics.EnabledExpensive {
defer func(start time.Time) { self.db.StorageUpdates += time.Since(start) }(time.Now())
}

// Update all the dirty slots in the trie
tr := self.getTrie(db)
for key, value := range self.dirtyStorage {
delete(self.dirtyStorage, key)
Expand All @@ -249,6 +261,12 @@ func (self *stateObject) updateTrie(db Database) Trie {
// UpdateRoot sets the trie root to the current root hash of
func (self *stateObject) updateRoot(db Database) {
self.updateTrie(db)

// Track the amount of time wasted on hashing the storage trie
if metrics.EnabledExpensive {
defer func(start time.Time) { self.db.StorageHashes += time.Since(start) }(time.Now())
}

self.data.Root = self.trie.Hash()
}

Expand All @@ -259,6 +277,12 @@ func (self *stateObject) CommitTrie(db Database) error {
if self.dbErr != nil {
return self.dbErr
}

// Track the amount of time wasted on committing the storage trie
if metrics.EnabledExpensive {
defer func(start time.Time) { self.db.StorageCommits += time.Since(start) }(time.Now())
}

root, err := self.trie.Commit(nil)
if err == nil {
self.data.Root = root
Expand Down
56 changes: 47 additions & 9 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ import (
"math/big"
"sort"
"sync"
"time"

"github.com/tomochain/tomochain/common"
"github.com/tomochain/tomochain/core/types"
"github.com/tomochain/tomochain/crypto"
"github.com/tomochain/tomochain/log"
"github.com/tomochain/tomochain/metrics"
"github.com/tomochain/tomochain/rlp"
"github.com/tomochain/tomochain/trie"
)
Expand Down Expand Up @@ -80,6 +82,16 @@ type StateDB struct {
validRevisions []revision
nextRevisionId int

// Measurements gathered during execution for debugging purposes
AccountReads time.Duration
AccountHashes time.Duration
AccountUpdates time.Duration
AccountCommits time.Duration
StorageReads time.Duration
StorageHashes time.Duration
StorageUpdates time.Duration
StorageCommits time.Duration

lock sync.Mutex
}

Expand Down Expand Up @@ -359,7 +371,13 @@ func (self *StateDB) Suicide(addr common.Address) bool {

// updateStateObject writes the given object to the trie.
func (self *StateDB) updateStateObject(stateObject *stateObject) {
// Track the amount of time wasted on updating the account from the trie
if metrics.EnabledExpensive {
defer func(start time.Time) { self.AccountUpdates += time.Since(start) }(time.Now())
}
// Encode the account and update the account trie
addr := stateObject.Address()

data, err := rlp.EncodeToBytes(stateObject)
if err != nil {
panic(fmt.Errorf("can't encode object at %x: %v", addr[:], err))
Expand All @@ -369,6 +387,11 @@ func (self *StateDB) updateStateObject(stateObject *stateObject) {

// deleteStateObject removes the given object from the state trie.
func (self *StateDB) deleteStateObject(stateObject *stateObject) {
// Track the amount of time wasted on deleting the account from the trie
if metrics.EnabledExpensive {
defer func(start time.Time) { self.AccountUpdates += time.Since(start) }(time.Now())
}
// Delete the account from the trie
stateObject.deleted = true
addr := stateObject.Address()
self.setError(self.trie.TryDelete(addr[:]))
Expand All @@ -383,19 +406,24 @@ func (self *StateDB) DeleteAddress(addr common.Address) {
}

// Retrieve a state object given my the address. Returns nil if not found.
func (self *StateDB) getStateObject(addr common.Address) (stateObject *stateObject) {
func (s *StateDB) getStateObject(addr common.Address) (stateObject *stateObject) {
// Prefer 'live' objects.
if obj := self.stateObjects[addr]; obj != nil {
if obj := s.stateObjects[addr]; obj != nil {
if obj.deleted {
return nil
}
return obj
}

// Track the amount of time wasted on loading the object from the database
if metrics.EnabledExpensive {
defer func(start time.Time) { s.AccountReads += time.Since(start) }(time.Now())
}

// Load the object from the database.
enc, err := self.trie.TryGet(addr[:])
enc, err := s.trie.TryGet(addr[:])
if len(enc) == 0 {
self.setError(err)
s.setError(err)
return nil
}
var data Account
Expand All @@ -404,8 +432,8 @@ func (self *StateDB) getStateObject(addr common.Address) (stateObject *stateObje
return nil
}
// Insert into the live set.
obj := newObject(self, addr, data, self.MarkStateObjectDirty)
self.setStateObject(obj)
obj := newObject(s, addr, data, s.MarkStateObjectDirty)
s.setStateObject(obj)
return obj
}

Expand Down Expand Up @@ -449,8 +477,8 @@ func (self *StateDB) createObject(addr common.Address) (newobj, prev *stateObjec
// CreateAccount is called during the EVM CREATE operation. The situation might arise that
// a contract does the following:
//
// 1. sends funds to sha(account ++ (nonce + 1))
// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1)
// 1. sends funds to sha(account ++ (nonce + 1))
// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1)
//
// Carrying over the balance ensures that Ether doesn't disappear.
func (self *StateDB) CreateAccount(addr common.Address) {
Expand Down Expand Up @@ -569,6 +597,12 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
// goes into transaction receipts.
func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
s.Finalise(deleteEmptyObjects)

// Track the amount of time wasted on hashing the account trie
if metrics.EnabledExpensive {
defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now())
}

return s.trie.Hash()
}

Expand Down Expand Up @@ -611,7 +645,7 @@ func (s *StateDB) clearJournalAndRefund() {
func (s *StateDB) Commit(deleteEmptyObjects bool) (root common.Hash, err error) {
defer s.clearJournalAndRefund()

// Commit objects to the trie.
// Commit objects to the trie, measuring the elapsed time
for addr, stateObject := range s.stateObjects {
_, isDirty := s.stateObjectsDirty[addr]
switch {
Expand All @@ -634,6 +668,10 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (root common.Hash, err error)
}
delete(s.stateObjectsDirty, addr)
}
// Write the account trie changes, measuring the amount of wasted time
if metrics.EnabledExpensive {
defer func(start time.Time) { s.AccountCommits += time.Since(start) }(time.Now())
}
// Write trie changes.
root, err = s.trie.Commit(func(leaf []byte, parent common.Hash) error {
var account Account
Expand Down
Loading

0 comments on commit 78e8ce0

Please sign in to comment.