Skip to content

Commit 1d0c7df

Browse files
committed
core/stateless: track number of leaf nodes at each trie depth
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.
1 parent 1263f3d commit 1d0c7df

File tree

2 files changed

+57
-142
lines changed

2 files changed

+57
-142
lines changed

core/stateless/stats.go

Lines changed: 16 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -20,73 +20,32 @@ import (
2020
"maps"
2121
"slices"
2222
"sort"
23+
"strconv"
2324
"strings"
2425

2526
"github.com/ethereum/go-ethereum/common"
2627
"github.com/ethereum/go-ethereum/metrics"
2728
)
2829

29-
var (
30-
accountTrieDepthAvg = metrics.NewRegisteredGauge("witness/trie/account/depth/avg", nil)
31-
accountTrieDepthMin = metrics.NewRegisteredGauge("witness/trie/account/depth/min", nil)
32-
accountTrieDepthMax = metrics.NewRegisteredGauge("witness/trie/account/depth/max", nil)
30+
var accountTrieLeavesAtDepth [16]*metrics.Counter
31+
var storageTrieLeavesAtDepth [16]*metrics.Counter
3332

34-
storageTrieDepthAvg = metrics.NewRegisteredGauge("witness/trie/storage/depth/avg", nil)
35-
storageTrieDepthMin = metrics.NewRegisteredGauge("witness/trie/storage/depth/min", nil)
36-
storageTrieDepthMax = metrics.NewRegisteredGauge("witness/trie/storage/depth/max", nil)
37-
)
38-
39-
// depthStats tracks min/avg/max statistics for trie access depths.
40-
type depthStats struct {
41-
totalDepth int64
42-
samples int64
43-
minDepth int64
44-
maxDepth int64
45-
}
46-
47-
// newDepthStats creates a new depthStats with default values.
48-
func newDepthStats() *depthStats {
49-
return &depthStats{minDepth: -1}
50-
}
51-
52-
// add records a new depth sample.
53-
func (d *depthStats) add(n int64) {
54-
if n < 0 {
55-
return
56-
}
57-
d.totalDepth += n
58-
d.samples++
59-
60-
if d.minDepth == -1 || n < d.minDepth {
61-
d.minDepth = n
62-
}
63-
if n > d.maxDepth {
64-
d.maxDepth = n
33+
func init() {
34+
for i := 0; i < 16; i++ {
35+
accountTrieLeavesAtDepth[i] = metrics.NewRegisteredCounter("witness/trie/account/leaves/depth_"+strconv.Itoa(i), nil)
36+
storageTrieLeavesAtDepth[i] = metrics.NewRegisteredCounter("witness/trie/storage/leaves/depth_"+strconv.Itoa(i), nil)
6537
}
6638
}
6739

68-
// report uploads the collected statistics into the provided gauges.
69-
func (d *depthStats) report(maxGauge, minGauge, avgGauge *metrics.Gauge) {
70-
if d.samples == 0 {
71-
return
72-
}
73-
maxGauge.Update(d.maxDepth)
74-
minGauge.Update(d.minDepth)
75-
avgGauge.Update(d.totalDepth / d.samples)
76-
}
77-
7840
// WitnessStats aggregates statistics for account and storage trie accesses.
7941
type WitnessStats struct {
80-
accountTrie *depthStats
81-
storageTrie *depthStats
42+
accountTrieLeaves [16]int64
43+
storageTrieLeaves [16]int64
8244
}
8345

8446
// NewWitnessStats creates a new WitnessStats collector.
8547
func NewWitnessStats() *WitnessStats {
86-
return &WitnessStats{
87-
accountTrie: newDepthStats(),
88-
storageTrie: newDepthStats(),
89-
}
48+
return &WitnessStats{}
9049
}
9150

9251
// Add records trie access depths from the given node paths.
@@ -102,16 +61,18 @@ func (s *WitnessStats) Add(nodes map[string][]byte, owner common.Hash) {
10261
// The last path is always a leaf.
10362
if i == len(paths)-1 || !strings.HasPrefix(paths[i+1], paths[i]) {
10463
if owner == (common.Hash{}) {
105-
s.accountTrie.add(int64(len(path)))
64+
s.accountTrieLeaves[len(path)] += 1
10665
} else {
107-
s.storageTrie.add(int64(len(path)))
66+
s.storageTrieLeaves[len(path)] += 1
10867
}
10968
}
11069
}
11170
}
11271

11372
// ReportMetrics reports the collected statistics to the global metrics registry.
11473
func (s *WitnessStats) ReportMetrics() {
115-
s.accountTrie.report(accountTrieDepthMax, accountTrieDepthMin, accountTrieDepthAvg)
116-
s.storageTrie.report(storageTrieDepthMax, storageTrieDepthMin, storageTrieDepthAvg)
74+
for i := 0; i < 16; i++ {
75+
accountTrieLeavesAtDepth[i].Inc(s.accountTrieLeaves[i])
76+
storageTrieLeavesAtDepth[i].Inc(s.storageTrieLeaves[i])
77+
}
11778
}

core/stateless/stats_test.go

Lines changed: 41 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -24,27 +24,32 @@ import (
2424

2525
func TestWitnessStatsAdd(t *testing.T) {
2626
tests := []struct {
27-
name string
28-
nodes map[string][]byte
29-
owner common.Hash
30-
expectedAccountDepth int64
31-
expectedStorageDepth int64
27+
name string
28+
nodes map[string][]byte
29+
owner common.Hash
30+
expectedAccountLeaves map[int64]int64
31+
expectedStorageLeaves map[int64]int64
3232
}{
3333
{
34-
name: "empty nodes",
35-
nodes: map[string][]byte{},
36-
owner: common.Hash{},
37-
expectedAccountDepth: 0,
38-
expectedStorageDepth: 0,
34+
name: "empty nodes",
35+
nodes: map[string][]byte{},
36+
owner: common.Hash{},
37+
},
38+
{
39+
name: "single account trie leaf at depth 0",
40+
nodes: map[string][]byte{
41+
"": []byte("data"),
42+
},
43+
owner: common.Hash{},
44+
expectedAccountLeaves: map[int64]int64{0: 1},
3945
},
4046
{
4147
name: "single account trie leaf",
4248
nodes: map[string][]byte{
4349
"abc": []byte("data"),
4450
},
45-
owner: common.Hash{},
46-
expectedAccountDepth: 3,
47-
expectedStorageDepth: 0,
51+
owner: common.Hash{},
52+
expectedAccountLeaves: map[int64]int64{3: 1},
4853
},
4954
{
5055
name: "account trie with internal nodes",
@@ -53,9 +58,8 @@ func TestWitnessStatsAdd(t *testing.T) {
5358
"ab": []byte("data2"),
5459
"abc": []byte("data3"),
5560
},
56-
owner: common.Hash{},
57-
expectedAccountDepth: 3, // Only "abc" is a leaf
58-
expectedStorageDepth: 0,
61+
owner: common.Hash{},
62+
expectedAccountLeaves: map[int64]int64{3: 1}, // Only "abc" is a leaf
5963
},
6064
{
6165
name: "multiple account trie branches",
@@ -67,9 +71,8 @@ func TestWitnessStatsAdd(t *testing.T) {
6771
"bc": []byte("data5"),
6872
"bcd": []byte("data6"),
6973
},
70-
owner: common.Hash{},
71-
expectedAccountDepth: 6, // "abc" (3) + "bcd" (3) = 6
72-
expectedStorageDepth: 0,
74+
owner: common.Hash{},
75+
expectedAccountLeaves: map[int64]int64{3: 2}, // "abc" (3) + "bcd" (3)
7376
},
7477
{
7578
name: "siblings are all leaves",
@@ -78,9 +81,8 @@ func TestWitnessStatsAdd(t *testing.T) {
7881
"ab": []byte("data2"),
7982
"ac": []byte("data3"),
8083
},
81-
owner: common.Hash{},
82-
expectedAccountDepth: 6, // 2 + 2 + 2 = 6
83-
expectedStorageDepth: 0,
84+
owner: common.Hash{},
85+
expectedAccountLeaves: map[int64]int64{2: 3},
8486
},
8587
{
8688
name: "storage trie leaves",
@@ -90,9 +92,8 @@ func TestWitnessStatsAdd(t *testing.T) {
9092
"123": []byte("data3"),
9193
"124": []byte("data4"),
9294
},
93-
owner: common.HexToHash("0x1234"),
94-
expectedAccountDepth: 0,
95-
expectedStorageDepth: 6, // "123" (3) + "124" (3) = 6
95+
owner: common.HexToHash("0x1234"),
96+
expectedStorageLeaves: map[int64]int64{3: 2}, // "123" (3) + "124" (3)
9697
},
9798
{
9899
name: "complex trie structure",
@@ -107,9 +108,8 @@ func TestWitnessStatsAdd(t *testing.T) {
107108
"235": []byte("data8"),
108109
"3": []byte("data9"),
109110
},
110-
owner: common.Hash{},
111-
expectedAccountDepth: 13, // "123"(3) + "124"(3) + "234"(3) + "235"(3) + "3"(1) = 13
112-
expectedStorageDepth: 0,
111+
owner: common.Hash{},
112+
expectedAccountLeaves: map[int64]int64{1: 1, 3: 4}, // "123"(3) + "124"(3) + "234"(3) + "235"(3) + "3"(1)
113113
},
114114
}
115115

@@ -118,74 +118,28 @@ func TestWitnessStatsAdd(t *testing.T) {
118118
stats := NewWitnessStats()
119119
stats.Add(tt.nodes, tt.owner)
120120

121+
var expectedAccountTrieLeaves [16]int64
122+
for depth, count := range tt.expectedAccountLeaves {
123+
expectedAccountTrieLeaves[depth] = count
124+
}
125+
var expectedStorageTrieLeaves [16]int64
126+
for depth, count := range tt.expectedStorageLeaves {
127+
expectedStorageTrieLeaves[depth] = count
128+
}
129+
121130
// Check account trie depth
122-
if stats.accountTrie.totalDepth != tt.expectedAccountDepth {
123-
t.Errorf("Account trie total depth = %d, want %d", stats.accountTrie.totalDepth, tt.expectedAccountDepth)
131+
if stats.accountTrieLeaves != expectedAccountTrieLeaves {
132+
t.Errorf("Account trie total depth = %v, want %v", stats.accountTrieLeaves, expectedAccountTrieLeaves)
124133
}
125134

126135
// Check storage trie depth
127-
if stats.storageTrie.totalDepth != tt.expectedStorageDepth {
128-
t.Errorf("Storage trie total depth = %d, want %d", stats.storageTrie.totalDepth, tt.expectedStorageDepth)
136+
if stats.storageTrieLeaves != expectedStorageTrieLeaves {
137+
t.Errorf("Storage trie total depth = %v, want %v", stats.storageTrieLeaves, expectedStorageTrieLeaves)
129138
}
130139
})
131140
}
132141
}
133142

134-
func TestWitnessStatsMinMax(t *testing.T) {
135-
stats := NewWitnessStats()
136-
137-
// Add some account trie nodes with varying depths
138-
stats.Add(map[string][]byte{
139-
"a": []byte("data1"),
140-
"ab": []byte("data2"),
141-
"abc": []byte("data3"),
142-
"abcd": []byte("data4"),
143-
"abcde": []byte("data5"),
144-
}, common.Hash{})
145-
146-
// Only "abcde" is a leaf (depth 5)
147-
if stats.accountTrie.minDepth != 5 {
148-
t.Errorf("Account trie min depth = %d, want %d", stats.accountTrie.minDepth, 5)
149-
}
150-
if stats.accountTrie.maxDepth != 5 {
151-
t.Errorf("Account trie max depth = %d, want %d", stats.accountTrie.maxDepth, 5)
152-
}
153-
154-
// Add more leaves with different depths
155-
stats.Add(map[string][]byte{
156-
"x": []byte("data6"),
157-
"yz": []byte("data7"),
158-
}, common.Hash{})
159-
160-
// Now we have leaves at depths 1, 2, and 5
161-
if stats.accountTrie.minDepth != 1 {
162-
t.Errorf("Account trie min depth after update = %d, want %d", stats.accountTrie.minDepth, 1)
163-
}
164-
if stats.accountTrie.maxDepth != 5 {
165-
t.Errorf("Account trie max depth after update = %d, want %d", stats.accountTrie.maxDepth, 5)
166-
}
167-
}
168-
169-
func TestWitnessStatsAverage(t *testing.T) {
170-
stats := NewWitnessStats()
171-
172-
// Add nodes that will create leaves at depths 2, 3, and 4
173-
stats.Add(map[string][]byte{
174-
"aa": []byte("data1"),
175-
"bb": []byte("data2"),
176-
"ccc": []byte("data3"),
177-
"dddd": []byte("data4"),
178-
}, common.Hash{})
179-
180-
// All are leaves: 2 + 2 + 3 + 4 = 11 total, 4 samples
181-
expectedAvg := int64(11) / int64(4)
182-
actualAvg := stats.accountTrie.totalDepth / stats.accountTrie.samples
183-
184-
if actualAvg != expectedAvg {
185-
t.Errorf("Account trie average depth = %d, want %d", actualAvg, expectedAvg)
186-
}
187-
}
188-
189143
func BenchmarkWitnessStatsAdd(b *testing.B) {
190144
// Create a realistic trie node structure
191145
nodes := make(map[string][]byte)

0 commit comments

Comments
 (0)