core: extend the code reader statistics (#33659)
Some checks are pending
/ Windows Build (push) Waiting to run
/ Docker Image (push) Waiting to run
/ Keeper Build (push) Waiting to run
/ Linux Build (push) Waiting to run
/ Linux Build (arm) (push) Waiting to run

This PR extends the statistics of contract code read by adding these
fields:

- **CacheHitBytes**: the total number of bytes served by cache
- **CacheMissBytes**: the total number of bytes read on cache miss
- **CodeReadBytes**: the total number of bytes for contract code read
This commit is contained in:
rjl493456442 2026-01-26 18:25:53 +08:00 committed by GitHub
parent 9a8e14e77e
commit c2595381bf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 48 additions and 19 deletions

View file

@ -76,6 +76,7 @@ var (
storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil)
storageCommitTimer = metrics.NewRegisteredResettingTimer("chain/storage/commits", nil)
codeReadTimer = metrics.NewRegisteredResettingTimer("chain/code/reads", nil)
codeReadBytesTimer = metrics.NewRegisteredResettingTimer("chain/code/readbytes", nil)
accountCacheHitMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/process/hit", nil)
accountCacheMissMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/process/miss", nil)
@ -2238,6 +2239,7 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
stats.StorageUpdated = int(statedb.StorageUpdated.Load())
stats.StorageDeleted = int(statedb.StorageDeleted.Load())
stats.CodeLoaded = statedb.CodeLoaded
stats.CodeLoadBytes = statedb.CodeLoadBytes
stats.Execution = ptime - (statedb.AccountReads + statedb.StorageReads + statedb.CodeReads) // The time spent on EVM processing
stats.Validation = vtime - (statedb.AccountHashes + statedb.AccountUpdates + statedb.StorageUpdates) // The time spent on block validation

View file

@ -46,6 +46,7 @@ type ExecuteStats struct {
StorageUpdated int // Number of storage slots updated
StorageDeleted int // Number of storage slots deleted
CodeLoaded int // Number of contract code loaded
CodeLoadBytes int // Number of bytes read from contract code
Execution time.Duration // Time spent on the EVM execution
Validation time.Duration // Time spent on the block validation
@ -74,6 +75,7 @@ func (s *ExecuteStats) reportMetrics() {
if s.CodeLoaded != 0 {
codeReadTimer.Update(s.CodeReads)
codeReadSingleTimer.Update(s.CodeReads / time.Duration(s.CodeLoaded))
codeReadBytesTimer.Update(time.Duration(s.CodeLoadBytes))
}
accountUpdateTimer.Update(s.AccountUpdates) // Account updates are complete(in validation)
storageUpdateTimer.Update(s.StorageUpdates) // Storage updates are complete(in validation)
@ -123,7 +125,7 @@ Validation: %v
State read: %v
Account read: %v(%d)
Storage read: %v(%d)
Code read: %v(%d)
Code read: %v(%d %v)
State write: %v
Trie commit: %v
@ -145,7 +147,7 @@ State write: %v
common.PrettyDuration(s.AccountReads+s.StorageReads+s.CodeReads),
common.PrettyDuration(s.AccountReads), s.AccountLoaded,
common.PrettyDuration(s.StorageReads), s.StorageLoaded,
common.PrettyDuration(s.CodeReads), s.CodeLoaded,
common.PrettyDuration(s.CodeReads), s.CodeLoaded, common.StorageSize(s.CodeLoadBytes),
// State write
common.PrettyDuration(max(s.AccountCommits, s.StorageCommits)+s.TrieDBCommit+s.SnapshotCommit+s.BlockWrite),

View file

@ -58,11 +58,28 @@ type ContractCodeReader interface {
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() (int64, int64)
GetStats() ContractCodeReaderStats
}
// StateReader defines the interface for accessing accounts and storage slots
@ -99,13 +116,11 @@ type Reader interface {
// ReaderStats wraps the statistics of reader.
type ReaderStats struct {
// Cache stats
AccountCacheHit int64
AccountCacheMiss int64
StorageCacheHit int64
StorageCacheMiss int64
ContractCodeHit int64
ContractCodeMiss int64
CodeStats ContractCodeReaderStats
}
// String implements fmt.Stringer, returning string format statistics.
@ -113,7 +128,6 @@ func (s ReaderStats) String() string {
var (
accountCacheHitRate float64
storageCacheHitRate float64
contractCodeHitRate float64
)
if s.AccountCacheHit > 0 {
accountCacheHitRate = float64(s.AccountCacheHit) / float64(s.AccountCacheHit+s.AccountCacheMiss) * 100
@ -121,13 +135,10 @@ func (s ReaderStats) String() string {
if s.StorageCacheHit > 0 {
storageCacheHitRate = float64(s.StorageCacheHit) / float64(s.StorageCacheHit+s.StorageCacheMiss) * 100
}
if s.ContractCodeHit > 0 {
contractCodeHitRate = float64(s.ContractCodeHit) / float64(s.ContractCodeHit+s.ContractCodeMiss) * 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, miss: %d, rate: %.2f\n", s.ContractCodeHit, s.ContractCodeMiss, contractCodeHitRate)
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
}
@ -150,8 +161,10 @@ type cachingCodeReader struct {
codeSizeCache *lru.Cache[common.Hash, int]
// Cache statistics
hit atomic.Int64 // Number of code lookups found in the cache.
miss atomic.Int64 // Number of code lookups not found in the cache.
hit atomic.Int64 // Number of code lookups found in the cache
miss atomic.Int64 // Number of code lookups not found in the cache
hitBytes atomic.Int64 // Total number of bytes read from cache
missBytes atomic.Int64 // Total number of bytes read from database
}
// newCachingCodeReader constructs the code reader.
@ -169,6 +182,7 @@ func (r *cachingCodeReader) Code(addr common.Address, codeHash common.Hash) ([]b
code, _ := r.codeCache.Get(codeHash)
if len(code) > 0 {
r.hit.Add(1)
r.hitBytes.Add(int64(len(code)))
return code, nil
}
r.miss.Add(1)
@ -177,6 +191,7 @@ func (r *cachingCodeReader) Code(addr common.Address, codeHash common.Hash) ([]b
if len(code) > 0 {
r.codeCache.Add(codeHash, code)
r.codeSizeCache.Add(codeHash, len(code))
r.missBytes.Add(int64(len(code)))
}
return code, nil
}
@ -202,9 +217,14 @@ func (r *cachingCodeReader) Has(addr common.Address, codeHash common.Hash) bool
return len(code) > 0
}
// GetStats returns the cache statistics fo the code reader.
func (r *cachingCodeReader) GetStats() (int64, int64) {
return r.hit.Load(), r.miss.Load()
// GetStats returns the statistics of the code reader.
func (r *cachingCodeReader) GetStats() ContractCodeReaderStats {
return ContractCodeReaderStats{
CacheHit: r.hit.Load(),
CacheMiss: r.miss.Load(),
CacheHitBytes: r.hitBytes.Load(),
CacheMissBytes: r.missBytes.Load(),
}
}
// flatReader wraps a database state reader and is safe for concurrent access.
@ -654,13 +674,11 @@ func (r *readerWithStats) Storage(addr common.Address, slot common.Hash) (common
// GetStats implements ReaderWithStats, returning the statistics of state reader.
func (r *readerWithStats) GetStats() ReaderStats {
codeHit, codeMiss := r.ContractCodeReaderWithStats.GetStats()
return ReaderStats{
AccountCacheHit: r.accountCacheHit.Load(),
AccountCacheMiss: r.accountCacheMiss.Load(),
StorageCacheHit: r.storageCacheHit.Load(),
StorageCacheMiss: r.storageCacheMiss.Load(),
ContractCodeHit: codeHit,
ContractCodeMiss: codeMiss,
CodeStats: r.ContractCodeReaderWithStats.GetStats(),
}
}

View file

@ -541,6 +541,7 @@ func (s *stateObject) Code() []byte {
defer func(start time.Time) {
s.db.CodeLoaded += 1
s.db.CodeReads += time.Since(start)
s.db.CodeLoadBytes += len(s.code)
}(time.Now())
code, err := s.db.reader.Code(s.address, common.BytesToHash(s.CodeHash()))

View file

@ -159,6 +159,12 @@ type StateDB struct {
StorageUpdated atomic.Int64 // Number of storage slots updated during the state transition
StorageDeleted atomic.Int64 // Number of storage slots deleted during the state transition
CodeLoaded int // Number of contract code loaded during the state transition
// CodeLoadBytes is the total number of bytes read from contract code.
// This value may be smaller than the actual number of bytes read, since
// some APIs (e.g. CodeSize) may load the entire code from either the
// cache or the database when the size is not available in the cache.
CodeLoadBytes int
}
// New creates a new state from a given trie.