mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-23 21:26:42 +00:00
core/state: state size metrics
Signed-off-by: jsvisa <delweng@gmail.com>
This commit is contained in:
parent
8ce2047348
commit
2f90e8a3da
2 changed files with 322 additions and 0 deletions
|
|
@ -139,6 +139,9 @@ type StateDB struct {
|
||||||
witness *stateless.Witness
|
witness *stateless.Witness
|
||||||
witnessStats *stateless.WitnessStats
|
witnessStats *stateless.WitnessStats
|
||||||
|
|
||||||
|
// State size tracking
|
||||||
|
stateSizeGen *stateSizeGenerator
|
||||||
|
|
||||||
// Measurements gathered during execution for debugging purposes
|
// Measurements gathered during execution for debugging purposes
|
||||||
AccountReads time.Duration
|
AccountReads time.Duration
|
||||||
AccountHashes time.Duration
|
AccountHashes time.Duration
|
||||||
|
|
@ -186,6 +189,15 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro
|
||||||
if db.TrieDB().IsVerkle() {
|
if db.TrieDB().IsVerkle() {
|
||||||
sdb.accessEvents = NewAccessEvents(db.PointCache())
|
sdb.accessEvents = NewAccessEvents(db.PointCache())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize state size tracking
|
||||||
|
if cachingDB, ok := db.(*CachingDB); ok {
|
||||||
|
sdb.stateSizeGen = newStateSizeGenerator(cachingDB.disk, db, root)
|
||||||
|
if !db.TrieDB().IsVerkle() {
|
||||||
|
sdb.stateSizeGen.run()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return sdb, nil
|
return sdb, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1350,6 +1362,11 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorag
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
s.TrieDBCommits += time.Since(start)
|
s.TrieDBCommits += time.Since(start)
|
||||||
|
|
||||||
|
// Update state size metrics
|
||||||
|
if s.stateSizeGen != nil {
|
||||||
|
s.stateSizeGen.updateMetrics(ret)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.reader, _ = s.db.Reader(s.originalRoot)
|
s.reader, _ = s.db.Reader(s.originalRoot)
|
||||||
|
|
|
||||||
305
core/state/statesize.go
Normal file
305
core/state/statesize.go
Normal file
|
|
@ -0,0 +1,305 @@
|
||||||
|
// Copyright 2025 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// State size metrics
|
||||||
|
var (
|
||||||
|
// Baseline state size metrics
|
||||||
|
stateSizeAccountsCountMeter = metrics.NewRegisteredMeter("statedb/statesize/accounts/count", nil)
|
||||||
|
stateSizeAccountsBytesMeter = metrics.NewRegisteredMeter("statedb/statesize/accounts/bytes", nil)
|
||||||
|
stateSizeStorageCountMeter = metrics.NewRegisteredMeter("statedb/statesize/storage/count", nil)
|
||||||
|
stateSizeStorageBytesMeter = metrics.NewRegisteredMeter("statedb/statesize/storage/bytes", nil)
|
||||||
|
stateSizeTrieNodesCountMeter = metrics.NewRegisteredMeter("statedb/statesize/trienodes/count", nil)
|
||||||
|
stateSizeTrieNodesBytesMeter = metrics.NewRegisteredMeter("statedb/statesize/trienodes/bytes", nil)
|
||||||
|
stateSizeContractsCountMeter = metrics.NewRegisteredMeter("statedb/statesize/contracts/count", nil)
|
||||||
|
stateSizeContractsBytesMeter = metrics.NewRegisteredMeter("statedb/statesize/contracts/bytes", nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// StateSizeMetrics represents the current state size statistics
|
||||||
|
type StateSizeMetrics struct {
|
||||||
|
Root common.Hash // Root hash of the state trie
|
||||||
|
AccountCount uint64
|
||||||
|
AccountBytes uint64
|
||||||
|
StorageCount uint64
|
||||||
|
StorageBytes uint64
|
||||||
|
TrieNodeCount uint64
|
||||||
|
TrieNodeBytes uint64
|
||||||
|
ContractCount uint64
|
||||||
|
ContractBytes uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateSizeGenerator handles the initialization and tracking of state size metrics
|
||||||
|
type stateSizeGenerator struct {
|
||||||
|
db ethdb.KeyValueStore
|
||||||
|
sdb Database
|
||||||
|
|
||||||
|
// Generator state
|
||||||
|
running bool
|
||||||
|
abort chan chan struct{}
|
||||||
|
done chan struct{}
|
||||||
|
|
||||||
|
// Metrics state
|
||||||
|
metrics *StateSizeMetrics
|
||||||
|
metricsLock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// newStateSizeGenerator creates a new state size generator
|
||||||
|
func newStateSizeGenerator(db ethdb.KeyValueStore, sdb Database, root common.Hash) *stateSizeGenerator {
|
||||||
|
return &stateSizeGenerator{
|
||||||
|
db: db,
|
||||||
|
sdb: sdb,
|
||||||
|
abort: make(chan chan struct{}),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
metrics: &StateSizeMetrics{Root: root},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// run starts the state size initialization in the background
|
||||||
|
func (g *stateSizeGenerator) run() {
|
||||||
|
if g.running {
|
||||||
|
g.stop()
|
||||||
|
log.Warn("Paused the leftover state size generation cycle")
|
||||||
|
}
|
||||||
|
g.running = true
|
||||||
|
go g.generate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop terminates the background generation
|
||||||
|
func (g *stateSizeGenerator) stop() {
|
||||||
|
if !g.running {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ch := make(chan struct{})
|
||||||
|
g.abort <- ch
|
||||||
|
<-ch
|
||||||
|
g.running = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate performs the state size initialization
|
||||||
|
func (g *stateSizeGenerator) generate() {
|
||||||
|
defer close(g.done)
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
if g.hasExistingMetrics() {
|
||||||
|
log.Info("State size metrics already initialized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for snapshot generator to complete
|
||||||
|
if db := g.sdb.TrieDB(); db != nil {
|
||||||
|
for !db.SnapshotCompleted() {
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Starting state size initialization")
|
||||||
|
g.initializeMetrics()
|
||||||
|
log.Info("Completed state size initialization", "elapsed", time.Since(start))
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasExistingMetrics checks if state size metrics already exist in the database
|
||||||
|
func (g *stateSizeGenerator) hasExistingMetrics() bool {
|
||||||
|
// Check for existing metrics by looking for a marker key
|
||||||
|
marker := rawdb.ReadStateSizeMetrics(g.db)
|
||||||
|
// TODO: check if the marker's root is the same as the current root
|
||||||
|
return marker != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// initializeMetrics performs the actual metrics initialization
|
||||||
|
func (g *stateSizeGenerator) initializeMetrics() {
|
||||||
|
var (
|
||||||
|
accountCount, accountBytes uint64
|
||||||
|
storageCount, storageBytes uint64
|
||||||
|
trieNodeCount, trieNodeBytes uint64
|
||||||
|
contractCount, contractBytes uint64
|
||||||
|
)
|
||||||
|
|
||||||
|
// Process accounts
|
||||||
|
log.Info("Initializing account metrics")
|
||||||
|
accountIter := g.db.NewIterator(rawdb.SnapshotAccountPrefix, nil)
|
||||||
|
defer accountIter.Release()
|
||||||
|
|
||||||
|
for accountIter.Next() {
|
||||||
|
key := accountIter.Key()
|
||||||
|
value := accountIter.Value()
|
||||||
|
|
||||||
|
// Count account
|
||||||
|
accountCount++
|
||||||
|
accountBytes += uint64(len(key) + len(value))
|
||||||
|
|
||||||
|
// Check if account has code (contract)
|
||||||
|
var account types.StateAccount
|
||||||
|
if err := rlp.DecodeBytes(value, &account); err == nil {
|
||||||
|
if !bytes.Equal(account.CodeHash, types.EmptyCodeHash[:]) {
|
||||||
|
contractCount++
|
||||||
|
// Code size will be counted separately
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process storage for this account
|
||||||
|
storageIter := g.db.NewIterator(append(rawdb.SnapshotStoragePrefix, key[1:]...), nil)
|
||||||
|
for storageIter.Next() {
|
||||||
|
storageKey := storageIter.Key()
|
||||||
|
storageValue := storageIter.Value()
|
||||||
|
storageCount++
|
||||||
|
storageBytes += uint64(len(storageKey) + len(storageValue))
|
||||||
|
}
|
||||||
|
storageIter.Release()
|
||||||
|
|
||||||
|
// Check for abort
|
||||||
|
select {
|
||||||
|
case abort := <-g.abort:
|
||||||
|
close(abort)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process trie nodes
|
||||||
|
log.Info("Initializing trie node metrics")
|
||||||
|
trieNodeIter := g.db.NewIterator(rawdb.TrieNodeAccountPrefix, nil)
|
||||||
|
defer trieNodeIter.Release()
|
||||||
|
|
||||||
|
for trieNodeIter.Next() {
|
||||||
|
key := trieNodeIter.Key()
|
||||||
|
value := trieNodeIter.Value()
|
||||||
|
trieNodeCount++
|
||||||
|
trieNodeBytes += uint64(len(key) + len(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process storage trie nodes
|
||||||
|
storageTrieIter := g.db.NewIterator(rawdb.TrieNodeStoragePrefix, nil)
|
||||||
|
defer storageTrieIter.Release()
|
||||||
|
|
||||||
|
for storageTrieIter.Next() {
|
||||||
|
key := storageTrieIter.Key()
|
||||||
|
value := storageTrieIter.Value()
|
||||||
|
trieNodeCount++
|
||||||
|
trieNodeBytes += uint64(len(key) + len(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process contract code
|
||||||
|
log.Info("Initializing contract code metrics")
|
||||||
|
codeIter := g.db.NewIterator(rawdb.CodePrefix, nil)
|
||||||
|
defer codeIter.Release()
|
||||||
|
|
||||||
|
for codeIter.Next() {
|
||||||
|
key := codeIter.Key()
|
||||||
|
value := codeIter.Value()
|
||||||
|
contractBytes += uint64(len(key) + len(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update metrics
|
||||||
|
g.metricsLock.Lock()
|
||||||
|
g.metrics.AccountCount = accountCount
|
||||||
|
g.metrics.AccountBytes = accountBytes
|
||||||
|
g.metrics.StorageCount = storageCount
|
||||||
|
g.metrics.StorageBytes = storageBytes
|
||||||
|
g.metrics.TrieNodeCount = trieNodeCount
|
||||||
|
g.metrics.TrieNodeBytes = trieNodeBytes
|
||||||
|
g.metrics.ContractCount = contractCount
|
||||||
|
g.metrics.ContractBytes = contractBytes
|
||||||
|
g.metricsLock.Unlock()
|
||||||
|
|
||||||
|
// Update metrics in database
|
||||||
|
g.persistMetrics()
|
||||||
|
|
||||||
|
// Update global metrics
|
||||||
|
stateSizeAccountsCountMeter.Mark(int64(accountCount))
|
||||||
|
stateSizeAccountsBytesMeter.Mark(int64(accountBytes))
|
||||||
|
stateSizeStorageCountMeter.Mark(int64(storageCount))
|
||||||
|
stateSizeStorageBytesMeter.Mark(int64(storageBytes))
|
||||||
|
stateSizeTrieNodesCountMeter.Mark(int64(trieNodeCount))
|
||||||
|
stateSizeTrieNodesBytesMeter.Mark(int64(trieNodeBytes))
|
||||||
|
stateSizeContractsCountMeter.Mark(int64(contractCount))
|
||||||
|
stateSizeContractsBytesMeter.Mark(int64(contractBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// persistMetrics saves the current metrics to the database
|
||||||
|
func (g *stateSizeGenerator) persistMetrics() {
|
||||||
|
g.metricsLock.RLock()
|
||||||
|
metrics := *g.metrics
|
||||||
|
g.metricsLock.RUnlock()
|
||||||
|
|
||||||
|
data, err := rlp.EncodeToBytes(metrics)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to encode state size metrics", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
batch := g.db.NewBatch()
|
||||||
|
rawdb.WriteStateSizeMetrics(batch, data)
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
log.Error("Failed to persist state size metrics", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateMetrics updates metrics based on state changes
|
||||||
|
func (g *stateSizeGenerator) updateMetrics(update *stateUpdate) {
|
||||||
|
var diff StateSizeMetrics
|
||||||
|
|
||||||
|
// Calculate account changes
|
||||||
|
for _, data := range update.accounts {
|
||||||
|
if len(data) > 0 {
|
||||||
|
diff.AccountCount++
|
||||||
|
diff.AccountBytes += uint64(common.HashLength + len(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate storage changes
|
||||||
|
for _, slots := range update.storages {
|
||||||
|
for _, data := range slots {
|
||||||
|
if len(data) > 0 {
|
||||||
|
diff.StorageCount++
|
||||||
|
diff.StorageBytes += uint64(2*common.HashLength + len(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate trie node changes
|
||||||
|
for _, nodeSet := range update.nodes.Sets {
|
||||||
|
for _, node := range nodeSet.Nodes {
|
||||||
|
diff.TrieNodeCount++
|
||||||
|
diff.TrieNodeBytes += uint64(len(node.Blob))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update local metrics
|
||||||
|
g.metricsLock.Lock()
|
||||||
|
g.metrics.Root = update.root
|
||||||
|
g.metrics.AccountCount += diff.AccountCount
|
||||||
|
g.metrics.AccountBytes += diff.AccountBytes
|
||||||
|
g.metrics.StorageCount += diff.StorageCount
|
||||||
|
g.metrics.StorageBytes += diff.StorageBytes
|
||||||
|
g.metrics.TrieNodeCount += diff.TrieNodeCount
|
||||||
|
g.metrics.TrieNodeBytes += diff.TrieNodeBytes
|
||||||
|
g.metricsLock.Unlock()
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue