diff --git a/core/blockchain.go b/core/blockchain.go index 8741b8b937..db3f71c44d 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -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 diff --git a/core/blockchain_stats.go b/core/blockchain_stats.go index b6e9c614c5..e8c5860294 100644 --- a/core/blockchain_stats.go +++ b/core/blockchain_stats.go @@ -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), diff --git a/core/state/reader.go b/core/state/reader.go index 2db9d1f9b4..35b732173b 100644 --- a/core/state/reader.go +++ b/core/state/reader.go @@ -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(), } } diff --git a/core/state/state_object.go b/core/state/state_object.go index 3b11553f04..2873c3cb8a 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -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())) diff --git a/core/state/statedb.go b/core/state/statedb.go index 39160aa1c7..610e7173cf 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -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.