mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 15:47:21 +00:00
core/stateless: track number of leaf nodes at each trie depth (#32533)
Switches to using counters so that the gauges don't cause any information to be lost. Counters can be used to calculate all sorts of metrics on Grafana. Which is also why min/avg/max logic is removed to make things simple and small here.
This commit is contained in:
parent
1263f3dfc1
commit
c36f7bec7f
2 changed files with 57 additions and 142 deletions
|
|
@ -20,73 +20,32 @@ import (
|
|||
"maps"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
)
|
||||
|
||||
var (
|
||||
accountTrieDepthAvg = metrics.NewRegisteredGauge("witness/trie/account/depth/avg", nil)
|
||||
accountTrieDepthMin = metrics.NewRegisteredGauge("witness/trie/account/depth/min", nil)
|
||||
accountTrieDepthMax = metrics.NewRegisteredGauge("witness/trie/account/depth/max", nil)
|
||||
var accountTrieLeavesAtDepth [16]*metrics.Counter
|
||||
var storageTrieLeavesAtDepth [16]*metrics.Counter
|
||||
|
||||
storageTrieDepthAvg = metrics.NewRegisteredGauge("witness/trie/storage/depth/avg", nil)
|
||||
storageTrieDepthMin = metrics.NewRegisteredGauge("witness/trie/storage/depth/min", nil)
|
||||
storageTrieDepthMax = metrics.NewRegisteredGauge("witness/trie/storage/depth/max", nil)
|
||||
)
|
||||
|
||||
// depthStats tracks min/avg/max statistics for trie access depths.
|
||||
type depthStats struct {
|
||||
totalDepth int64
|
||||
samples int64
|
||||
minDepth int64
|
||||
maxDepth int64
|
||||
}
|
||||
|
||||
// newDepthStats creates a new depthStats with default values.
|
||||
func newDepthStats() *depthStats {
|
||||
return &depthStats{minDepth: -1}
|
||||
}
|
||||
|
||||
// add records a new depth sample.
|
||||
func (d *depthStats) add(n int64) {
|
||||
if n < 0 {
|
||||
return
|
||||
func init() {
|
||||
for i := 0; i < 16; i++ {
|
||||
accountTrieLeavesAtDepth[i] = metrics.NewRegisteredCounter("witness/trie/account/leaves/depth_"+strconv.Itoa(i), nil)
|
||||
storageTrieLeavesAtDepth[i] = metrics.NewRegisteredCounter("witness/trie/storage/leaves/depth_"+strconv.Itoa(i), nil)
|
||||
}
|
||||
d.totalDepth += n
|
||||
d.samples++
|
||||
|
||||
if d.minDepth == -1 || n < d.minDepth {
|
||||
d.minDepth = n
|
||||
}
|
||||
if n > d.maxDepth {
|
||||
d.maxDepth = n
|
||||
}
|
||||
}
|
||||
|
||||
// report uploads the collected statistics into the provided gauges.
|
||||
func (d *depthStats) report(maxGauge, minGauge, avgGauge *metrics.Gauge) {
|
||||
if d.samples == 0 {
|
||||
return
|
||||
}
|
||||
maxGauge.Update(d.maxDepth)
|
||||
minGauge.Update(d.minDepth)
|
||||
avgGauge.Update(d.totalDepth / d.samples)
|
||||
}
|
||||
|
||||
// WitnessStats aggregates statistics for account and storage trie accesses.
|
||||
type WitnessStats struct {
|
||||
accountTrie *depthStats
|
||||
storageTrie *depthStats
|
||||
accountTrieLeaves [16]int64
|
||||
storageTrieLeaves [16]int64
|
||||
}
|
||||
|
||||
// NewWitnessStats creates a new WitnessStats collector.
|
||||
func NewWitnessStats() *WitnessStats {
|
||||
return &WitnessStats{
|
||||
accountTrie: newDepthStats(),
|
||||
storageTrie: newDepthStats(),
|
||||
}
|
||||
return &WitnessStats{}
|
||||
}
|
||||
|
||||
// Add records trie access depths from the given node paths.
|
||||
|
|
@ -102,9 +61,9 @@ func (s *WitnessStats) Add(nodes map[string][]byte, owner common.Hash) {
|
|||
// The last path is always a leaf.
|
||||
if i == len(paths)-1 || !strings.HasPrefix(paths[i+1], paths[i]) {
|
||||
if owner == (common.Hash{}) {
|
||||
s.accountTrie.add(int64(len(path)))
|
||||
s.accountTrieLeaves[len(path)] += 1
|
||||
} else {
|
||||
s.storageTrie.add(int64(len(path)))
|
||||
s.storageTrieLeaves[len(path)] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -112,6 +71,8 @@ func (s *WitnessStats) Add(nodes map[string][]byte, owner common.Hash) {
|
|||
|
||||
// ReportMetrics reports the collected statistics to the global metrics registry.
|
||||
func (s *WitnessStats) ReportMetrics() {
|
||||
s.accountTrie.report(accountTrieDepthMax, accountTrieDepthMin, accountTrieDepthAvg)
|
||||
s.storageTrie.report(storageTrieDepthMax, storageTrieDepthMin, storageTrieDepthAvg)
|
||||
for i := 0; i < 16; i++ {
|
||||
accountTrieLeavesAtDepth[i].Inc(s.accountTrieLeaves[i])
|
||||
storageTrieLeavesAtDepth[i].Inc(s.storageTrieLeaves[i])
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,27 +24,32 @@ import (
|
|||
|
||||
func TestWitnessStatsAdd(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
nodes map[string][]byte
|
||||
owner common.Hash
|
||||
expectedAccountDepth int64
|
||||
expectedStorageDepth int64
|
||||
name string
|
||||
nodes map[string][]byte
|
||||
owner common.Hash
|
||||
expectedAccountLeaves map[int64]int64
|
||||
expectedStorageLeaves map[int64]int64
|
||||
}{
|
||||
{
|
||||
name: "empty nodes",
|
||||
nodes: map[string][]byte{},
|
||||
owner: common.Hash{},
|
||||
expectedAccountDepth: 0,
|
||||
expectedStorageDepth: 0,
|
||||
name: "empty nodes",
|
||||
nodes: map[string][]byte{},
|
||||
owner: common.Hash{},
|
||||
},
|
||||
{
|
||||
name: "single account trie leaf at depth 0",
|
||||
nodes: map[string][]byte{
|
||||
"": []byte("data"),
|
||||
},
|
||||
owner: common.Hash{},
|
||||
expectedAccountLeaves: map[int64]int64{0: 1},
|
||||
},
|
||||
{
|
||||
name: "single account trie leaf",
|
||||
nodes: map[string][]byte{
|
||||
"abc": []byte("data"),
|
||||
},
|
||||
owner: common.Hash{},
|
||||
expectedAccountDepth: 3,
|
||||
expectedStorageDepth: 0,
|
||||
owner: common.Hash{},
|
||||
expectedAccountLeaves: map[int64]int64{3: 1},
|
||||
},
|
||||
{
|
||||
name: "account trie with internal nodes",
|
||||
|
|
@ -53,9 +58,8 @@ func TestWitnessStatsAdd(t *testing.T) {
|
|||
"ab": []byte("data2"),
|
||||
"abc": []byte("data3"),
|
||||
},
|
||||
owner: common.Hash{},
|
||||
expectedAccountDepth: 3, // Only "abc" is a leaf
|
||||
expectedStorageDepth: 0,
|
||||
owner: common.Hash{},
|
||||
expectedAccountLeaves: map[int64]int64{3: 1}, // Only "abc" is a leaf
|
||||
},
|
||||
{
|
||||
name: "multiple account trie branches",
|
||||
|
|
@ -67,9 +71,8 @@ func TestWitnessStatsAdd(t *testing.T) {
|
|||
"bc": []byte("data5"),
|
||||
"bcd": []byte("data6"),
|
||||
},
|
||||
owner: common.Hash{},
|
||||
expectedAccountDepth: 6, // "abc" (3) + "bcd" (3) = 6
|
||||
expectedStorageDepth: 0,
|
||||
owner: common.Hash{},
|
||||
expectedAccountLeaves: map[int64]int64{3: 2}, // "abc" (3) + "bcd" (3)
|
||||
},
|
||||
{
|
||||
name: "siblings are all leaves",
|
||||
|
|
@ -78,9 +81,8 @@ func TestWitnessStatsAdd(t *testing.T) {
|
|||
"ab": []byte("data2"),
|
||||
"ac": []byte("data3"),
|
||||
},
|
||||
owner: common.Hash{},
|
||||
expectedAccountDepth: 6, // 2 + 2 + 2 = 6
|
||||
expectedStorageDepth: 0,
|
||||
owner: common.Hash{},
|
||||
expectedAccountLeaves: map[int64]int64{2: 3},
|
||||
},
|
||||
{
|
||||
name: "storage trie leaves",
|
||||
|
|
@ -90,9 +92,8 @@ func TestWitnessStatsAdd(t *testing.T) {
|
|||
"123": []byte("data3"),
|
||||
"124": []byte("data4"),
|
||||
},
|
||||
owner: common.HexToHash("0x1234"),
|
||||
expectedAccountDepth: 0,
|
||||
expectedStorageDepth: 6, // "123" (3) + "124" (3) = 6
|
||||
owner: common.HexToHash("0x1234"),
|
||||
expectedStorageLeaves: map[int64]int64{3: 2}, // "123" (3) + "124" (3)
|
||||
},
|
||||
{
|
||||
name: "complex trie structure",
|
||||
|
|
@ -107,9 +108,8 @@ func TestWitnessStatsAdd(t *testing.T) {
|
|||
"235": []byte("data8"),
|
||||
"3": []byte("data9"),
|
||||
},
|
||||
owner: common.Hash{},
|
||||
expectedAccountDepth: 13, // "123"(3) + "124"(3) + "234"(3) + "235"(3) + "3"(1) = 13
|
||||
expectedStorageDepth: 0,
|
||||
owner: common.Hash{},
|
||||
expectedAccountLeaves: map[int64]int64{1: 1, 3: 4}, // "123"(3) + "124"(3) + "234"(3) + "235"(3) + "3"(1)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -118,74 +118,28 @@ func TestWitnessStatsAdd(t *testing.T) {
|
|||
stats := NewWitnessStats()
|
||||
stats.Add(tt.nodes, tt.owner)
|
||||
|
||||
var expectedAccountTrieLeaves [16]int64
|
||||
for depth, count := range tt.expectedAccountLeaves {
|
||||
expectedAccountTrieLeaves[depth] = count
|
||||
}
|
||||
var expectedStorageTrieLeaves [16]int64
|
||||
for depth, count := range tt.expectedStorageLeaves {
|
||||
expectedStorageTrieLeaves[depth] = count
|
||||
}
|
||||
|
||||
// Check account trie depth
|
||||
if stats.accountTrie.totalDepth != tt.expectedAccountDepth {
|
||||
t.Errorf("Account trie total depth = %d, want %d", stats.accountTrie.totalDepth, tt.expectedAccountDepth)
|
||||
if stats.accountTrieLeaves != expectedAccountTrieLeaves {
|
||||
t.Errorf("Account trie total depth = %v, want %v", stats.accountTrieLeaves, expectedAccountTrieLeaves)
|
||||
}
|
||||
|
||||
// Check storage trie depth
|
||||
if stats.storageTrie.totalDepth != tt.expectedStorageDepth {
|
||||
t.Errorf("Storage trie total depth = %d, want %d", stats.storageTrie.totalDepth, tt.expectedStorageDepth)
|
||||
if stats.storageTrieLeaves != expectedStorageTrieLeaves {
|
||||
t.Errorf("Storage trie total depth = %v, want %v", stats.storageTrieLeaves, expectedStorageTrieLeaves)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWitnessStatsMinMax(t *testing.T) {
|
||||
stats := NewWitnessStats()
|
||||
|
||||
// Add some account trie nodes with varying depths
|
||||
stats.Add(map[string][]byte{
|
||||
"a": []byte("data1"),
|
||||
"ab": []byte("data2"),
|
||||
"abc": []byte("data3"),
|
||||
"abcd": []byte("data4"),
|
||||
"abcde": []byte("data5"),
|
||||
}, common.Hash{})
|
||||
|
||||
// Only "abcde" is a leaf (depth 5)
|
||||
if stats.accountTrie.minDepth != 5 {
|
||||
t.Errorf("Account trie min depth = %d, want %d", stats.accountTrie.minDepth, 5)
|
||||
}
|
||||
if stats.accountTrie.maxDepth != 5 {
|
||||
t.Errorf("Account trie max depth = %d, want %d", stats.accountTrie.maxDepth, 5)
|
||||
}
|
||||
|
||||
// Add more leaves with different depths
|
||||
stats.Add(map[string][]byte{
|
||||
"x": []byte("data6"),
|
||||
"yz": []byte("data7"),
|
||||
}, common.Hash{})
|
||||
|
||||
// Now we have leaves at depths 1, 2, and 5
|
||||
if stats.accountTrie.minDepth != 1 {
|
||||
t.Errorf("Account trie min depth after update = %d, want %d", stats.accountTrie.minDepth, 1)
|
||||
}
|
||||
if stats.accountTrie.maxDepth != 5 {
|
||||
t.Errorf("Account trie max depth after update = %d, want %d", stats.accountTrie.maxDepth, 5)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWitnessStatsAverage(t *testing.T) {
|
||||
stats := NewWitnessStats()
|
||||
|
||||
// Add nodes that will create leaves at depths 2, 3, and 4
|
||||
stats.Add(map[string][]byte{
|
||||
"aa": []byte("data1"),
|
||||
"bb": []byte("data2"),
|
||||
"ccc": []byte("data3"),
|
||||
"dddd": []byte("data4"),
|
||||
}, common.Hash{})
|
||||
|
||||
// All are leaves: 2 + 2 + 3 + 4 = 11 total, 4 samples
|
||||
expectedAvg := int64(11) / int64(4)
|
||||
actualAvg := stats.accountTrie.totalDepth / stats.accountTrie.samples
|
||||
|
||||
if actualAvg != expectedAvg {
|
||||
t.Errorf("Account trie average depth = %d, want %d", actualAvg, expectedAvg)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkWitnessStatsAdd(b *testing.B) {
|
||||
// Create a realistic trie node structure
|
||||
nodes := make(map[string][]byte)
|
||||
|
|
|
|||
Loading…
Reference in a new issue