Skip to content

Commit c7b8924

Browse files
core/state: expose the state reader stats (#31998)
This pull request introduces a mechanism to expose statistics from the state reader, specifically related to cache utilization during state prefetching. To improve state access performance, a pair of state readers is constructed with a shared local cache. One reader to execute transactions ahead of time to warm up the cache. The other reader is used by the actual chain processing logic, which can benefit from the prefetched states. This PR adds visibility into how effective the cache is by exposing relevant usage statistics. --------- Signed-off-by: Csaba Kiraly <[email protected]> Co-authored-by: Csaba Kiraly <[email protected]>
1 parent 846d13a commit c7b8924

File tree

3 files changed

+139
-19
lines changed

3 files changed

+139
-19
lines changed

core/blockchain.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,16 @@ var (
7777
storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil)
7878
storageCommitTimer = metrics.NewRegisteredResettingTimer("chain/storage/commits", nil)
7979

80+
accountCacheHitMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/process/hit", nil)
81+
accountCacheMissMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/process/miss", nil)
82+
storageCacheHitMeter = metrics.NewRegisteredMeter("chain/storage/reads/cache/process/hit", nil)
83+
storageCacheMissMeter = metrics.NewRegisteredMeter("chain/storage/reads/cache/process/miss", nil)
84+
85+
accountCacheHitPrefetchMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/prefetch/hit", nil)
86+
accountCacheMissPrefetchMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/prefetch/miss", nil)
87+
storageCacheHitPrefetchMeter = metrics.NewRegisteredMeter("chain/storage/reads/cache/prefetch/hit", nil)
88+
storageCacheMissPrefetchMeter = metrics.NewRegisteredMeter("chain/storage/reads/cache/prefetch/miss", nil)
89+
8090
accountReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/account/single/reads", nil)
8191
storageReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/storage/single/reads", nil)
8292

@@ -1944,18 +1954,32 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
19441954
//
19451955
// Note: the main processor and prefetcher share the same reader with a local
19461956
// cache for mitigating the overhead of state access.
1947-
reader, err := bc.statedb.ReaderWithCache(parentRoot)
1957+
prefetch, process, err := bc.statedb.ReadersWithCacheStats(parentRoot)
19481958
if err != nil {
19491959
return nil, err
19501960
}
1951-
throwaway, err := state.NewWithReader(parentRoot, bc.statedb, reader)
1961+
throwaway, err := state.NewWithReader(parentRoot, bc.statedb, prefetch)
19521962
if err != nil {
19531963
return nil, err
19541964
}
1955-
statedb, err = state.NewWithReader(parentRoot, bc.statedb, reader)
1965+
statedb, err = state.NewWithReader(parentRoot, bc.statedb, process)
19561966
if err != nil {
19571967
return nil, err
19581968
}
1969+
// Upload the statistics of reader at the end
1970+
defer func() {
1971+
stats := prefetch.GetStats()
1972+
accountCacheHitPrefetchMeter.Mark(stats.AccountHit)
1973+
accountCacheMissPrefetchMeter.Mark(stats.AccountMiss)
1974+
storageCacheHitPrefetchMeter.Mark(stats.StorageHit)
1975+
storageCacheMissPrefetchMeter.Mark(stats.StorageMiss)
1976+
stats = process.GetStats()
1977+
accountCacheHitMeter.Mark(stats.AccountHit)
1978+
accountCacheMissMeter.Mark(stats.AccountMiss)
1979+
storageCacheHitMeter.Mark(stats.StorageHit)
1980+
storageCacheMissMeter.Mark(stats.StorageMiss)
1981+
}()
1982+
19591983
go func(start time.Time, throwaway *state.StateDB, block *types.Block) {
19601984
// Disable tracing for prefetcher executions.
19611985
vmCfg := bc.cfg.VmConfig

core/state/database.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -209,13 +209,16 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
209209
return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), combined), nil
210210
}
211211

212-
// ReaderWithCache creates a state reader with internal local cache.
213-
func (db *CachingDB) ReaderWithCache(stateRoot common.Hash) (Reader, error) {
212+
// ReadersWithCacheStats creates a pair of state readers sharing the same internal cache and
213+
// same backing Reader, but exposing separate statistics.
214+
// and statistics.
215+
func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (ReaderWithStats, ReaderWithStats, error) {
214216
reader, err := db.Reader(stateRoot)
215217
if err != nil {
216-
return nil, err
218+
return nil, nil, err
217219
}
218-
return newReaderWithCache(reader), nil
220+
shared := newReaderWithCache(reader)
221+
return newReaderWithCacheStats(shared), newReaderWithCacheStats(shared), nil
219222
}
220223

221224
// OpenTrie opens the main account trie at a specific root hash.

core/state/reader.go

Lines changed: 105 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package state
1919
import (
2020
"errors"
2121
"sync"
22+
"sync/atomic"
2223

2324
"github.com/ethereum/go-ethereum/common"
2425
"github.com/ethereum/go-ethereum/common/lru"
@@ -82,6 +83,20 @@ type Reader interface {
8283
StateReader
8384
}
8485

86+
// ReaderStats wraps the statistics of reader.
87+
type ReaderStats struct {
88+
AccountHit int64
89+
AccountMiss int64
90+
StorageHit int64
91+
StorageMiss int64
92+
}
93+
94+
// ReaderWithStats wraps the additional method to retrieve the reader statistics from.
95+
type ReaderWithStats interface {
96+
Reader
97+
GetStats() ReaderStats
98+
}
99+
85100
// cachingCodeReader implements ContractCodeReader, accessing contract code either in
86101
// local key-value store or the shared code cache.
87102
//
@@ -414,35 +429,43 @@ func newReaderWithCache(reader Reader) *readerWithCache {
414429
return r
415430
}
416431

417-
// Account implements StateReader, retrieving the account specified by the address.
418-
// The returned account might be nil if it's not existent.
432+
// account retrieves the account specified by the address along with a flag
433+
// indicating whether it's found in the cache or not. The returned account
434+
// might be nil if it's not existent.
419435
//
420436
// An error will be returned if the state is corrupted in the underlying reader.
421-
func (r *readerWithCache) Account(addr common.Address) (*types.StateAccount, error) {
437+
func (r *readerWithCache) account(addr common.Address) (*types.StateAccount, bool, error) {
422438
// Try to resolve the requested account in the local cache
423439
r.accountLock.RLock()
424440
acct, ok := r.accounts[addr]
425441
r.accountLock.RUnlock()
426442
if ok {
427-
return acct, nil
443+
return acct, true, nil
428444
}
429445
// Try to resolve the requested account from the underlying reader
430446
acct, err := r.Reader.Account(addr)
431447
if err != nil {
432-
return nil, err
448+
return nil, false, err
433449
}
434450
r.accountLock.Lock()
435451
r.accounts[addr] = acct
436452
r.accountLock.Unlock()
437-
return acct, nil
453+
return acct, false, nil
438454
}
439455

440-
// Storage implements StateReader, retrieving the storage slot specified by the
441-
// address and slot key. The returned storage slot might be empty if it's not
442-
// existent.
456+
// Account implements StateReader, retrieving the account specified by the address.
457+
// The returned account might be nil if it's not existent.
443458
//
444459
// An error will be returned if the state is corrupted in the underlying reader.
445-
func (r *readerWithCache) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
460+
func (r *readerWithCache) Account(addr common.Address) (*types.StateAccount, error) {
461+
account, _, err := r.account(addr)
462+
return account, err
463+
}
464+
465+
// storage retrieves the storage slot specified by the address and slot key, along
466+
// with a flag indicating whether it's found in the cache or not. The returned
467+
// storage slot might be empty if it's not existent.
468+
func (r *readerWithCache) storage(addr common.Address, slot common.Hash) (common.Hash, bool, error) {
446469
var (
447470
value common.Hash
448471
ok bool
@@ -456,12 +479,12 @@ func (r *readerWithCache) Storage(addr common.Address, slot common.Hash) (common
456479
}
457480
bucket.lock.RUnlock()
458481
if ok {
459-
return value, nil
482+
return value, true, nil
460483
}
461484
// Try to resolve the requested storage slot from the underlying reader
462485
value, err := r.Reader.Storage(addr, slot)
463486
if err != nil {
464-
return common.Hash{}, err
487+
return common.Hash{}, false, err
465488
}
466489
bucket.lock.Lock()
467490
slots, ok = bucket.storages[addr]
@@ -472,5 +495,75 @@ func (r *readerWithCache) Storage(addr common.Address, slot common.Hash) (common
472495
slots[slot] = value
473496
bucket.lock.Unlock()
474497

498+
return value, false, nil
499+
}
500+
501+
// Storage implements StateReader, retrieving the storage slot specified by the
502+
// address and slot key. The returned storage slot might be empty if it's not
503+
// existent.
504+
//
505+
// An error will be returned if the state is corrupted in the underlying reader.
506+
func (r *readerWithCache) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
507+
value, _, err := r.storage(addr, slot)
508+
return value, err
509+
}
510+
511+
type readerWithCacheStats struct {
512+
*readerWithCache
513+
accountHit atomic.Int64
514+
accountMiss atomic.Int64
515+
storageHit atomic.Int64
516+
storageMiss atomic.Int64
517+
}
518+
519+
// newReaderWithCacheStats constructs the reader with additional statistics tracked.
520+
func newReaderWithCacheStats(reader *readerWithCache) *readerWithCacheStats {
521+
return &readerWithCacheStats{
522+
readerWithCache: reader,
523+
}
524+
}
525+
526+
// Account implements StateReader, retrieving the account specified by the address.
527+
// The returned account might be nil if it's not existent.
528+
//
529+
// An error will be returned if the state is corrupted in the underlying reader.
530+
func (r *readerWithCacheStats) Account(addr common.Address) (*types.StateAccount, error) {
531+
account, incache, err := r.readerWithCache.account(addr)
532+
if err != nil {
533+
return nil, err
534+
}
535+
if incache {
536+
r.accountHit.Add(1)
537+
} else {
538+
r.accountMiss.Add(1)
539+
}
540+
return account, nil
541+
}
542+
543+
// Storage implements StateReader, retrieving the storage slot specified by the
544+
// address and slot key. The returned storage slot might be empty if it's not
545+
// existent.
546+
//
547+
// An error will be returned if the state is corrupted in the underlying reader.
548+
func (r *readerWithCacheStats) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
549+
value, incache, err := r.readerWithCache.storage(addr, slot)
550+
if err != nil {
551+
return common.Hash{}, err
552+
}
553+
if incache {
554+
r.storageHit.Add(1)
555+
} else {
556+
r.storageMiss.Add(1)
557+
}
475558
return value, nil
476559
}
560+
561+
// GetStats implements ReaderWithStats, returning the statistics of state reader.
562+
func (r *readerWithCacheStats) GetStats() ReaderStats {
563+
return ReaderStats{
564+
AccountHit: r.accountHit.Load(),
565+
AccountMiss: r.accountMiss.Load(),
566+
StorageHit: r.storageHit.Load(),
567+
StorageMiss: r.storageMiss.Load(),
568+
}
569+
}

0 commit comments

Comments
 (0)