trie: add combined depth summary

This commit is contained in:
lightclient 2026-02-18 21:46:28 +00:00
parent a596a0aba3
commit 07015878c7
No known key found for this signature in database
GPG key ID: 91289C7FE4ADADBD
2 changed files with 123 additions and 4 deletions

View file

@ -328,9 +328,11 @@ func Summarize(dumpPath string, config *InspectConfig) error {
}
summary.StorageCount++
summary.DepthHistogram[snapshot.MaxDepth]++
summary.StorageTotals.Short += snapshot.Summary.Short
summary.StorageTotals.Full += snapshot.Summary.Full
summary.StorageTotals.Value += snapshot.Summary.Value
for i := 0; i < trieStatLevels; i++ {
summary.StorageLevels[i].Short += record.Levels[i].Short
summary.StorageLevels[i].Full += record.Levels[i].Full
summary.StorageLevels[i].Value += record.Levels[i].Value
}
depthTop.TryInsert(snapshot)
totalTop.TryInsert(snapshot)
@ -339,6 +341,11 @@ func Summarize(dumpPath string, config *InspectConfig) error {
if summary.Account == nil {
return fmt.Errorf("dump file %s does not contain the account trie sentinel record", dumpPath)
}
for i := 0; i < trieStatLevels; i++ {
summary.StorageTotals.Short += summary.StorageLevels[i].Short
summary.StorageTotals.Full += summary.StorageLevels[i].Full
summary.StorageTotals.Value += summary.StorageLevels[i].Value
}
summary.TopByDepth = depthTop.Sorted()
summary.TopByTotalNodes = totalTop.Sorted()
summary.TopByValueNodes = valueTop.Sorted()
@ -522,6 +529,7 @@ type inspectSummary struct {
Account *storageStats
StorageCount uint64
StorageTotals jsonLevel
StorageLevels [trieStatLevels]jsonLevel
DepthHistogram [trieStatLevels]uint64
TopByDepth []*storageStats
TopByTotalNodes []*storageStats
@ -529,6 +537,7 @@ type inspectSummary struct {
}
func (s *inspectSummary) display() {
s.displayCombinedDepthTable()
s.Account.toLevelStats().display("Accounts trie")
fmt.Println("Storage trie aggregate summary")
fmt.Printf("Total storage tries: %d\n", s.StorageCount)
@ -553,6 +562,30 @@ func (s *inspectSummary) display() {
s.displayTop("Top storage tries by value (slot) count", s.TopByValueNodes)
}
func (s *inspectSummary) displayCombinedDepthTable() {
accountTotal := s.Account.Summary.Short + s.Account.Summary.Full + s.Account.Summary.Value
storageTotal := s.StorageTotals.Short + s.StorageTotals.Full + s.StorageTotals.Value
fmt.Println("Trie Depth Distribution")
fmt.Printf("Account Trie: %d nodes\n", accountTotal)
fmt.Printf("Storage Tries: %d nodes across %d tries\n", storageTotal, s.StorageCount)
b := new(strings.Builder)
table := tablewriter.NewWriter(b)
table.SetHeader([]string{"Depth", "Account Nodes", "Storage Nodes"})
for i := 0; i < trieStatLevels; i++ {
accountNodes := s.Account.Levels[i].Short + s.Account.Levels[i].Full + s.Account.Levels[i].Value
storageNodes := s.StorageLevels[i].Short + s.StorageLevels[i].Full + s.StorageLevels[i].Value
if accountNodes == 0 && storageNodes == 0 {
continue
}
table.AppendBulk([][]string{{fmt.Sprint(i), fmt.Sprint(accountNodes), fmt.Sprint(storageNodes)}})
}
table.Render()
fmt.Print(b.String())
fmt.Println()
}
func (s *inspectSummary) displayTop(title string, list []*storageStats) {
fmt.Println(title)
if len(list) == 0 {
@ -575,6 +608,7 @@ func (s *inspectSummary) MarshalJSON() ([]byte, error) {
type jsonStorageSummary struct {
TotalStorageTries uint64 `json:"TotalStorageTries"`
Totals jsonLevel `json:"Totals"`
Levels []jsonLevel `json:"Levels"`
DepthHistogram [trieStatLevels]uint64 `json:"DepthHistogram"`
}
type jsonInspectSummary struct {
@ -593,6 +627,7 @@ func (s *inspectSummary) MarshalJSON() ([]byte, error) {
StorageSummary: jsonStorageSummary{
TotalStorageTries: s.StorageCount,
Totals: s.StorageTotals,
Levels: trimLevels(s.StorageLevels),
DepthHistogram: s.DepthHistogram,
},
TopByDepth: s.TopByDepth,

View file

@ -17,8 +17,11 @@
package trie
import (
"encoding/json"
"math/rand"
"os"
"path/filepath"
"reflect"
"testing"
"github.com/ethereum/go-ethereum/core/rawdb"
@ -56,12 +59,93 @@ func TestInspect(t *testing.T) {
}); err != nil {
t.Fatalf("inspect failed: %v", err)
}
reanalysisPath := filepath.Join(tempDir, "trie-summary-reanalysis.json")
if err := Summarize(dumpPath, &InspectConfig{
TopN: 1,
Path: filepath.Join(tempDir, "trie-summary-reanalysis.json"),
Path: reanalysisPath,
}); err != nil {
t.Fatalf("summarize failed: %v", err)
}
inspectSummaryPath := filepath.Join(tempDir, "trie-summary.json")
inspectOut := loadInspectJSON(t, inspectSummaryPath)
reanalysisOut := loadInspectJSON(t, reanalysisPath)
if len(inspectOut.StorageSummary.Levels) == 0 {
t.Fatal("expected StorageSummary.Levels to be populated")
}
if !reflect.DeepEqual(inspectOut.AccountTrie, reanalysisOut.AccountTrie) {
t.Fatal("account trie summary mismatch between inspect and summarize")
}
if !reflect.DeepEqual(inspectOut.StorageSummary, reanalysisOut.StorageSummary) {
t.Fatal("storage summary mismatch between inspect and summarize")
}
assertStorageTotalsMatchLevels(t, inspectOut)
assertStorageTotalsMatchLevels(t, reanalysisOut)
assertAccountTotalsMatchLevels(t, inspectOut.AccountTrie)
assertAccountTotalsMatchLevels(t, reanalysisOut.AccountTrie)
var histogramTotal uint64
for _, count := range inspectOut.StorageSummary.DepthHistogram {
histogramTotal += count
}
if histogramTotal != inspectOut.StorageSummary.TotalStorageTries {
t.Fatalf("depth histogram total %d does not match total storage tries %d", histogramTotal, inspectOut.StorageSummary.TotalStorageTries)
}
}
type inspectJSONOutput struct {
// Reuse storageStats for AccountTrie JSON to avoid introducing a parallel
// account summary test type. AccountTrie JSON includes Levels+Summary,
// which map directly; other storageStats fields remain zero-values.
AccountTrie storageStats `json:"AccountTrie"`
StorageSummary struct {
TotalStorageTries uint64 `json:"TotalStorageTries"`
Totals jsonLevel `json:"Totals"`
Levels []jsonLevel `json:"Levels"`
DepthHistogram [trieStatLevels]uint64 `json:"DepthHistogram"`
} `json:"StorageSummary"`
}
func loadInspectJSON(t *testing.T, path string) inspectJSONOutput {
t.Helper()
raw, err := os.ReadFile(path)
if err != nil {
t.Fatalf("failed to read %s: %v", path, err)
}
var out inspectJSONOutput
if err := json.Unmarshal(raw, &out); err != nil {
t.Fatalf("failed to decode %s: %v", path, err)
}
return out
}
func assertStorageTotalsMatchLevels(t *testing.T, out inspectJSONOutput) {
t.Helper()
var fromLevels jsonLevel
for _, level := range out.StorageSummary.Levels {
fromLevels.Short += level.Short
fromLevels.Full += level.Full
fromLevels.Value += level.Value
}
if fromLevels.Short != out.StorageSummary.Totals.Short || fromLevels.Full != out.StorageSummary.Totals.Full || fromLevels.Value != out.StorageSummary.Totals.Value {
t.Fatalf("storage totals mismatch: levels=%+v totals=%+v", fromLevels, out.StorageSummary.Totals)
}
}
func assertAccountTotalsMatchLevels(t *testing.T, account storageStats) {
t.Helper()
var fromLevels jsonLevel
for _, level := range account.Levels {
fromLevels.Short += level.Short
fromLevels.Full += level.Full
fromLevels.Value += level.Value
}
if fromLevels.Short != account.Summary.Short || fromLevels.Full != account.Summary.Full || fromLevels.Value != account.Summary.Value {
t.Fatalf("account totals mismatch: levels=%+v totals=%+v", fromLevels, account.Summary)
}
}
func makeAccountsWithStorage(db *testDb, size int, storage bool) (addresses [][20]byte, accounts [][]byte) {