mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-24 08:49:29 +00:00
core, core/state: instrument BAL slow-block metrics
Populates per-block state read/write counts in slow-block JSON for BAL blocks (which #34892 left as TBD), and adds reader-level read timing. Builds on top of bal-devnet-3 — most of the PR's earlier slow-block log infrastructure was adapted into upstream by that commit, so this change is now scoped to the metric population that the BAL alone can derive. - BAL helpers: BlockAccessList.{UniqueAccountCount, UniqueStorageSlotCount, WrittenCounts}. WrittenCounts walks the BAL once and returns the block-aggregate write counts. - Reader-level read timing: *reader times all synchronous Account/Storage/ Code/CodeSize calls via atomic counters; exposed via ReadTimes() ReadDurations and the new state.ReadTimer interface. Replaces StateDB- level AccountReads/StorageReads/CodeReads tracking (the StateDB shouldn't time its dependencies — the reader is where the I/O happens). - Reader-level code-load dedup: *reader.codeLoaded sync.Map records the first-seen byte length per address; CodeLoads() returns (count, bytes). Exposed via state.CodeLoadTracker. Replaces StateDB CodeLoaded/ CodeLoadBytes tracking and the SnapshotCodeLoads aggregation pattern. - BALStateTransition: caches BlockAccessList.WrittenCounts() once at construction; tracks accountDeleted/storageDeleted atomics for the parallel root-pass (the BAL alone can't distinguish a selfdestruct from a balance/nonce reset). Exposes Deletions() DeletionCounts. Drops the older accountUpdated/storageUpdated/codeUpdated/codeUpdateBytes counters (now derived from WrittenCounts). - BAL block stats path (blockchain.go): populates StateCounts directly — AccountUpdated = WrittenCounts.Accounts - Deletions.Accounts (same for storage). AccountLoaded/StorageLoaded come from BAL. CodeLoaded/ CodeLoadBytes come from the shared *reader (deduplicated across phase StateDBs naturally because they share one reader instance). - Non-BAL block stats path: read durations come from the reader; counts from StateDB fields. StorageUpdated/StorageDeleted unified to int width. - Hard type assertions: state.ReadTimer / state.CodeLoadTracker / state.ReaderStater consumers use direct casts (no silent zero fallback) — every Reader chain in production satisfies these interfaces. - Meter alignment: account/storage Updated meters subtract Deletions to avoid double-reporting blocks under both Update and Delete dashboards.
This commit is contained in:
parent
230c6a5e11
commit
fd76921afa
11 changed files with 277 additions and 98 deletions
|
|
@ -656,6 +656,20 @@ func (bc *BlockChain) processBlockWithAccessList(parentRoot common.Hash, block *
|
|||
writeTime := time.Since(writeStart)
|
||||
var stats ExecuteStats
|
||||
|
||||
wc := stateTransition.WrittenCounts()
|
||||
d := stateTransition.Deletions()
|
||||
codeLoaded, codeLoadBytes := prefetchReader.(state.CodeLoadTracker).CodeLoads()
|
||||
stats.AccountLoaded = al.UniqueAccountCount()
|
||||
stats.AccountUpdated = wc.Accounts - d.Accounts
|
||||
stats.AccountDeleted = d.Accounts
|
||||
stats.StorageLoaded = al.UniqueStorageSlotCount()
|
||||
stats.StorageUpdated = wc.StorageSlots - d.Storage
|
||||
stats.StorageDeleted = d.Storage
|
||||
stats.CodeLoaded = codeLoaded
|
||||
stats.CodeLoadBytes = codeLoadBytes
|
||||
stats.CodeUpdated = wc.Codes
|
||||
stats.CodeUpdateBytes = wc.CodeBytes
|
||||
|
||||
stats.ExecWall = res.ExecTime
|
||||
stats.PostProcess = res.PostProcessTime
|
||||
|
||||
|
|
@ -666,12 +680,13 @@ func (bc *BlockChain) processBlockWithAccessList(parentRoot common.Hash, block *
|
|||
stats.DatabaseCommit = m.TrieDBCommits
|
||||
stats.Prefetch = m.StatePrefetch
|
||||
}
|
||||
|
||||
readerReads := prefetchReader.(state.ReadTimer).ReadTimes()
|
||||
stats.AccountReads = readerReads.Account
|
||||
stats.StorageReads = readerReads.Storage
|
||||
stats.CodeReads = readerReads.Code
|
||||
stats.Prefetch = prefetchReader.(state.PrefetcherMetricer).Metrics().Elapsed
|
||||
|
||||
if r, ok := prefetchReader.(state.ReaderStater); ok {
|
||||
stats.StateReadCacheStats = r.GetStats()
|
||||
}
|
||||
stats.StateReadCacheStats = prefetchReader.(state.ReaderStater).GetStats()
|
||||
|
||||
elapsed := time.Since(startTime) + 1 // prevent zero division
|
||||
stats.TotalTime = elapsed
|
||||
|
|
@ -2435,13 +2450,14 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
|||
proctime = time.Since(startTime) // processing + validation + cross validation
|
||||
stats = &ExecuteStats{}
|
||||
)
|
||||
// Update the metrics touched during block processing and validation
|
||||
stats.AccountReads = statedb.AccountReads // Account reads are complete(in processing)
|
||||
stats.StorageReads = statedb.StorageReads // Storage reads are complete(in processing)
|
||||
stats.AccountUpdates = statedb.AccountUpdates // Account updates are complete(in validation)
|
||||
stats.StorageUpdates = statedb.StorageUpdates // Storage updates are complete(in validation)
|
||||
stats.AccountHashes = statedb.AccountHashes // Account hashes are complete(in validation)
|
||||
stats.CodeReads = statedb.CodeReads
|
||||
reads := statedb.Reader().(state.ReadTimer).ReadTimes()
|
||||
codeLoaded, codeLoadBytes := statedb.Reader().(state.CodeLoadTracker).CodeLoads()
|
||||
stats.AccountReads = reads.Account
|
||||
stats.StorageReads = reads.Storage
|
||||
stats.CodeReads = reads.Code
|
||||
stats.AccountUpdates = statedb.AccountUpdates
|
||||
stats.StorageUpdates = statedb.StorageUpdates
|
||||
stats.AccountHashes = statedb.AccountHashes
|
||||
|
||||
stats.AccountLoaded = statedb.AccountLoaded
|
||||
stats.AccountUpdated = statedb.AccountUpdated
|
||||
|
|
@ -2449,15 +2465,14 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
|||
stats.StorageLoaded = statedb.StorageLoaded
|
||||
stats.StorageUpdated = int(statedb.StorageUpdated.Load())
|
||||
stats.StorageDeleted = int(statedb.StorageDeleted.Load())
|
||||
|
||||
stats.CodeLoaded = statedb.CodeLoaded
|
||||
stats.CodeLoadBytes = statedb.CodeLoadBytes
|
||||
stats.CodeLoaded = codeLoaded
|
||||
stats.CodeLoadBytes = codeLoadBytes
|
||||
stats.CodeUpdated = statedb.CodeUpdated
|
||||
stats.CodeUpdateBytes = statedb.CodeUpdateBytes
|
||||
|
||||
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.CrossValidation = xvtime // The time spent on stateless cross validation
|
||||
stats.Execution = ptime - (reads.Account + reads.Storage + reads.Code)
|
||||
stats.Validation = vtime - (statedb.AccountHashes + statedb.AccountUpdates + statedb.StorageUpdates)
|
||||
stats.CrossValidation = xvtime
|
||||
|
||||
// Write the block to the chain and get the status.
|
||||
var status WriteStatus
|
||||
|
|
|
|||
|
|
@ -38,17 +38,16 @@ type ExecuteStats struct {
|
|||
StorageCommits time.Duration // Time spent on the storage trie commit
|
||||
CodeReads time.Duration // Time spent on the contract code read
|
||||
|
||||
// TODO: code bytes loaded
|
||||
AccountLoaded int // Number of accounts loaded
|
||||
AccountUpdated int // Number of accounts updated
|
||||
AccountDeleted int // Number of accounts deleted
|
||||
StorageLoaded int // Number of storage slots loaded
|
||||
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
|
||||
CodeUpdated int // Number of contract code written (CREATE/CREATE2 + EIP-7702)
|
||||
CodeUpdateBytes int // Total bytes of code written
|
||||
AccountLoaded int
|
||||
AccountUpdated int
|
||||
AccountDeleted int
|
||||
StorageLoaded int
|
||||
StorageUpdated int
|
||||
StorageDeleted int
|
||||
CodeLoaded int
|
||||
CodeLoadBytes int
|
||||
CodeUpdated int
|
||||
CodeUpdateBytes int
|
||||
|
||||
Execution time.Duration // Time spent on the EVM execution
|
||||
Validation time.Duration // Time spent on the block validation
|
||||
|
|
@ -304,8 +303,8 @@ func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Durat
|
|||
}
|
||||
|
||||
func (s *ExecuteStats) reportBALMetrics() {
|
||||
accountCommitTimer.Update(s.AccountCommits) // Account commits are complete, we can mark them
|
||||
storageCommitTimer.Update(s.StorageCommits) // Storage commits are complete, we can mark them
|
||||
accountCommitTimer.Update(s.AccountCommits)
|
||||
storageCommitTimer.Update(s.StorageCommits)
|
||||
|
||||
if m := s.balTransitionStats; m != nil {
|
||||
stateTriePrefetchTimer.Update(m.StatePrefetch)
|
||||
|
|
|
|||
|
|
@ -14,16 +14,13 @@ import (
|
|||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// ProcessResultWithMetrics wraps ProcessResult with some metrics that are
|
||||
// emitted when executing blocks containing access lists.
|
||||
// ProcessResultWithMetrics wraps ProcessResult with timing breakdown for BAL block processing.
|
||||
type ProcessResultWithMetrics struct {
|
||||
ProcessResult *ProcessResult
|
||||
PreProcessTime time.Duration
|
||||
StateTransitionMetrics *state.BALStateTransitionMetrics
|
||||
// the time it took to execute all txs in the block
|
||||
ExecTime time.Duration
|
||||
PostProcessTime time.Duration
|
||||
// TODO: have the prefetch metric in here as well?
|
||||
ExecTime time.Duration
|
||||
PostProcessTime time.Duration
|
||||
}
|
||||
|
||||
// ParallelStateProcessor is used to execute and verify blocks containing
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package state
|
|||
import (
|
||||
"maps"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
|
@ -15,30 +16,28 @@ import (
|
|||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// BALStateTransition is responsible for performing the state root update
|
||||
// and commit for EIP 7928 access-list-containing blocks. An instance of
|
||||
// this object is only used for a single block.
|
||||
// BALStateTransition performs the state root update and commit for EIP-7928
|
||||
// access-list-containing blocks. One instance per block.
|
||||
type BALStateTransition struct {
|
||||
accessList bal.AccessListReader
|
||||
written bal.WrittenCounts
|
||||
db Database
|
||||
reader Reader
|
||||
stateTrie Trie
|
||||
parentRoot common.Hash
|
||||
|
||||
// the computed state root of the block
|
||||
rootHash common.Hash
|
||||
// the state modifications performed by the block
|
||||
diffs bal.StateMutations
|
||||
|
||||
// a map of common.Address -> *types.StateAccount containing the block
|
||||
// prestate of all accounts that will be modified
|
||||
prestates sync.Map
|
||||
diffs bal.StateMutations
|
||||
|
||||
prestates sync.Map
|
||||
postStates map[common.Address]*types.StateAccount
|
||||
// a map of common.Address -> Trie containing the account tries for all
|
||||
// accounts with mutated storage
|
||||
tries sync.Map //map[common.Address]Trie
|
||||
deletions map[common.Address]struct{}
|
||||
tries sync.Map
|
||||
deletions map[common.Address]struct{}
|
||||
|
||||
// Deletion counters; not derivable from the BAL alone (selfdestruct vs
|
||||
// balance/nonce reset is indistinguishable without prestate).
|
||||
accountDeleted int
|
||||
storageDeleted atomic.Int64
|
||||
|
||||
stateUpdate *stateUpdate
|
||||
|
||||
|
|
@ -52,6 +51,19 @@ func (s *BALStateTransition) Metrics() *BALStateTransitionMetrics {
|
|||
return &s.metrics
|
||||
}
|
||||
|
||||
// DeletionCounts holds per-block deletion counters from the parallel root-pass.
|
||||
type DeletionCounts struct {
|
||||
Accounts int
|
||||
Storage int
|
||||
}
|
||||
|
||||
func (s *BALStateTransition) Deletions() DeletionCounts {
|
||||
return DeletionCounts{
|
||||
Accounts: s.accountDeleted,
|
||||
Storage: int(s.storageDeleted.Load()),
|
||||
}
|
||||
}
|
||||
|
||||
type BALStateTransitionMetrics struct {
|
||||
// trie hashing metrics
|
||||
AccountUpdate time.Duration
|
||||
|
|
@ -75,6 +87,7 @@ func NewBALStateTransition(block *types.Block, prefetchReader Reader, db Databas
|
|||
|
||||
return &BALStateTransition{
|
||||
accessList: bal.NewAccessListReader(*block.AccessList()),
|
||||
written: block.AccessList().WrittenCounts(),
|
||||
db: db,
|
||||
reader: prefetchReader,
|
||||
stateTrie: stateTrie,
|
||||
|
|
@ -90,6 +103,11 @@ func NewBALStateTransition(block *types.Block, prefetchReader Reader, db Databas
|
|||
}, nil
|
||||
}
|
||||
|
||||
// WrittenCounts returns the cached BAL write counts (computed once per block).
|
||||
func (s *BALStateTransition) WrittenCounts() bal.WrittenCounts {
|
||||
return s.written
|
||||
}
|
||||
|
||||
func (s *BALStateTransition) Error() error {
|
||||
return s.err
|
||||
}
|
||||
|
|
@ -334,15 +352,11 @@ func (s *BALStateTransition) CommitWithUpdate(block uint64, deleteEmptyObjects b
|
|||
return common.Hash{}, nil, err
|
||||
}
|
||||
|
||||
/*
|
||||
TODO: derive these from the BAL
|
||||
^ I think even then, there is a semantic difference with how these metrics were calculated previously
|
||||
I don't know if it makes sense to recompute those, or just derive new ones from the BAL
|
||||
accountUpdatedMeter.Mark(int64(s.accountUpdated))
|
||||
storageUpdatedMeter.Mark(s.storageUpdated.Load())
|
||||
accountDeletedMeter.Mark(int64(s.accountDeleted))
|
||||
storageDeletedMeter.Mark(s.storageDeleted.Load())
|
||||
*/
|
||||
storageDeleted := s.storageDeleted.Load()
|
||||
accountUpdatedMeter.Mark(int64(s.written.Accounts - s.accountDeleted))
|
||||
storageUpdatedMeter.Mark(int64(s.written.StorageSlots) - storageDeleted)
|
||||
accountDeletedMeter.Mark(int64(s.accountDeleted))
|
||||
storageDeletedMeter.Mark(storageDeleted)
|
||||
accountTrieUpdatedMeter.Mark(int64(accountTrieNodesUpdated))
|
||||
accountTrieDeletedMeter.Mark(int64(accountTrieNodesDeleted))
|
||||
storageTriesUpdatedMeter.Mark(int64(storageTrieNodesUpdated))
|
||||
|
|
@ -477,6 +491,7 @@ func (s *BALStateTransition) IntermediateRoot(_ bool) common.Hash {
|
|||
return common.Hash{}
|
||||
}
|
||||
s.deletions[mutatedAddr] = struct{}{}
|
||||
s.accountDeleted++
|
||||
} else {
|
||||
acct, code := s.updateAccount(mutatedAddr)
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ package state
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/overlay"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
|
|
@ -28,8 +32,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/trie/transitiontrie"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
"github.com/ethereum/go-ethereum/triedb/database"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// ContractCodeReader defines the interface for accessing contract code.
|
||||
|
|
@ -525,14 +527,18 @@ func (r *stateReaderWithStats) GetStateStats() StateReaderStats {
|
|||
}
|
||||
}
|
||||
|
||||
// reader aggregates a code reader and a state reader into a single object.
|
||||
type reader struct {
|
||||
ContractCodeReader
|
||||
StateReader
|
||||
PrefetcherMetricer
|
||||
|
||||
accountReadNS atomic.Int64
|
||||
storageReadNS atomic.Int64
|
||||
codeReadNS atomic.Int64
|
||||
|
||||
codeLoaded sync.Map // common.Address → int (first-seen len(code))
|
||||
}
|
||||
|
||||
// newReader constructs a reader with the supplied code reader and state reader.
|
||||
func newReader(codeReader ContractCodeReader, stateReader StateReader) *reader {
|
||||
return &reader{
|
||||
ContractCodeReader: codeReader,
|
||||
|
|
@ -548,6 +554,53 @@ func newReaderWithPrefetch(codeReader ContractCodeReader, stateReader StateReade
|
|||
}
|
||||
}
|
||||
|
||||
func (r *reader) Account(addr common.Address) (*types.StateAccount, error) {
|
||||
defer func(start time.Time) { r.accountReadNS.Add(int64(time.Since(start))) }(time.Now())
|
||||
return r.StateReader.Account(addr)
|
||||
}
|
||||
|
||||
func (r *reader) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
|
||||
defer func(start time.Time) { r.storageReadNS.Add(int64(time.Since(start))) }(time.Now())
|
||||
return r.StateReader.Storage(addr, slot)
|
||||
}
|
||||
|
||||
func (r *reader) Code(addr common.Address, codeHash common.Hash) []byte {
|
||||
defer func(start time.Time) { r.codeReadNS.Add(int64(time.Since(start))) }(time.Now())
|
||||
code := r.ContractCodeReader.Code(addr, codeHash)
|
||||
if len(code) > 0 {
|
||||
r.codeLoaded.LoadOrStore(addr, len(code))
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
func (r *reader) CodeSize(addr common.Address, codeHash common.Hash) (int, error) {
|
||||
defer func(start time.Time) { r.codeReadNS.Add(int64(time.Since(start))) }(time.Now())
|
||||
size, err := r.ContractCodeReader.CodeSize(addr, codeHash)
|
||||
if err == nil && size > 0 {
|
||||
r.codeLoaded.LoadOrStore(addr, size)
|
||||
}
|
||||
return size, err
|
||||
}
|
||||
|
||||
func (r *reader) ReadTimes() ReadDurations {
|
||||
return ReadDurations{
|
||||
Account: time.Duration(r.accountReadNS.Load()),
|
||||
Storage: time.Duration(r.storageReadNS.Load()),
|
||||
Code: time.Duration(r.codeReadNS.Load()),
|
||||
}
|
||||
}
|
||||
|
||||
// CodeLoads returns the count of unique contracts whose code was fetched and
|
||||
// the sum of their first-seen byte lengths. Call after Reader use has quiesced.
|
||||
func (r *reader) CodeLoads() (count, bytes int) {
|
||||
r.codeLoaded.Range(func(_, v any) bool {
|
||||
count++
|
||||
bytes += v.(int)
|
||||
return true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// GetCodeStats returns the statistics of code access.
|
||||
func (r *reader) GetCodeStats() ContractCodeReaderStats {
|
||||
if stater, ok := r.ContractCodeReader.(ContractCodeReaderStater); ok {
|
||||
|
|
@ -571,3 +624,20 @@ func (r *reader) GetStats() ReaderStats {
|
|||
StateStats: r.GetStateStats(),
|
||||
}
|
||||
}
|
||||
|
||||
// PrefetchReadTimes forwards to the wrapped prefetcher, or returns zero.
|
||||
func (r *reader) PrefetchReadTimes() (account, storage time.Duration) {
|
||||
if pr, ok := r.StateReader.(interface {
|
||||
PrefetchReadTimes() (time.Duration, time.Duration)
|
||||
}); ok {
|
||||
return pr.PrefetchReadTimes()
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// WaitPrefetch blocks until the wrapped prefetcher drains; no-op otherwise.
|
||||
func (r *reader) WaitPrefetch() {
|
||||
if pr, ok := r.StateReader.(interface{ Wait() error }); ok {
|
||||
_ = pr.Wait()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -382,3 +382,21 @@ func (r *readerTracker) TouchStorage(addr common.Address, slot common.Hash) {
|
|||
}
|
||||
list[slot] = struct{}{}
|
||||
}
|
||||
|
||||
func (r *readerTracker) ReadTimes() ReadDurations {
|
||||
return r.Reader.(ReadTimer).ReadTimes()
|
||||
}
|
||||
|
||||
func (r *readerTracker) CodeLoads() (count, bytes int) {
|
||||
return r.Reader.(CodeLoadTracker).CodeLoads()
|
||||
}
|
||||
|
||||
// GetStateStats forwards stats from the wrapped reader; without this, BAL
|
||||
// blocks would emit zero cache hit/miss counts.
|
||||
func (r *prefetchStateReader) GetStateStats() StateReaderStats {
|
||||
if stater, ok := r.StateReader.(StateReaderStater); ok {
|
||||
return stater.GetStateStats()
|
||||
}
|
||||
return StateReaderStats{}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -263,3 +263,31 @@ func TestTrackerSurvivesStateDBCache(t *testing.T) {
|
|||
t.Fatal("slot must be tracked on cache hit (storage)")
|
||||
}
|
||||
}
|
||||
|
||||
// TestPrefetchStateReaderForwardsStats locks down that prefetchStateReader
|
||||
// exposes the underlying stateReaderWithStats counters via GetStateStats.
|
||||
func TestPrefetchStateReaderForwardsStats(t *testing.T) {
|
||||
stub := newRefStateReader()
|
||||
addr := testrand.Address()
|
||||
|
||||
cached := newStateReaderWithCache(stub)
|
||||
withStats := newStateReaderWithStats(cached)
|
||||
prefetch := newPrefetchStateReaderInternal(withStats, nil, 1)
|
||||
|
||||
if _, err := prefetch.Account(addr); err != nil {
|
||||
t.Fatalf("Account: %v", err)
|
||||
}
|
||||
if _, err := prefetch.Account(addr); err != nil {
|
||||
t.Fatalf("Account (second): %v", err)
|
||||
}
|
||||
|
||||
stats := withStats.GetStateStats()
|
||||
if stats.AccountCacheHit == 0 || stats.AccountCacheMiss == 0 {
|
||||
t.Fatalf("inner stats not populated: %+v", stats)
|
||||
}
|
||||
gotStats := prefetch.GetStateStats()
|
||||
if gotStats != stats {
|
||||
t.Fatalf("forward mismatch: got %+v, want %+v", gotStats, stats)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,25 @@
|
|||
|
||||
package state
|
||||
|
||||
import "time"
|
||||
|
||||
// ReadDurations groups cumulative read durations by category.
|
||||
type ReadDurations struct {
|
||||
Account time.Duration
|
||||
Storage time.Duration
|
||||
Code time.Duration
|
||||
}
|
||||
|
||||
// ReadTimer exposes a Reader's cumulative read durations.
|
||||
type ReadTimer interface {
|
||||
ReadTimes() ReadDurations
|
||||
}
|
||||
|
||||
// CodeLoadTracker exposes a Reader's deduplicated code-load count and bytes.
|
||||
type CodeLoadTracker interface {
|
||||
CodeLoads() (count, bytes int)
|
||||
}
|
||||
|
||||
// ContractCodeReaderStats aggregates statistics for the contract code reader.
|
||||
type ContractCodeReaderStats struct {
|
||||
CacheHit int64 // Number of cache hits
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import (
|
|||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
|
|
@ -226,13 +225,11 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
|
|||
}
|
||||
s.db.StorageLoaded++
|
||||
|
||||
start := time.Now()
|
||||
value, err := s.db.reader.Storage(s.address, key)
|
||||
if err != nil {
|
||||
s.db.setError(err)
|
||||
return common.Hash{}
|
||||
}
|
||||
s.db.StorageReads += time.Since(start)
|
||||
|
||||
// Schedule the resolved storage slots for prefetching if it's enabled.
|
||||
if s.db.prefetcher != nil && s.data.Root != types.EmptyRootHash {
|
||||
|
|
@ -644,12 +641,6 @@ func (s *stateObject) Code() []byte {
|
|||
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
|
||||
return nil
|
||||
}
|
||||
defer func(start time.Time) {
|
||||
s.db.CodeLoaded += 1
|
||||
s.db.CodeReads += time.Since(start)
|
||||
s.db.CodeLoadBytes += len(s.code)
|
||||
}(time.Now())
|
||||
|
||||
code := s.db.reader.Code(s.address, common.BytesToHash(s.CodeHash()))
|
||||
if len(code) == 0 {
|
||||
s.db.setError(fmt.Errorf("code is not found %x", s.CodeHash()))
|
||||
|
|
@ -668,11 +659,6 @@ func (s *stateObject) CodeSize() int {
|
|||
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
|
||||
return 0
|
||||
}
|
||||
defer func(start time.Time) {
|
||||
s.db.CodeLoaded += 1
|
||||
s.db.CodeReads += time.Since(start)
|
||||
}(time.Now())
|
||||
|
||||
size, err := s.db.reader.CodeSize(s.address, common.BytesToHash(s.CodeHash()))
|
||||
if err != nil {
|
||||
s.db.setError(err)
|
||||
|
|
|
|||
|
|
@ -156,33 +156,24 @@ type StateDB struct {
|
|||
// State witness if cross validation is needed
|
||||
witness *stateless.Witness
|
||||
|
||||
// Measurements gathered during execution for debugging purposes
|
||||
AccountReads time.Duration
|
||||
// Per-block counters surfaced in ExecuteStats; read durations and code-loads
|
||||
// are tracked on the reader (see ReadTimer, CodeLoadTracker).
|
||||
AccountHashes time.Duration
|
||||
AccountUpdates time.Duration
|
||||
AccountCommits time.Duration
|
||||
|
||||
StorageReads time.Duration
|
||||
StorageUpdates time.Duration
|
||||
StorageCommits time.Duration
|
||||
DatabaseCommits time.Duration
|
||||
CodeReads time.Duration
|
||||
|
||||
AccountLoaded int // Number of accounts retrieved from the database during the state transition
|
||||
AccountUpdated int // Number of accounts updated during the state transition
|
||||
AccountDeleted int // Number of accounts deleted during the state transition
|
||||
StorageLoaded int // Number of storage slots retrieved from the database 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
|
||||
|
||||
// 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.
|
||||
CodeLoaded int // Number of contract code loaded during the state transition
|
||||
CodeLoadBytes int // Total bytes of resolved code
|
||||
CodeUpdated int // Number of contracts with code changes that persisted
|
||||
CodeUpdateBytes int // Total bytes of persisted code written
|
||||
AccountLoaded int
|
||||
AccountUpdated int
|
||||
AccountDeleted int
|
||||
StorageLoaded int
|
||||
StorageUpdated atomic.Int64
|
||||
StorageDeleted atomic.Int64
|
||||
CodeUpdated int
|
||||
CodeUpdateBytes int
|
||||
}
|
||||
|
||||
// New creates a new state from a given trie.
|
||||
|
|
@ -635,13 +626,11 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
|
|||
|
||||
s.AccountLoaded++
|
||||
|
||||
start := time.Now()
|
||||
acct, err := s.reader.Account(addr)
|
||||
if err != nil {
|
||||
s.setError(fmt.Errorf("getStateObject (%x) error: %w", addr.Bytes(), err))
|
||||
return nil
|
||||
}
|
||||
s.AccountReads += time.Since(start)
|
||||
|
||||
// Short circuit if the account is not found
|
||||
if acct == nil {
|
||||
|
|
|
|||
|
|
@ -44,6 +44,49 @@ import (
|
|||
// BlockAccessList is the encoding format of AccessListBuilder.
|
||||
type BlockAccessList []AccountAccess
|
||||
|
||||
// UniqueAccountCount returns the number of distinct account addresses in
|
||||
// the block access list.
|
||||
func (e BlockAccessList) UniqueAccountCount() int {
|
||||
return len(e)
|
||||
}
|
||||
|
||||
// UniqueStorageSlotCount returns the total number of distinct (address, slot)
|
||||
// pairs accessed across all accounts. Reads and writes are disjoint per
|
||||
// account by spec validation, so we can sum them directly.
|
||||
func (e BlockAccessList) UniqueStorageSlotCount() int {
|
||||
var n int
|
||||
for i := range e {
|
||||
n += len(e[i].StorageReads) + len(e[i].StorageChanges)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// WrittenCounts groups per-block aggregate write counts derived from the BAL.
|
||||
type WrittenCounts struct {
|
||||
Accounts int
|
||||
StorageSlots int
|
||||
Codes int
|
||||
CodeBytes int
|
||||
}
|
||||
|
||||
// WrittenCounts walks the BAL once and returns the aggregate write counts.
|
||||
func (e BlockAccessList) WrittenCounts() WrittenCounts {
|
||||
var w WrittenCounts
|
||||
for i := range e {
|
||||
a := &e[i]
|
||||
if len(a.StorageChanges) > 0 || len(a.BalanceChanges) > 0 ||
|
||||
len(a.NonceChanges) > 0 || len(a.CodeChanges) > 0 {
|
||||
w.Accounts++
|
||||
}
|
||||
w.StorageSlots += len(a.StorageChanges)
|
||||
if n := len(a.CodeChanges); n > 0 {
|
||||
w.Codes++
|
||||
w.CodeBytes += len(a.CodeChanges[n-1].Code)
|
||||
}
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func (e BlockAccessList) EncodeRLP(_w io.Writer) error {
|
||||
w := rlp.NewEncoderBuffer(_w)
|
||||
l := w.List()
|
||||
|
|
|
|||
Loading…
Reference in a new issue