mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-22 20:56:42 +00:00
core: extend code read statistics with cache and unique metrics
This extends the execution statistics infrastructure from #33442 with: - Code cache hit/miss tracking (completing parity with account/storage) - Code bytes read metric for I/O volume analysis - Unique state access metrics (accounts, storage slots, contracts) The slow block log now shows: Code read: 626ns(4, 1.07 KiB) Unique state access: Accounts: 6 Storage slots: 11 Contracts executed: 4 Reader statistics account: hit: 4, miss: 6, rate: 40.00 storage: hit: 0, miss: 11, rate: 0.00 code: hit: 4, miss: 0, rate: 100.00
This commit is contained in:
parent
ffe9dc97e5
commit
be2d43090c
9 changed files with 212 additions and 29 deletions
|
|
@ -87,6 +87,9 @@ var (
|
||||||
storageCacheHitPrefetchMeter = metrics.NewRegisteredMeter("chain/storage/reads/cache/prefetch/hit", nil)
|
storageCacheHitPrefetchMeter = metrics.NewRegisteredMeter("chain/storage/reads/cache/prefetch/hit", nil)
|
||||||
storageCacheMissPrefetchMeter = metrics.NewRegisteredMeter("chain/storage/reads/cache/prefetch/miss", nil)
|
storageCacheMissPrefetchMeter = metrics.NewRegisteredMeter("chain/storage/reads/cache/prefetch/miss", nil)
|
||||||
|
|
||||||
|
codeCacheHitMeter = metrics.NewRegisteredMeter("chain/code/reads/cache/hit", nil)
|
||||||
|
codeCacheMissMeter = metrics.NewRegisteredMeter("chain/code/reads/cache/miss", nil)
|
||||||
|
|
||||||
accountReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/account/single/reads", nil)
|
accountReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/account/single/reads", nil)
|
||||||
storageReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/storage/single/reads", nil)
|
storageReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/storage/single/reads", nil)
|
||||||
codeReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/code/single/reads", nil)
|
codeReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/code/single/reads", nil)
|
||||||
|
|
@ -2060,6 +2063,8 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
|
||||||
accountCacheMissMeter.Mark(rStat.AccountCacheMiss)
|
accountCacheMissMeter.Mark(rStat.AccountCacheMiss)
|
||||||
storageCacheHitMeter.Mark(rStat.StorageCacheHit)
|
storageCacheHitMeter.Mark(rStat.StorageCacheHit)
|
||||||
storageCacheMissMeter.Mark(rStat.StorageCacheMiss)
|
storageCacheMissMeter.Mark(rStat.StorageCacheMiss)
|
||||||
|
codeCacheHitMeter.Mark(rStat.CodeCacheHit)
|
||||||
|
codeCacheMissMeter.Mark(rStat.CodeCacheMiss)
|
||||||
|
|
||||||
if result != nil {
|
if result != nil {
|
||||||
result.stats.StatePrefetchCacheStats = pStat
|
result.stats.StatePrefetchCacheStats = pStat
|
||||||
|
|
@ -2182,6 +2187,11 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
|
||||||
stats.StorageUpdated = int(statedb.StorageUpdated.Load())
|
stats.StorageUpdated = int(statedb.StorageUpdated.Load())
|
||||||
stats.StorageDeleted = int(statedb.StorageDeleted.Load())
|
stats.StorageDeleted = int(statedb.StorageDeleted.Load())
|
||||||
stats.CodeLoaded = statedb.CodeLoaded
|
stats.CodeLoaded = statedb.CodeLoaded
|
||||||
|
stats.CodeBytesRead = statedb.CodeBytesRead
|
||||||
|
|
||||||
|
stats.UniqueAccountsAccessed = statedb.UniqueAccountsAccessed()
|
||||||
|
stats.UniqueStorageAccessed = statedb.UniqueStorageAccessed()
|
||||||
|
stats.UniqueCodeExecuted = statedb.UniqueCodeExecuted()
|
||||||
|
|
||||||
stats.Execution = ptime - (statedb.AccountReads + statedb.StorageReads + statedb.CodeReads) // The time spent on EVM processing
|
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
|
stats.Validation = vtime - (statedb.AccountHashes + statedb.AccountUpdates + statedb.StorageUpdates) // The time spent on block validation
|
||||||
|
|
|
||||||
|
|
@ -39,13 +39,19 @@ type ExecuteStats struct {
|
||||||
StorageCommits time.Duration // Time spent on the storage trie commit
|
StorageCommits time.Duration // Time spent on the storage trie commit
|
||||||
CodeReads time.Duration // Time spent on the contract code read
|
CodeReads time.Duration // Time spent on the contract code read
|
||||||
|
|
||||||
AccountLoaded int // Number of accounts loaded
|
AccountLoaded int // Number of accounts loaded
|
||||||
AccountUpdated int // Number of accounts updated
|
AccountUpdated int // Number of accounts updated
|
||||||
AccountDeleted int // Number of accounts deleted
|
AccountDeleted int // Number of accounts deleted
|
||||||
StorageLoaded int // Number of storage slots loaded
|
StorageLoaded int // Number of storage slots loaded
|
||||||
StorageUpdated int // Number of storage slots updated
|
StorageUpdated int // Number of storage slots updated
|
||||||
StorageDeleted int // Number of storage slots deleted
|
StorageDeleted int // Number of storage slots deleted
|
||||||
CodeLoaded int // Number of contract code loaded
|
CodeLoaded int // Number of contract code loaded
|
||||||
|
CodeBytesRead int64 // Total bytes of contract code read
|
||||||
|
|
||||||
|
// Unique access metrics
|
||||||
|
UniqueAccountsAccessed int // Number of unique accounts accessed
|
||||||
|
UniqueStorageAccessed int // Number of unique storage slots accessed
|
||||||
|
UniqueCodeExecuted int // Number of unique contracts executed
|
||||||
|
|
||||||
Execution time.Duration // Time spent on the EVM execution
|
Execution time.Duration // Time spent on the EVM execution
|
||||||
Validation time.Duration // Time spent on the block validation
|
Validation time.Duration // Time spent on the block validation
|
||||||
|
|
@ -100,6 +106,8 @@ func (s *ExecuteStats) reportMetrics() {
|
||||||
accountCacheMissMeter.Mark(s.StateReadCacheStats.AccountCacheMiss)
|
accountCacheMissMeter.Mark(s.StateReadCacheStats.AccountCacheMiss)
|
||||||
storageCacheHitMeter.Mark(s.StateReadCacheStats.StorageCacheHit)
|
storageCacheHitMeter.Mark(s.StateReadCacheStats.StorageCacheHit)
|
||||||
storageCacheMissMeter.Mark(s.StateReadCacheStats.StorageCacheMiss)
|
storageCacheMissMeter.Mark(s.StateReadCacheStats.StorageCacheMiss)
|
||||||
|
codeCacheHitMeter.Mark(s.StateReadCacheStats.CodeCacheHit)
|
||||||
|
codeCacheMissMeter.Mark(s.StateReadCacheStats.CodeCacheMiss)
|
||||||
}
|
}
|
||||||
|
|
||||||
// logSlow prints the detailed execution statistics if the block is regarded as slow.
|
// logSlow prints the detailed execution statistics if the block is regarded as slow.
|
||||||
|
|
@ -119,7 +127,12 @@ Validation: %v
|
||||||
State read: %v
|
State read: %v
|
||||||
Account read: %v(%d)
|
Account read: %v(%d)
|
||||||
Storage read: %v(%d)
|
Storage read: %v(%d)
|
||||||
Code read: %v(%d)
|
Code read: %v(%d, %s)
|
||||||
|
|
||||||
|
Unique state access:
|
||||||
|
Accounts: %d
|
||||||
|
Storage slots: %d
|
||||||
|
Contracts executed: %d
|
||||||
|
|
||||||
State hash: %v
|
State hash: %v
|
||||||
Account hash: %v
|
Account hash: %v
|
||||||
|
|
@ -140,7 +153,12 @@ DB write: %v
|
||||||
common.PrettyDuration(s.AccountReads+s.StorageReads+s.CodeReads),
|
common.PrettyDuration(s.AccountReads+s.StorageReads+s.CodeReads),
|
||||||
common.PrettyDuration(s.AccountReads), s.AccountLoaded,
|
common.PrettyDuration(s.AccountReads), s.AccountLoaded,
|
||||||
common.PrettyDuration(s.StorageReads), s.StorageLoaded,
|
common.PrettyDuration(s.StorageReads), s.StorageLoaded,
|
||||||
common.PrettyDuration(s.CodeReads), s.CodeLoaded,
|
common.PrettyDuration(s.CodeReads), s.CodeLoaded, common.StorageSize(s.CodeBytesRead),
|
||||||
|
|
||||||
|
// Unique state access
|
||||||
|
s.UniqueAccountsAccessed,
|
||||||
|
s.UniqueStorageAccessed,
|
||||||
|
s.UniqueCodeExecuted,
|
||||||
|
|
||||||
// State hash
|
// State hash
|
||||||
common.PrettyDuration(s.AccountHashes+s.AccountUpdates+s.StorageUpdates+max(s.AccountCommits, s.StorageCommits)),
|
common.PrettyDuration(s.AccountHashes+s.AccountUpdates+s.StorageUpdates+max(s.AccountCommits, s.StorageCommits)),
|
||||||
|
|
|
||||||
|
|
@ -226,7 +226,6 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
|
||||||
|
|
||||||
// ReadersWithCacheStats creates a pair of state readers sharing the same internal cache and
|
// ReadersWithCacheStats creates a pair of state readers sharing the same internal cache and
|
||||||
// same backing Reader, but exposing separate statistics.
|
// same backing Reader, but exposing separate statistics.
|
||||||
// and statistics.
|
|
||||||
func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (ReaderWithStats, ReaderWithStats, error) {
|
func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (ReaderWithStats, ReaderWithStats, error) {
|
||||||
reader, err := db.Reader(stateRoot)
|
reader, err := db.Reader(stateRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,8 @@ type ReaderStats struct {
|
||||||
AccountCacheMiss int64
|
AccountCacheMiss int64
|
||||||
StorageCacheHit int64
|
StorageCacheHit int64
|
||||||
StorageCacheMiss int64
|
StorageCacheMiss int64
|
||||||
|
CodeCacheHit int64
|
||||||
|
CodeCacheMiss int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// String implements fmt.Stringer, returning string format statistics.
|
// String implements fmt.Stringer, returning string format statistics.
|
||||||
|
|
@ -105,6 +107,7 @@ func (s ReaderStats) String() string {
|
||||||
var (
|
var (
|
||||||
accountCacheHitRate float64
|
accountCacheHitRate float64
|
||||||
storageCacheHitRate float64
|
storageCacheHitRate float64
|
||||||
|
codeCacheHitRate float64
|
||||||
)
|
)
|
||||||
if s.AccountCacheHit > 0 {
|
if s.AccountCacheHit > 0 {
|
||||||
accountCacheHitRate = float64(s.AccountCacheHit) / float64(s.AccountCacheHit+s.AccountCacheMiss) * 100
|
accountCacheHitRate = float64(s.AccountCacheHit) / float64(s.AccountCacheHit+s.AccountCacheMiss) * 100
|
||||||
|
|
@ -112,9 +115,13 @@ func (s ReaderStats) String() string {
|
||||||
if s.StorageCacheHit > 0 {
|
if s.StorageCacheHit > 0 {
|
||||||
storageCacheHitRate = float64(s.StorageCacheHit) / float64(s.StorageCacheHit+s.StorageCacheMiss) * 100
|
storageCacheHitRate = float64(s.StorageCacheHit) / float64(s.StorageCacheHit+s.StorageCacheMiss) * 100
|
||||||
}
|
}
|
||||||
|
if s.CodeCacheHit > 0 {
|
||||||
|
codeCacheHitRate = float64(s.CodeCacheHit) / float64(s.CodeCacheHit+s.CodeCacheMiss) * 100
|
||||||
|
}
|
||||||
msg := fmt.Sprintf("Reader statistics\n")
|
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("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("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.CodeCacheHit, s.CodeCacheMiss, codeCacheHitRate)
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,32 +153,46 @@ func newCachingCodeReader(db ethdb.KeyValueReader, codeCache *lru.SizeConstraine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Code implements ContractCodeReader, retrieving a particular contract's code.
|
// code retrieves a particular contract's code along with a flag indicating
|
||||||
// If the contract code doesn't exist, no error will be returned.
|
// whether it was found in the cache.
|
||||||
func (r *cachingCodeReader) Code(addr common.Address, codeHash common.Hash) ([]byte, error) {
|
func (r *cachingCodeReader) code(addr common.Address, codeHash common.Hash) ([]byte, bool, error) {
|
||||||
code, _ := r.codeCache.Get(codeHash)
|
code, found := r.codeCache.Get(codeHash)
|
||||||
if len(code) > 0 {
|
if found && len(code) > 0 {
|
||||||
return code, nil
|
return code, true, nil
|
||||||
}
|
}
|
||||||
code = rawdb.ReadCode(r.db, codeHash)
|
code = rawdb.ReadCode(r.db, codeHash)
|
||||||
if len(code) > 0 {
|
if len(code) > 0 {
|
||||||
r.codeCache.Add(codeHash, code)
|
r.codeCache.Add(codeHash, code)
|
||||||
r.codeSizeCache.Add(codeHash, len(code))
|
r.codeSizeCache.Add(codeHash, len(code))
|
||||||
}
|
}
|
||||||
return code, nil
|
return code, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code implements ContractCodeReader, retrieving a particular contract's code.
|
||||||
|
// If the contract code doesn't exist, no error will be returned.
|
||||||
|
func (r *cachingCodeReader) Code(addr common.Address, codeHash common.Hash) ([]byte, error) {
|
||||||
|
code, _, err := r.code(addr, codeHash)
|
||||||
|
return code, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// codeSize retrieves a particular contract's code size along with a flag indicating
|
||||||
|
// whether it was found in the cache.
|
||||||
|
func (r *cachingCodeReader) codeSize(addr common.Address, codeHash common.Hash) (int, bool, error) {
|
||||||
|
if cached, ok := r.codeSizeCache.Get(codeHash); ok {
|
||||||
|
return cached, true, nil
|
||||||
|
}
|
||||||
|
code, _, err := r.code(addr, codeHash)
|
||||||
|
if err != nil {
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
return len(code), false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CodeSize implements ContractCodeReader, retrieving a particular contracts code's size.
|
// CodeSize implements ContractCodeReader, retrieving a particular contracts code's size.
|
||||||
// If the contract code doesn't exist, no error will be returned.
|
// If the contract code doesn't exist, no error will be returned.
|
||||||
func (r *cachingCodeReader) CodeSize(addr common.Address, codeHash common.Hash) (int, error) {
|
func (r *cachingCodeReader) CodeSize(addr common.Address, codeHash common.Hash) (int, error) {
|
||||||
if cached, ok := r.codeSizeCache.Get(codeHash); ok {
|
size, _, err := r.codeSize(addr, codeHash)
|
||||||
return cached, nil
|
return size, err
|
||||||
}
|
|
||||||
code, err := r.Code(addr, codeHash)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return len(code), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Has returns the flag indicating whether the contract code with
|
// Has returns the flag indicating whether the contract code with
|
||||||
|
|
@ -463,6 +484,28 @@ func newReader(codeReader ContractCodeReader, stateReader StateReader) *reader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// code retrieves contract code with a flag indicating cache hit status.
|
||||||
|
// Returns (code, incache, error). If the underlying code reader doesn't
|
||||||
|
// support cache tracking, incache is always false.
|
||||||
|
func (r *reader) code(addr common.Address, codeHash common.Hash) ([]byte, bool, error) {
|
||||||
|
if cr, ok := r.ContractCodeReader.(*cachingCodeReader); ok {
|
||||||
|
return cr.code(addr, codeHash)
|
||||||
|
}
|
||||||
|
code, err := r.ContractCodeReader.Code(addr, codeHash)
|
||||||
|
return code, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// codeSize retrieves contract code size with a flag indicating cache hit status.
|
||||||
|
// Returns (size, incache, error). If the underlying code reader doesn't
|
||||||
|
// support cache tracking, incache is always false.
|
||||||
|
func (r *reader) codeSize(addr common.Address, codeHash common.Hash) (int, bool, error) {
|
||||||
|
if cr, ok := r.ContractCodeReader.(*cachingCodeReader); ok {
|
||||||
|
return cr.codeSize(addr, codeHash)
|
||||||
|
}
|
||||||
|
size, err := r.ContractCodeReader.CodeSize(addr, codeHash)
|
||||||
|
return size, false, err
|
||||||
|
}
|
||||||
|
|
||||||
// readerWithCache is a wrapper around Reader that maintains additional state caches
|
// readerWithCache is a wrapper around Reader that maintains additional state caches
|
||||||
// to support concurrent state access.
|
// to support concurrent state access.
|
||||||
type readerWithCache struct {
|
type readerWithCache struct {
|
||||||
|
|
@ -573,6 +616,24 @@ func (r *readerWithCache) Storage(addr common.Address, slot common.Hash) (common
|
||||||
return value, err
|
return value, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// code retrieves contract code with a flag indicating cache hit status.
|
||||||
|
func (r *readerWithCache) code(addr common.Address, codeHash common.Hash) ([]byte, bool, error) {
|
||||||
|
if rr, ok := r.Reader.(*reader); ok {
|
||||||
|
return rr.code(addr, codeHash)
|
||||||
|
}
|
||||||
|
code, err := r.Reader.Code(addr, codeHash)
|
||||||
|
return code, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// codeSize retrieves contract code size with a flag indicating cache hit status.
|
||||||
|
func (r *readerWithCache) codeSize(addr common.Address, codeHash common.Hash) (int, bool, error) {
|
||||||
|
if rr, ok := r.Reader.(*reader); ok {
|
||||||
|
return rr.codeSize(addr, codeHash)
|
||||||
|
}
|
||||||
|
size, err := r.Reader.CodeSize(addr, codeHash)
|
||||||
|
return size, false, err
|
||||||
|
}
|
||||||
|
|
||||||
type readerWithCacheStats struct {
|
type readerWithCacheStats struct {
|
||||||
*readerWithCache
|
*readerWithCache
|
||||||
|
|
||||||
|
|
@ -580,6 +641,8 @@ type readerWithCacheStats struct {
|
||||||
accountCacheMiss atomic.Int64
|
accountCacheMiss atomic.Int64
|
||||||
storageCacheHit atomic.Int64
|
storageCacheHit atomic.Int64
|
||||||
storageCacheMiss atomic.Int64
|
storageCacheMiss atomic.Int64
|
||||||
|
codeCacheHit atomic.Int64
|
||||||
|
codeCacheMiss atomic.Int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// newReaderWithCacheStats constructs the reader with additional statistics tracked.
|
// newReaderWithCacheStats constructs the reader with additional statistics tracked.
|
||||||
|
|
@ -624,6 +687,34 @@ func (r *readerWithCacheStats) Storage(addr common.Address, slot common.Hash) (c
|
||||||
return value, nil
|
return value, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Code implements ContractCodeReader, retrieving a particular contract's code.
|
||||||
|
func (r *readerWithCacheStats) Code(addr common.Address, codeHash common.Hash) ([]byte, error) {
|
||||||
|
code, incache, err := r.readerWithCache.code(addr, codeHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if incache {
|
||||||
|
r.codeCacheHit.Add(1)
|
||||||
|
} else {
|
||||||
|
r.codeCacheMiss.Add(1)
|
||||||
|
}
|
||||||
|
return code, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CodeSize implements ContractCodeReader, retrieving a particular contract's code size.
|
||||||
|
func (r *readerWithCacheStats) CodeSize(addr common.Address, codeHash common.Hash) (int, error) {
|
||||||
|
size, incache, err := r.readerWithCache.codeSize(addr, codeHash)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if incache {
|
||||||
|
r.codeCacheHit.Add(1)
|
||||||
|
} else {
|
||||||
|
r.codeCacheMiss.Add(1)
|
||||||
|
}
|
||||||
|
return size, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetStats implements ReaderWithStats, returning the statistics of state reader.
|
// GetStats implements ReaderWithStats, returning the statistics of state reader.
|
||||||
func (r *readerWithCacheStats) GetStats() ReaderStats {
|
func (r *readerWithCacheStats) GetStats() ReaderStats {
|
||||||
return ReaderStats{
|
return ReaderStats{
|
||||||
|
|
@ -631,5 +722,7 @@ func (r *readerWithCacheStats) GetStats() ReaderStats {
|
||||||
AccountCacheMiss: r.accountCacheMiss.Load(),
|
AccountCacheMiss: r.accountCacheMiss.Load(),
|
||||||
StorageCacheHit: r.storageCacheHit.Load(),
|
StorageCacheHit: r.storageCacheHit.Load(),
|
||||||
StorageCacheMiss: r.storageCacheMiss.Load(),
|
StorageCacheMiss: r.storageCacheMiss.Load(),
|
||||||
|
CodeCacheHit: r.codeCacheHit.Load(),
|
||||||
|
CodeCacheMiss: r.codeCacheMiss.Load(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -543,6 +543,7 @@ func (s *stateObject) Code() []byte {
|
||||||
if len(code) == 0 {
|
if len(code) == 0 {
|
||||||
s.db.setError(fmt.Errorf("code is not found %x", s.CodeHash()))
|
s.db.setError(fmt.Errorf("code is not found %x", s.CodeHash()))
|
||||||
}
|
}
|
||||||
|
s.db.CodeBytesRead += int64(len(code))
|
||||||
s.code = code
|
s.code = code
|
||||||
return code
|
return code
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,10 @@ type StateDB struct {
|
||||||
StorageUpdated atomic.Int64 // Number of storage slots updated during the state transition
|
StorageUpdated atomic.Int64 // Number of storage slots updated during the state transition
|
||||||
StorageDeleted atomic.Int64 // Number of storage slots deleted 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
|
CodeLoaded int // Number of contract code loaded during the state transition
|
||||||
|
CodeBytesRead int64 // Total bytes of contract code read during the state transition
|
||||||
|
|
||||||
|
// Unique contracts executed tracking (set of code hashes executed during the state transition)
|
||||||
|
executedCodes map[common.Hash]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new state from a given trie.
|
// New creates a new state from a given trie.
|
||||||
|
|
@ -186,6 +190,7 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro
|
||||||
journal: newJournal(),
|
journal: newJournal(),
|
||||||
accessList: newAccessList(),
|
accessList: newAccessList(),
|
||||||
transientStorage: newTransientStorage(),
|
transientStorage: newTransientStorage(),
|
||||||
|
executedCodes: make(map[common.Hash]struct{}),
|
||||||
}
|
}
|
||||||
if db.TrieDB().IsVerkle() {
|
if db.TrieDB().IsVerkle() {
|
||||||
sdb.accessEvents = NewAccessEvents(db.PointCache())
|
sdb.accessEvents = NewAccessEvents(db.PointCache())
|
||||||
|
|
@ -379,6 +384,37 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash {
|
||||||
return common.Hash{}
|
return common.Hash{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarkCodeExecuted records that a contract's code was executed.
|
||||||
|
// This is used for metrics tracking to count unique contracts executed.
|
||||||
|
func (s *StateDB) MarkCodeExecuted(codeHash common.Hash) {
|
||||||
|
if codeHash == types.EmptyCodeHash || codeHash == (common.Hash{}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.executedCodes == nil {
|
||||||
|
return // Skip tracking for state copies (e.g., prefetcher)
|
||||||
|
}
|
||||||
|
s.executedCodes[codeHash] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UniqueCodeExecuted returns the number of unique contract codes executed.
|
||||||
|
func (s *StateDB) UniqueCodeExecuted() int {
|
||||||
|
return len(s.executedCodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UniqueAccountsAccessed returns the number of unique accounts accessed during the state transition.
|
||||||
|
func (s *StateDB) UniqueAccountsAccessed() int {
|
||||||
|
return len(s.stateObjects)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UniqueStorageAccessed returns the number of unique storage slots accessed during the state transition.
|
||||||
|
func (s *StateDB) UniqueStorageAccessed() int {
|
||||||
|
count := 0
|
||||||
|
for _, obj := range s.stateObjects {
|
||||||
|
count += len(obj.originStorage)
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
// GetState retrieves the value associated with the specific key.
|
// GetState retrieves the value associated with the specific key.
|
||||||
func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
|
func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
|
||||||
stateObject := s.getStateObject(addr)
|
stateObject := s.getStateObject(addr)
|
||||||
|
|
|
||||||
|
|
@ -291,3 +291,13 @@ func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inner returns the underlying StateDB.
|
||||||
|
func (s *hookedStateDB) Inner() *StateDB {
|
||||||
|
return s.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkCodeExecuted records that a contract's code was executed.
|
||||||
|
func (s *hookedStateDB) MarkCodeExecuted(codeHash common.Hash) {
|
||||||
|
s.inner.MarkCodeExecuted(codeHash)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -293,7 +293,10 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
||||||
// The contract is a scoped environment for this execution context only.
|
// The contract is a scoped environment for this execution context only.
|
||||||
contract := NewContract(caller, addr, value, gas, evm.jumpDests)
|
contract := NewContract(caller, addr, value, gas, evm.jumpDests)
|
||||||
contract.IsSystemCall = isSystemCall(caller)
|
contract.IsSystemCall = isSystemCall(caller)
|
||||||
contract.SetCallCode(evm.resolveCodeHash(addr), code)
|
codeHash := evm.resolveCodeHash(addr)
|
||||||
|
contract.SetCallCode(codeHash, code)
|
||||||
|
// Track unique contract execution for metrics
|
||||||
|
evm.StateDB.MarkCodeExecuted(codeHash)
|
||||||
ret, err = evm.Run(contract, input, false)
|
ret, err = evm.Run(contract, input, false)
|
||||||
gas = contract.Gas
|
gas = contract.Gas
|
||||||
}
|
}
|
||||||
|
|
@ -352,7 +355,10 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
|
||||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||||
// The contract is a scoped environment for this execution context only.
|
// The contract is a scoped environment for this execution context only.
|
||||||
contract := NewContract(caller, caller, value, gas, evm.jumpDests)
|
contract := NewContract(caller, caller, value, gas, evm.jumpDests)
|
||||||
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
|
codeHash := evm.resolveCodeHash(addr)
|
||||||
|
contract.SetCallCode(codeHash, evm.resolveCode(addr))
|
||||||
|
// Track unique contract execution for metrics
|
||||||
|
evm.StateDB.MarkCodeExecuted(codeHash)
|
||||||
ret, err = evm.Run(contract, input, false)
|
ret, err = evm.Run(contract, input, false)
|
||||||
gas = contract.Gas
|
gas = contract.Gas
|
||||||
}
|
}
|
||||||
|
|
@ -396,7 +402,10 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
|
||||||
//
|
//
|
||||||
// Note: The value refers to the original value from the parent call.
|
// Note: The value refers to the original value from the parent call.
|
||||||
contract := NewContract(originCaller, caller, value, gas, evm.jumpDests)
|
contract := NewContract(originCaller, caller, value, gas, evm.jumpDests)
|
||||||
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
|
codeHash := evm.resolveCodeHash(addr)
|
||||||
|
contract.SetCallCode(codeHash, evm.resolveCode(addr))
|
||||||
|
// Track unique contract execution for metrics
|
||||||
|
evm.StateDB.MarkCodeExecuted(codeHash)
|
||||||
ret, err = evm.Run(contract, input, false)
|
ret, err = evm.Run(contract, input, false)
|
||||||
gas = contract.Gas
|
gas = contract.Gas
|
||||||
}
|
}
|
||||||
|
|
@ -447,7 +456,10 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
|
||||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||||
// The contract is a scoped environment for this execution context only.
|
// The contract is a scoped environment for this execution context only.
|
||||||
contract := NewContract(caller, addr, new(uint256.Int), gas, evm.jumpDests)
|
contract := NewContract(caller, addr, new(uint256.Int), gas, evm.jumpDests)
|
||||||
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
|
codeHash := evm.resolveCodeHash(addr)
|
||||||
|
contract.SetCallCode(codeHash, evm.resolveCode(addr))
|
||||||
|
// Track unique contract execution for metrics
|
||||||
|
evm.StateDB.MarkCodeExecuted(codeHash)
|
||||||
|
|
||||||
// When an error was returned by the EVM or when setting the creation code
|
// When an error was returned by the EVM or when setting the creation code
|
||||||
// above we revert to the snapshot and consume any gas remaining. Additionally
|
// above we revert to the snapshot and consume any gas remaining. Additionally
|
||||||
|
|
|
||||||
|
|
@ -101,4 +101,8 @@ type StateDB interface {
|
||||||
|
|
||||||
// Finalise must be invoked at the end of a transaction
|
// Finalise must be invoked at the end of a transaction
|
||||||
Finalise(bool)
|
Finalise(bool)
|
||||||
|
|
||||||
|
// MarkCodeExecuted records that a contract's code was executed.
|
||||||
|
// Used for metrics tracking to count unique contracts executed.
|
||||||
|
MarkCodeExecuted(codeHash common.Hash)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue