core: rework state reader with stats

This commit is contained in:
Gary Rong 2026-01-30 12:06:17 +08:00
parent 00540f9469
commit 418cd7a8cd
5 changed files with 165 additions and 121 deletions

View file

@ -2153,8 +2153,12 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
// Upload the statistics of reader at the end // Upload the statistics of reader at the end
defer func() { defer func() {
if result != nil { if result != nil {
result.stats.StatePrefetchCacheStats = prefetch.GetStats() if stater, ok := prefetch.(state.ReaderStater); ok {
result.stats.StateReadCacheStats = process.GetStats() result.stats.StatePrefetchCacheStats = stater.GetStats()
}
if stater, ok := process.(state.ReaderStater); ok {
result.stats.StateReadCacheStats = stater.GetStats()
}
} }
}() }()
go func(start time.Time, throwaway *state.StateDB, block *types.Block) { go func(start time.Time, throwaway *state.StateDB, block *types.Block) {

View file

@ -94,15 +94,15 @@ func (s *ExecuteStats) reportMetrics() {
chainMgaspsMeter.Update(time.Duration(s.MgasPerSecond)) // TODO(rjl493456442) generalize the ResettingTimer chainMgaspsMeter.Update(time.Duration(s.MgasPerSecond)) // TODO(rjl493456442) generalize the ResettingTimer
// Cache hit rates // Cache hit rates
accountCacheHitPrefetchMeter.Mark(s.StatePrefetchCacheStats.AccountCacheHit) accountCacheHitPrefetchMeter.Mark(s.StatePrefetchCacheStats.StateStats.AccountCacheHit)
accountCacheMissPrefetchMeter.Mark(s.StatePrefetchCacheStats.AccountCacheMiss) accountCacheMissPrefetchMeter.Mark(s.StatePrefetchCacheStats.StateStats.AccountCacheMiss)
storageCacheHitPrefetchMeter.Mark(s.StatePrefetchCacheStats.StorageCacheHit) storageCacheHitPrefetchMeter.Mark(s.StatePrefetchCacheStats.StateStats.StorageCacheHit)
storageCacheMissPrefetchMeter.Mark(s.StatePrefetchCacheStats.StorageCacheMiss) storageCacheMissPrefetchMeter.Mark(s.StatePrefetchCacheStats.StateStats.StorageCacheMiss)
accountCacheHitMeter.Mark(s.StateReadCacheStats.AccountCacheHit) accountCacheHitMeter.Mark(s.StateReadCacheStats.StateStats.AccountCacheHit)
accountCacheMissMeter.Mark(s.StateReadCacheStats.AccountCacheMiss) accountCacheMissMeter.Mark(s.StateReadCacheStats.StateStats.AccountCacheMiss)
storageCacheHitMeter.Mark(s.StateReadCacheStats.StorageCacheHit) storageCacheHitMeter.Mark(s.StateReadCacheStats.StateStats.StorageCacheHit)
storageCacheMissMeter.Mark(s.StateReadCacheStats.StorageCacheMiss) storageCacheMissMeter.Mark(s.StateReadCacheStats.StateStats.StorageCacheMiss)
} }
// slowBlockLog represents the JSON structure for slow block logging. // slowBlockLog represents the JSON structure for slow block logging.
@ -177,14 +177,6 @@ type slowBlockCodeCacheEntry struct {
MissBytes int64 `json:"miss_bytes"` MissBytes int64 `json:"miss_bytes"`
} }
// calculateHitRate computes the cache hit rate as a percentage (0-100).
func calculateHitRate(hits, misses int64) float64 {
if total := hits + misses; total > 0 {
return float64(hits) / float64(total) * 100.0
}
return 0.0
}
// durationToMs converts a time.Duration to milliseconds as a float64 // durationToMs converts a time.Duration to milliseconds as a float64
// with sub-millisecond precision for accurate cross-client metrics. // with sub-millisecond precision for accurate cross-client metrics.
func durationToMs(d time.Duration) float64 { func durationToMs(d time.Duration) float64 {
@ -238,19 +230,19 @@ func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Durat
}, },
Cache: slowBlockCache{ Cache: slowBlockCache{
Account: slowBlockCacheEntry{ Account: slowBlockCacheEntry{
Hits: s.StateReadCacheStats.AccountCacheHit, Hits: s.StateReadCacheStats.StateStats.AccountCacheHit,
Misses: s.StateReadCacheStats.AccountCacheMiss, Misses: s.StateReadCacheStats.StateStats.AccountCacheMiss,
HitRate: calculateHitRate(s.StateReadCacheStats.AccountCacheHit, s.StateReadCacheStats.AccountCacheMiss), HitRate: s.StateReadCacheStats.StateStats.AccountCacheHitRate(),
}, },
Storage: slowBlockCacheEntry{ Storage: slowBlockCacheEntry{
Hits: s.StateReadCacheStats.StorageCacheHit, Hits: s.StateReadCacheStats.StateStats.StorageCacheHit,
Misses: s.StateReadCacheStats.StorageCacheMiss, Misses: s.StateReadCacheStats.StateStats.StorageCacheMiss,
HitRate: calculateHitRate(s.StateReadCacheStats.StorageCacheHit, s.StateReadCacheStats.StorageCacheMiss), HitRate: s.StateReadCacheStats.StateStats.StorageCacheHitRate(),
}, },
Code: slowBlockCodeCacheEntry{ Code: slowBlockCodeCacheEntry{
Hits: s.StateReadCacheStats.CodeStats.CacheHit, Hits: s.StateReadCacheStats.CodeStats.CacheHit,
Misses: s.StateReadCacheStats.CodeStats.CacheMiss, Misses: s.StateReadCacheStats.CodeStats.CacheMiss,
HitRate: calculateHitRate(s.StateReadCacheStats.CodeStats.CacheHit, s.StateReadCacheStats.CodeStats.CacheMiss), HitRate: s.StateReadCacheStats.CodeStats.HitRate(),
HitBytes: s.StateReadCacheStats.CodeStats.CacheHitBytes, HitBytes: s.StateReadCacheStats.CodeStats.CacheHitBytes,
MissBytes: s.StateReadCacheStats.CodeStats.CacheMissBytes, MissBytes: s.StateReadCacheStats.CodeStats.CacheMissBytes,
}, },

View file

@ -224,15 +224,14 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
// ReadersWithCacheStats creates a pair of state readers that share the same // ReadersWithCacheStats creates a pair of state readers that share the same
// underlying state reader and internal state cache, while maintaining separate // underlying state reader and internal state cache, while maintaining separate
// statistics respectively. // statistics respectively.
func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (ReaderWithStats, ReaderWithStats, error) { func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (Reader, Reader, error) {
r, err := db.StateReader(stateRoot) r, err := db.StateReader(stateRoot)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
sr := newStateReaderWithCache(r) sr := newStateReaderWithCache(r)
ra := newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), newStateReaderWithStats(sr))
ra := newReaderWithStats(sr, newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache)) rb := newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), newStateReaderWithStats(sr))
rb := newReaderWithStats(sr, newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache))
return ra, rb, nil return ra, rb, nil
} }

