Skip to content

Commit 042c47c

Browse files
authored
core: log detailed statistics for slow block (#32812)
This PR introduces a new debug feature, logging the slow blocks with detailed performance statistics, such as state read, EVM execution and so on. Notably, the detailed performance statistics of slow blocks won't be logged during the sync to not overwhelm users. Specifically, the statistics are only logged if there is a single block processed. Example output ``` ########## SLOW BLOCK ######### Block: 23537063 (0xa7f878611c2dd27f245fc41107d12ebcf06b4e289f1d6acf44d49a169554ee09) txs: 248, mgasps: 202.99 EVM execution: 63.295ms Validation: 1.130ms Account read: 6.634ms(648) Storage read: 17.391ms(1434) State hash: 6.722ms DB commit: 3.260ms Block write: 1.954ms Total: 99.094ms State read cache: account (hit: 622, miss: 26), storage (hit: 1325, miss: 109) ############################## ```
1 parent da3822d commit 042c47c

File tree

12 files changed

+293
-87
lines changed

12 files changed

+293
-87
lines changed

cmd/geth/chaincmd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ if one is set. Otherwise it prints the genesis from the datadir.`,
9696
utils.CacheNoPrefetchFlag,
9797
utils.CachePreimagesFlag,
9898
utils.NoCompactionFlag,
99+
utils.LogSlowBlockFlag,
99100
utils.MetricsEnabledFlag,
100101
utils.MetricsEnabledExpensiveFlag,
101102
utils.MetricsHTTPFlag,

cmd/geth/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ var (
157157
utils.BeaconGenesisTimeFlag,
158158
utils.BeaconCheckpointFlag,
159159
utils.BeaconCheckpointFileFlag,
160+
utils.LogSlowBlockFlag,
160161
}, utils.NetworkFlags, utils.DatabaseFlags)
161162

162163
rpcFlags = []cli.Flag{

cmd/utils/flags.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,12 @@ var (
672672
Usage: "Disables db compaction after import",
673673
Category: flags.LoggingCategory,
674674
}
675+
LogSlowBlockFlag = &cli.DurationFlag{
676+
Name: "debug.logslowblock",
677+
Usage: "Block execution time threshold beyond which detailed statistics will be logged (0 means disable)",
678+
Value: ethconfig.Defaults.SlowBlockThreshold,
679+
Category: flags.LoggingCategory,
680+
}
675681

676682
// MISC settings
677683
SyncTargetFlag = &cli.StringFlag{
@@ -1720,6 +1726,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
17201726
if ctx.IsSet(LogNoHistoryFlag.Name) {
17211727
cfg.LogNoHistory = true
17221728
}
1729+
if ctx.IsSet(LogSlowBlockFlag.Name) {
1730+
cfg.SlowBlockThreshold = ctx.Duration(LogSlowBlockFlag.Name)
1731+
}
17231732
if ctx.IsSet(LogExportCheckpointsFlag.Name) {
17241733
cfg.LogExportCheckpoints = ctx.String(LogExportCheckpointsFlag.Name)
17251734
}
@@ -2299,6 +2308,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
22992308
Preimages: ctx.Bool(CachePreimagesFlag.Name),
23002309
StateScheme: scheme,
23012310
StateHistory: ctx.Uint64(StateHistoryFlag.Name),
2311+
23022312
// Disable transaction indexing/unindexing.
23032313
TxLookupLimit: -1,
23042314

@@ -2310,6 +2320,9 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
23102320

23112321
// Enable state size tracking if enabled
23122322
StateSizeTracking: ctx.Bool(StateSizeTrackingFlag.Name),
2323+
2324+
// Configure the slow block statistic logger
2325+
SlowBlockThreshold: ctx.Duration(LogSlowBlockFlag.Name),
23132326
}
23142327
if options.ArchiveMode && !options.Preimages {
23152328
options.Preimages = true

core/blockchain.go

Lines changed: 84 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,10 @@ type BlockChainConfig struct {
198198

199199
// StateSizeTracking indicates whether the state size tracking is enabled.
200200
StateSizeTracking bool
201+
202+
// SlowBlockThreshold is the block execution time threshold beyond which
203+
// detailed statistics will be logged.
204+
SlowBlockThreshold time.Duration
201205
}
202206

203207
// DefaultConfig returns the default config.
@@ -337,7 +341,8 @@ type BlockChain struct {
337341
logger *tracing.Hooks
338342
stateSizer *state.SizeTracker // State size tracking
339343

340-
lastForkReadyAlert time.Time // Last time there was a fork readiness print out
344+
lastForkReadyAlert time.Time // Last time there was a fork readiness print out
345+
slowBlockThreshold time.Duration // Block execution time threshold beyond which detailed statistics will be logged
341346
}
342347

343348
// NewBlockChain returns a fully initialised block chain using information
@@ -372,19 +377,20 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
372377
log.Info("")
373378

374379
bc := &BlockChain{
375-
chainConfig: chainConfig,
376-
cfg: cfg,
377-
db: db,
378-
triedb: triedb,
379-
triegc: prque.New[int64, common.Hash](nil),
380-
chainmu: syncx.NewClosableMutex(),
381-
bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit),
382-
bodyRLPCache: lru.NewCache[common.Hash, rlp.RawValue](bodyCacheLimit),
383-
receiptsCache: lru.NewCache[common.Hash, []*types.Receipt](receiptsCacheLimit),
384-
blockCache: lru.NewCache[common.Hash, *types.Block](blockCacheLimit),
385-
txLookupCache: lru.NewCache[common.Hash, txLookup](txLookupCacheLimit),
386-
engine: engine,
387-
logger: cfg.VmConfig.Tracer,
380+
chainConfig: chainConfig,
381+
cfg: cfg,
382+
db: db,
383+
triedb: triedb,
384+
triegc: prque.New[int64, common.Hash](nil),
385+
chainmu: syncx.NewClosableMutex(),
386+
bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit),
387+
bodyRLPCache: lru.NewCache[common.Hash, rlp.RawValue](bodyCacheLimit),
388+
receiptsCache: lru.NewCache[common.Hash, []*types.Receipt](receiptsCacheLimit),
389+
blockCache: lru.NewCache[common.Hash, *types.Block](blockCacheLimit),
390+
txLookupCache: lru.NewCache[common.Hash, txLookup](txLookupCacheLimit),
391+
engine: engine,
392+
logger: cfg.VmConfig.Tracer,
393+
slowBlockThreshold: cfg.SlowBlockThreshold,
388394
}
389395
bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.insertStopped)
390396
if err != nil {
@@ -1847,7 +1853,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
18471853
// still need re-execution to generate snapshots that are missing
18481854
case err != nil && !errors.Is(err, ErrKnownBlock):
18491855
stats.ignored += len(it.chain)
1850-
bc.reportBlock(block, nil, err)
1856+
bc.reportBadBlock(block, nil, err)
18511857
return nil, it.index, err
18521858
}
18531859
// Track the singleton witness from this chain insertion (if any)
@@ -1915,6 +1921,14 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
19151921
if err != nil {
19161922
return nil, it.index, err
19171923
}
1924+
res.stats.reportMetrics()
1925+
1926+
// Log slow block only if a single block is inserted (usually after the
1927+
// initial sync) to not overwhelm the users.
1928+
if len(chain) == 1 {
1929+
res.stats.logSlow(block, bc.slowBlockThreshold)
1930+
}
1931+
19181932
// Report the import stats before returning the various results
19191933
stats.processed++
19201934
stats.usedGas += res.usedGas
@@ -1975,15 +1989,20 @@ type blockProcessingResult struct {
19751989
procTime time.Duration
19761990
status WriteStatus
19771991
witness *stateless.Witness
1992+
stats *ExecuteStats
19781993
}
19791994

19801995
func (bpr *blockProcessingResult) Witness() *stateless.Witness {
19811996
return bpr.witness
19821997
}
19831998

1999+
func (bpr *blockProcessingResult) Stats() *ExecuteStats {
2000+
return bpr.stats
2001+
}
2002+
19842003
// ProcessBlock executes and validates the given block. If there was no error
19852004
// it writes the block and associated state to database.
1986-
func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool) (_ *blockProcessingResult, blockEndErr error) {
2005+
func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool) (result *blockProcessingResult, blockEndErr error) {
19872006
var (
19882007
err error
19892008
startTime = time.Now()
@@ -2017,16 +2036,22 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
20172036
}
20182037
// Upload the statistics of reader at the end
20192038
defer func() {
2020-
stats := prefetch.GetStats()
2021-
accountCacheHitPrefetchMeter.Mark(stats.AccountHit)
2022-
accountCacheMissPrefetchMeter.Mark(stats.AccountMiss)
2023-
storageCacheHitPrefetchMeter.Mark(stats.StorageHit)
2024-
storageCacheMissPrefetchMeter.Mark(stats.StorageMiss)
2025-
stats = process.GetStats()
2026-
accountCacheHitMeter.Mark(stats.AccountHit)
2027-
accountCacheMissMeter.Mark(stats.AccountMiss)
2028-
storageCacheHitMeter.Mark(stats.StorageHit)
2029-
storageCacheMissMeter.Mark(stats.StorageMiss)
2039+
pStat := prefetch.GetStats()
2040+
accountCacheHitPrefetchMeter.Mark(pStat.AccountCacheHit)
2041+
accountCacheMissPrefetchMeter.Mark(pStat.AccountCacheMiss)
2042+
storageCacheHitPrefetchMeter.Mark(pStat.StorageCacheHit)
2043+
storageCacheMissPrefetchMeter.Mark(pStat.StorageCacheMiss)
2044+
2045+
rStat := process.GetStats()
2046+
accountCacheHitMeter.Mark(rStat.AccountCacheHit)
2047+
accountCacheMissMeter.Mark(rStat.AccountCacheMiss)
2048+
storageCacheHitMeter.Mark(rStat.StorageCacheHit)
2049+
storageCacheMissMeter.Mark(rStat.StorageCacheMiss)
2050+
2051+
if result != nil {
2052+
result.stats.StatePrefetchCacheStats = pStat
2053+
result.stats.StateReadCacheStats = rStat
2054+
}
20302055
}()
20312056

20322057
go func(start time.Time, throwaway *state.StateDB, block *types.Block) {
@@ -2083,14 +2108,14 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
20832108
pstart := time.Now()
20842109
res, err := bc.processor.Process(block, statedb, bc.cfg.VmConfig)
20852110
if err != nil {
2086-
bc.reportBlock(block, res, err)
2111+
bc.reportBadBlock(block, res, err)
20872112
return nil, err
20882113
}
20892114
ptime := time.Since(pstart)
20902115

20912116
vstart := time.Now()
20922117
if err := bc.validator.ValidateState(block, statedb, res, false); err != nil {
2093-
bc.reportBlock(block, res, err)
2118+
bc.reportBadBlock(block, res, err)
20942119
return nil, err
20952120
}
20962121
vtime := time.Since(vstart)
@@ -2124,26 +2149,28 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
21242149
}
21252150
}
21262151

2127-
xvtime := time.Since(xvstart)
2128-
proctime := time.Since(startTime) // processing + validation + cross validation
2129-
2152+
var (
2153+
xvtime = time.Since(xvstart)
2154+
proctime = time.Since(startTime) // processing + validation + cross validation
2155+
stats = &ExecuteStats{}
2156+
)
21302157
// Update the metrics touched during block processing and validation
2131-
accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing)
2132-
storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete(in processing)
2133-
if statedb.AccountLoaded != 0 {
2134-
accountReadSingleTimer.Update(statedb.AccountReads / time.Duration(statedb.AccountLoaded))
2135-
}
2136-
if statedb.StorageLoaded != 0 {
2137-
storageReadSingleTimer.Update(statedb.StorageReads / time.Duration(statedb.StorageLoaded))
2138-
}
2139-
accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete(in validation)
2140-
storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete(in validation)
2141-
accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete(in validation)
2142-
triehash := statedb.AccountHashes // The time spent on tries hashing
2143-
trieUpdate := statedb.AccountUpdates + statedb.StorageUpdates // The time spent on tries update
2144-
blockExecutionTimer.Update(ptime - (statedb.AccountReads + statedb.StorageReads)) // The time spent on EVM processing
2145-
blockValidationTimer.Update(vtime - (triehash + trieUpdate)) // The time spent on block validation
2146-
blockCrossValidationTimer.Update(xvtime) // The time spent on stateless cross validation
2158+
stats.AccountReads = statedb.AccountReads // Account reads are complete(in processing)
2159+
stats.StorageReads = statedb.StorageReads // Storage reads are complete(in processing)
2160+
stats.AccountUpdates = statedb.AccountUpdates // Account updates are complete(in validation)
2161+
stats.StorageUpdates = statedb.StorageUpdates // Storage updates are complete(in validation)
2162+
stats.AccountHashes = statedb.AccountHashes // Account hashes are complete(in validation)
2163+
2164+
stats.AccountLoaded = statedb.AccountLoaded
2165+
stats.AccountUpdated = statedb.AccountUpdated
2166+
stats.AccountDeleted = statedb.AccountDeleted
2167+
stats.StorageLoaded = statedb.StorageLoaded
2168+
stats.StorageUpdated = int(statedb.StorageUpdated.Load())
2169+
stats.StorageDeleted = int(statedb.StorageDeleted.Load())
2170+
2171+
stats.Execution = ptime - (statedb.AccountReads + statedb.StorageReads) // The time spent on EVM processing
2172+
stats.Validation = vtime - (statedb.AccountHashes + statedb.AccountUpdates + statedb.StorageUpdates) // The time spent on block validation
2173+
stats.CrossValidation = xvtime // The time spent on stateless cross validation
21472174

21482175
// Write the block to the chain and get the status.
21492176
var (
@@ -2165,24 +2192,22 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
21652192
}
21662193

21672194
// Update the metrics touched during block commit
2168-
accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them
2169-
storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them
2170-
snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them
2171-
triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them
2195+
stats.AccountCommits = statedb.AccountCommits // Account commits are complete, we can mark them
2196+
stats.StorageCommits = statedb.StorageCommits // Storage commits are complete, we can mark them
2197+
stats.SnapshotCommit = statedb.SnapshotCommits // Snapshot commits are complete, we can mark them
2198+
stats.TrieDBCommit = statedb.TrieDBCommits // Trie database commits are complete, we can mark them
2199+
stats.BlockWrite = time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits
21722200

2173-
blockWriteTimer.Update(time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits)
21742201
elapsed := time.Since(startTime) + 1 // prevent zero division
2175-
blockInsertTimer.Update(elapsed)
2176-
2177-
// TODO(rjl493456442) generalize the ResettingTimer
2178-
mgasps := float64(res.GasUsed) * 1000 / float64(elapsed)
2179-
chainMgaspsMeter.Update(time.Duration(mgasps))
2202+
stats.TotalTime = elapsed
2203+
stats.MgasPerSecond = float64(res.GasUsed) * 1000 / float64(elapsed)
21802204

21812205
return &blockProcessingResult{
21822206
usedGas: res.GasUsed,
21832207
procTime: proctime,
21842208
status: status,
21852209
witness: witness,
2210+
stats: stats,
21862211
}, nil
21872212
}
21882213

@@ -2667,8 +2692,8 @@ func (bc *BlockChain) skipBlock(err error, it *insertIterator) bool {
26672692
return false
26682693
}
26692694

2670-
// reportBlock logs a bad block error.
2671-
func (bc *BlockChain) reportBlock(block *types.Block, res *ProcessResult, err error) {
2695+
// reportBadBlock logs a bad block error.
2696+
func (bc *BlockChain) reportBadBlock(block *types.Block, res *ProcessResult, err error) {
26722697
var receipts types.Receipts
26732698
if res != nil {
26742699
receipts = res.Receipts

0 commit comments

Comments
 (0)