github.com/MetalBlockchain/subnet-evm@v0.4.9/sync/statesync/trie_sync_stats.go (about) 1 // (c) 2021-2022, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package statesync 5 6 import ( 7 "strings" 8 "sync" 9 "time" 10 11 utils_math "github.com/MetalBlockchain/metalgo/utils/math" 12 "github.com/MetalBlockchain/subnet-evm/metrics" 13 "github.com/ethereum/go-ethereum/common" 14 "github.com/ethereum/go-ethereum/log" 15 ) 16 17 const ( 18 updateFrequency = 1 * time.Minute 19 leafRateHalfLife = 1 * time.Minute 20 epsilon = 1e-6 // added to avoid division by 0 21 ) 22 23 // trieSyncStats keeps track of the total number of leafs and tries 24 // completed during a sync. 25 type trieSyncStats struct { 26 lock sync.Mutex 27 28 lastUpdated time.Time 29 leafsRate utils_math.Averager 30 31 triesRemaining int 32 triesSynced int 33 triesStartTime time.Time 34 leafsSinceUpdate uint64 35 36 remainingLeafs map[*trieSegment]uint64 37 38 // metrics 39 totalLeafs metrics.Counter 40 triesSegmented metrics.Counter 41 leafsRateGauge metrics.Gauge 42 } 43 44 func newTrieSyncStats() *trieSyncStats { 45 now := time.Now() 46 return &trieSyncStats{ 47 remainingLeafs: make(map[*trieSegment]uint64), 48 lastUpdated: now, 49 50 // metrics 51 totalLeafs: metrics.GetOrRegisterCounter("state_sync_total_leafs", nil), 52 leafsRateGauge: metrics.GetOrRegisterGauge("state_sync_leafs_per_second", nil), 53 triesSegmented: metrics.GetOrRegisterCounter("state_sync_tries_segmented", nil), 54 } 55 } 56 57 // incTriesSegmented increases the metric for segmented tries. 58 func (t *trieSyncStats) incTriesSegmented() { 59 t.triesSegmented.Inc(1) // safe to be called concurrently 60 } 61 62 // incLeafs takes a lock and adds [count] to the total number of leafs synced. 63 // periodically outputs a log message with the number of leafs and tries. 64 func (t *trieSyncStats) incLeafs(segment *trieSegment, count uint64, remaining uint64) { 65 t.lock.Lock() 66 defer t.lock.Unlock() 67 68 t.totalLeafs.Inc(int64(count)) 69 t.leafsSinceUpdate += count 70 t.remainingLeafs[segment] = remaining 71 72 now := time.Now() 73 sinceUpdate := now.Sub(t.lastUpdated) 74 if sinceUpdate > updateFrequency { 75 t.updateETA(sinceUpdate, now) 76 t.lastUpdated = now 77 t.leafsSinceUpdate = 0 78 } 79 } 80 81 // estimateSegmentsInProgressTime retrns the ETA for all trie segments 82 // in progress to finish (uses the one with most remaining leafs to estimate). 83 func (t *trieSyncStats) estimateSegmentsInProgressTime() time.Duration { 84 if len(t.remainingLeafs) == 0 { 85 // if there are no tries in progress, return 0 86 return 0 87 } 88 89 maxLeafs := uint64(0) 90 for _, leafs := range t.remainingLeafs { 91 if leafs > maxLeafs { 92 maxLeafs = leafs 93 } 94 } 95 perThreadLeafsRate := (t.leafsRate.Read() + epsilon) / float64(len(t.remainingLeafs)) 96 return time.Duration(float64(maxLeafs)/perThreadLeafsRate) * time.Second 97 } 98 99 // trieDone takes a lock and adds one to the total number of tries synced. 100 func (t *trieSyncStats) trieDone(root common.Hash) { 101 t.lock.Lock() 102 defer t.lock.Unlock() 103 104 for segment := range t.remainingLeafs { 105 if segment.trie.root == root { 106 delete(t.remainingLeafs, segment) 107 } 108 } 109 110 t.triesSynced++ 111 t.triesRemaining-- 112 } 113 114 // updateETA calculates and logs and ETA based on the number of leafs 115 // currently in progress and the number of tries remaining. 116 // assumes lock is held. 117 func (t *trieSyncStats) updateETA(sinceUpdate time.Duration, now time.Time) { 118 leafsRate := float64(t.leafsSinceUpdate) / sinceUpdate.Seconds() 119 if t.leafsRate == nil { 120 t.leafsRate = utils_math.NewAverager(leafsRate, leafRateHalfLife, now) 121 } else { 122 t.leafsRate.Observe(leafsRate, now) 123 } 124 t.leafsRateGauge.Update(int64(t.leafsRate.Read())) 125 126 leafsTime := t.estimateSegmentsInProgressTime() 127 if t.triesSynced == 0 { 128 // provide a separate ETA for the account trie syncing step since we 129 // don't know the total number of storage tries yet. 130 log.Info("state sync: syncing account trie", "ETA", roundETA(leafsTime)) 131 return 132 } 133 134 triesTime := now.Sub(t.triesStartTime) * time.Duration(t.triesRemaining) / time.Duration(t.triesSynced) 135 log.Info( 136 "state sync: syncing storage tries", 137 "triesRemaining", t.triesRemaining, 138 "ETA", roundETA(leafsTime+triesTime), // TODO: should we use max instead of sum? 139 ) 140 } 141 142 func (t *trieSyncStats) setTriesRemaining(triesRemaining int) { 143 t.lock.Lock() 144 defer t.lock.Unlock() 145 146 t.triesRemaining = triesRemaining 147 t.triesStartTime = time.Now() 148 } 149 150 // roundETA rounds [d] to a minute and chops off the "0s" suffix 151 // returns "<1m" if [d] rounds to 0 minutes. 152 func roundETA(d time.Duration) string { 153 str := d.Round(time.Minute).String() 154 str = strings.TrimSuffix(str, "0s") 155 if len(str) == 0 { 156 return "<1m" 157 } 158 return str 159 }