View file

@ -18,7 +18,6 @@ package state
import ( import (
"errors" "errors"
"fmt"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -38,6 +37,8 @@ import (
) )
// ContractCodeReader defines the interface for accessing contract code. // ContractCodeReader defines the interface for accessing contract code.
//
// ContractCodeReader is supposed to be thread-safe.
type ContractCodeReader interface { type ContractCodeReader interface {
// Has returns the flag indicating whether the contract code with // Has returns the flag indicating whether the contract code with
// specified address and hash exists or not. // specified address and hash exists or not.
@ -58,35 +59,10 @@ type ContractCodeReader interface {
CodeSize(addr common.Address, codeHash common.Hash) (int, error) CodeSize(addr common.Address, codeHash common.Hash) (int, error)
} }
// ContractCodeReaderStats aggregates statistics for the contract code reader.
type ContractCodeReaderStats struct {
CacheHit int64 // Number of cache hits
CacheMiss int64 // Number of cache misses
CacheHitBytes int64 // Total bytes served from cache
CacheMissBytes int64 // Total bytes read on cache misses
}
// HitRate returns the cache hit rate.
func (s ContractCodeReaderStats) HitRate() float64 {
if s.CacheHit == 0 {
return 0
}
return float64(s.CacheHit) / float64(s.CacheHit+s.CacheMiss)
}
// ContractCodeReaderWithStats extends ContractCodeReader by adding GetStats to
// expose statistics of code reader.
type ContractCodeReaderWithStats interface {
ContractCodeReader
GetStats() ContractCodeReaderStats
}
// StateReader defines the interface for accessing accounts and storage slots // StateReader defines the interface for accessing accounts and storage slots
// associated with a specific state. // associated with a specific state.
// //
// StateReader is assumed to be thread-safe and implementation must take care // StateReader is supposed to be thread-safe.
// of the concurrency issue by themselves.
type StateReader interface { type StateReader interface {
// Account retrieves the account associated with a particular address. // Account retrieves the account associated with a particular address.
// //
@ -114,40 +90,6 @@ type Reader interface {
StateReader StateReader
} }
// ReaderStats wraps the statistics of reader.
type ReaderStats struct {
AccountCacheHit int64
AccountCacheMiss int64
StorageCacheHit int64
StorageCacheMiss int64
CodeStats ContractCodeReaderStats
}
// String implements fmt.Stringer, returning string format statistics.
func (s ReaderStats) String() string {
var (
accountCacheHitRate float64
storageCacheHitRate float64
)
if s.AccountCacheHit > 0 {
accountCacheHitRate = float64(s.AccountCacheHit) / float64(s.AccountCacheHit+s.AccountCacheMiss) * 100
}
if s.StorageCacheHit > 0 {
storageCacheHitRate = float64(s.StorageCacheHit) / float64(s.StorageCacheHit+s.StorageCacheMiss) * 100
}
msg := fmt.Sprintf("Reader statistics\n")
msg += fmt.Sprintf("account: hit: %d, miss: %d, rate: %.2f\n", s.AccountCacheHit, s.AccountCacheMiss, accountCacheHitRate)
msg += fmt.Sprintf("storage: hit: %d, miss: %d, rate: %.2f\n", s.StorageCacheHit, s.StorageCacheMiss, storageCacheHitRate)
msg += fmt.Sprintf("code: hit: %d(%v), miss: %d(%v), rate: %.2f\n", s.CodeStats.CacheHit, common.StorageSize(s.CodeStats.CacheHitBytes), s.CodeStats.CacheMiss, common.StorageSize(s.CodeStats.CacheMissBytes), s.CodeStats.HitRate())
return msg
}
// ReaderWithStats wraps the additional method to retrieve the reader statistics from.
type ReaderWithStats interface {
Reader
GetStats() ReaderStats
}
// cachingCodeReader implements ContractCodeReader, accessing contract code either in // cachingCodeReader implements ContractCodeReader, accessing contract code either in
// local key-value store or the shared code cache. // local key-value store or the shared code cache.
// //
@ -210,15 +152,16 @@ func (r *cachingCodeReader) CodeSize(addr common.Address, codeHash common.Hash)
return len(code), nil return len(code), nil
} }
// Has returns the flag indicating whether the contract code with // Has implements ContractCodeReader, returning the flag indicating whether
// specified address and hash exists or not. // the contract code with specified address and hash exists or not.
func (r *cachingCodeReader) Has(addr common.Address, codeHash common.Hash) bool { func (r *cachingCodeReader) Has(addr common.Address, codeHash common.Hash) bool {
code, _ := r.Code(addr, codeHash) code, _ := r.Code(addr, codeHash)
return len(code) > 0 return len(code) > 0
} }
// GetStats returns the statistics of the code reader. // GetCodeStats implements ContractCodeReaderStater, returning the statistics
func (r *cachingCodeReader) GetStats() ContractCodeReaderStats { // of the code reader.
func (r *cachingCodeReader) GetCodeStats() ContractCodeReaderStats {
return ContractCodeReaderStats{ return ContractCodeReaderStats{
CacheHit: r.hit.Load(), CacheHit: r.hit.Load(),
CacheMiss: r.miss.Load(), CacheMiss: r.miss.Load(),
@ -495,20 +438,6 @@ func (r *multiStateReader) Storage(addr common.Address, slot common.Hash) (commo
return common.Hash{}, errors.Join(errs...) return common.Hash{}, errors.Join(errs...)
} }
// reader is the wrapper of ContractCodeReader and StateReader interface.
type reader struct {
ContractCodeReader
StateReader
}
// newReader constructs a reader with the supplied code reader and state reader.
func newReader(codeReader ContractCodeReader, stateReader StateReader) *reader {
return &reader{
ContractCodeReader: codeReader,
StateReader: stateReader,
}
}
// stateReaderWithCache is a wrapper around StateReader that maintains additional // stateReaderWithCache is a wrapper around StateReader that maintains additional
// state caches to support concurrent state access. // state caches to support concurrent state access.
type stateReaderWithCache struct { type stateReaderWithCache struct {
@ -619,9 +548,10 @@ func (r *stateReaderWithCache) Storage(addr common.Address, slot common.Hash) (c
return value, err return value, err
} }
type readerWithStats struct { // stateReaderWithStats is a wrapper over the stateReaderWithCache, tracking
// the cache hit statistics of the reader.
type stateReaderWithStats struct {
*stateReaderWithCache *stateReaderWithCache
ContractCodeReaderWithStats
accountCacheHit atomic.Int64 accountCacheHit atomic.Int64
accountCacheMiss atomic.Int64 accountCacheMiss atomic.Int64
@ -629,11 +559,10 @@ type readerWithStats struct {
storageCacheMiss atomic.Int64 storageCacheMiss atomic.Int64
} }
// newReaderWithStats constructs the reader with additional statistics tracked. // newReaderWithStats constructs the state reader with additional statistics tracked.
func newReaderWithStats(sr *stateReaderWithCache, cr ContractCodeReaderWithStats) *readerWithStats { func newStateReaderWithStats(sr *stateReaderWithCache) *stateReaderWithStats {
return &readerWithStats{ return &stateReaderWithStats{
stateReaderWithCache: sr, stateReaderWithCache: sr,
ContractCodeReaderWithStats: cr,
} }
} }
@ -641,7 +570,7 @@ func newReaderWithStats(sr *stateReaderWithCache, cr ContractCodeReaderWithStats
// The returned account might be nil if it's not existent. // The returned account might be nil if it's not existent.
// //
// An error will be returned if the state is corrupted in the underlying reader. // An error will be returned if the state is corrupted in the underlying reader.
func (r *readerWithStats) Account(addr common.Address) (*types.StateAccount, error) { func (r *stateReaderWithStats) Account(addr common.Address) (*types.StateAccount, error) {
account, incache, err := r.stateReaderWithCache.account(addr) account, incache, err := r.stateReaderWithCache.account(addr)
if err != nil { if err != nil {
return nil, err return nil, err
@ -659,7 +588,7 @@ func (r *readerWithStats) Account(addr common.Address) (*types.StateAccount, err
// existent. // existent.
// //
// An error will be returned if the state is corrupted in the underlying reader. // An error will be returned if the state is corrupted in the underlying reader.
func (r *readerWithStats) Storage(addr common.Address, slot common.Hash) (common.Hash, error) { func (r *stateReaderWithStats) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
value, incache, err := r.stateReaderWithCache.storage(addr, slot) value, incache, err := r.stateReaderWithCache.storage(addr, slot)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
@ -672,13 +601,51 @@ func (r *readerWithStats) Storage(addr common.Address, slot common.Hash) (common
return value, nil return value, nil
} }
// GetStats implements ReaderWithStats, returning the statistics of state reader. // GetStateStats implements StateReaderStater, returning the statistics of the
func (r *readerWithStats) GetStats() ReaderStats { // state reader.
return ReaderStats{ func (r *stateReaderWithStats) GetStateStats() StateReaderStats {
return StateReaderStats{
AccountCacheHit: r.accountCacheHit.Load(), AccountCacheHit: r.accountCacheHit.Load(),
AccountCacheMiss: r.accountCacheMiss.Load(), AccountCacheMiss: r.accountCacheMiss.Load(),
StorageCacheHit: r.storageCacheHit.Load(), StorageCacheHit: r.storageCacheHit.Load(),
StorageCacheMiss: r.storageCacheMiss.Load(), StorageCacheMiss: r.storageCacheMiss.Load(),
CodeStats: r.ContractCodeReaderWithStats.GetStats(), }
}
// reader aggregates a code reader and a state reader into a single object.
type reader struct {
ContractCodeReader
StateReader
}
// newReader constructs a reader with the supplied code reader and state reader.
func newReader(codeReader ContractCodeReader, stateReader StateReader) *reader {
return &reader{
ContractCodeReader: codeReader,
StateReader: stateReader,
}
}
// GetCodeStats returns the statistics of code access.
func (r *reader) GetCodeStats() ContractCodeReaderStats {
if stater, ok := r.ContractCodeReader.(ContractCodeReaderStater); ok {
return stater.GetCodeStats()
}
return ContractCodeReaderStats{}
}
// GetStateStats returns the statistics of state access.
func (r *reader) GetStateStats() StateReaderStats {
if stater, ok := r.StateReader.(StateReaderStater); ok {
return stater.GetStateStats()
}
return StateReaderStats{}
}
// GetStats returns the aggregated statistics for both state and code access.
func (r *reader) GetStats() ReaderStats {
return ReaderStats{
CodeStats: r.GetCodeStats(),
StateStats: r.GetStateStats(),
} }
} }

View file

@ -0,0 +1,82 @@
// Copyright 2026 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// 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 <http://www.gnu.org/licenses/>.
package state
// ContractCodeReaderStats aggregates statistics for the contract code reader.
type ContractCodeReaderStats struct {
CacheHit int64 // Number of cache hits
CacheMiss int64 // Number of cache misses
CacheHitBytes int64 // Total bytes served from cache
CacheMissBytes int64 // Total bytes read on cache misses
}
// HitRate returns the cache hit rate in percentage.
func (s ContractCodeReaderStats) HitRate() float64 {
total := s.CacheHit + s.CacheMiss
if total == 0 {
return 0
}
return float64(s.CacheHit) / float64(total) * 100
}
// ContractCodeReaderStater wraps the method to retrieve the statistics of
// contract code reader.
type ContractCodeReaderStater interface {
GetCodeStats() ContractCodeReaderStats
}
// StateReaderStats aggregates statistics for the state reader.
type StateReaderStats struct {
AccountCacheHit int64 // Number of account cache hits
AccountCacheMiss int64 // Number of account cache misses
StorageCacheHit int64 // Number of storage cache hits
StorageCacheMiss int64 // Number of storage cache misses
}
// AccountCacheHitRate returns the cache hit rate of account requests in percentage.
func (s StateReaderStats) AccountCacheHitRate() float64 {
total := s.AccountCacheHit + s.AccountCacheMiss
if total == 0 {
return 0
}
return float64(s.AccountCacheHit) / float64(total) * 100
}
// StorageCacheHitRate returns the cache hit rate of storage requests in percentage.
func (s StateReaderStats) StorageCacheHitRate() float64 {
total := s.StorageCacheHit + s.StorageCacheMiss
if total == 0 {
return 0
}
return float64(s.StorageCacheHit) / float64(total) * 100
}
// StateReaderStater wraps the method to retrieve the statistics of state reader.
type StateReaderStater interface {
GetStateStats() StateReaderStats
}
// ReaderStats wraps the statistics of reader.
type ReaderStats struct {
CodeStats ContractCodeReaderStats
StateStats StateReaderStats
}
// ReaderStater defines the capability to retrieve aggregated statistics.
type ReaderStater interface {
GetStats() ReaderStats
}