Skip to content

Commit 2f3aef8

Browse files
authored
fix(dot/sync): sync benchmark (#2234)
- only keep 30 latest samples in a ring buffer - Inject current time in benchmark methods - Remove force setting blocks to `1` when `0` blocks were processed
1 parent a90a6e0 commit 2f3aef8

File tree

3 files changed

+276
-18
lines changed

3 files changed

+276
-18
lines changed

dot/sync/benchmark.go

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,44 +4,64 @@
44
package sync
55

66
import (
7+
"container/ring"
78
"time"
89
)
910

1011
type syncBenchmarker struct {
1112
start time.Time
1213
startBlock uint64
13-
blocksPerSecond []float64
14+
blocksPerSecond *ring.Ring
15+
samplesToKeep int
1416
}
1517

16-
func newSyncBenchmarker() *syncBenchmarker {
18+
func newSyncBenchmarker(samplesToKeep int) *syncBenchmarker {
19+
if samplesToKeep == 0 {
20+
panic("cannot have 0 samples to keep")
21+
}
22+
1723
return &syncBenchmarker{
18-
blocksPerSecond: []float64{},
24+
blocksPerSecond: ring.New(samplesToKeep),
25+
samplesToKeep: samplesToKeep,
1926
}
2027
}
2128

22-
func (b *syncBenchmarker) begin(block uint64) {
23-
b.start = time.Now()
29+
func (b *syncBenchmarker) begin(now time.Time, block uint64) {
30+
b.start = now
2431
b.startBlock = block
2532
}
2633

27-
func (b *syncBenchmarker) end(block uint64) {
28-
duration := time.Since(b.start)
34+
func (b *syncBenchmarker) end(now time.Time, block uint64) {
35+
duration := now.Sub(b.start)
2936
blocks := block - b.startBlock
30-
if blocks == 0 {
31-
blocks = 1
32-
}
3337
bps := float64(blocks) / duration.Seconds()
34-
b.blocksPerSecond = append(b.blocksPerSecond, bps)
38+
b.blocksPerSecond.Value = bps
39+
b.blocksPerSecond = b.blocksPerSecond.Next()
3540
}
3641

3742
func (b *syncBenchmarker) average() float64 {
38-
sum := float64(0)
39-
for _, bps := range b.blocksPerSecond {
43+
var sum float64
44+
var elementsSet int
45+
b.blocksPerSecond.Do(func(x interface{}) {
46+
if x == nil {
47+
return
48+
}
49+
bps := x.(float64)
4050
sum += bps
51+
elementsSet++
52+
})
53+
54+
if elementsSet == 0 {
55+
return 0
4156
}
42-
return sum / float64(len(b.blocksPerSecond))
57+
58+
return sum / float64(elementsSet)
4359
}
4460

4561
func (b *syncBenchmarker) mostRecentAverage() float64 {
46-
return b.blocksPerSecond[len(b.blocksPerSecond)-1]
62+
value := b.blocksPerSecond.Prev().Value
63+
if value == nil {
64+
return 0
65+
}
66+
return value.(float64)
4767
}

dot/sync/benchmark_test.go

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
// Copyright 2022 ChainSafe Systems (ON)
2+
// SPDX-License-Identifier: LGPL-3.0-only
3+
4+
package sync
5+
6+
import (
7+
"container/ring"
8+
"testing"
9+
"time"
10+
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
func Test_newSyncBenchmarker(t *testing.T) {
15+
t.Parallel()
16+
17+
t.Run("10 samples to keep", func(t *testing.T) {
18+
const samplesToKeep = 10
19+
actual := newSyncBenchmarker(samplesToKeep)
20+
21+
expected := &syncBenchmarker{
22+
blocksPerSecond: ring.New(samplesToKeep),
23+
samplesToKeep: samplesToKeep,
24+
}
25+
26+
assert.Equal(t, expected, actual)
27+
})
28+
29+
t.Run("panics on 0 sample to keep", func(t *testing.T) {
30+
const samplesToKeep = 0
31+
assert.PanicsWithValue(t, "cannot have 0 samples to keep", func() {
32+
newSyncBenchmarker(samplesToKeep)
33+
})
34+
})
35+
}
36+
37+
func Test_syncBenchmarker_begin(t *testing.T) {
38+
t.Parallel()
39+
40+
const startSec = 1000
41+
start := time.Unix(startSec, 0)
42+
const startBlock = 10
43+
44+
b := syncBenchmarker{}
45+
b.begin(start, startBlock)
46+
47+
expected := syncBenchmarker{
48+
start: start,
49+
startBlock: startBlock,
50+
}
51+
52+
assert.Equal(t, expected, b)
53+
}
54+
55+
func Test_syncBenchmarker_end(t *testing.T) {
56+
t.Parallel()
57+
58+
const startSec = 1000
59+
start := time.Unix(startSec, 0)
60+
61+
const nowSec = 1010
62+
now := time.Unix(nowSec, 0)
63+
64+
const (
65+
startBlock = 10
66+
endBlock = 12
67+
)
68+
69+
const ringCap = 3
70+
71+
blocksPerSecond := ring.New(ringCap)
72+
blocksPerSecond.Value = 1.00
73+
blocksPerSecond = blocksPerSecond.Next()
74+
75+
b := syncBenchmarker{
76+
start: start,
77+
startBlock: startBlock,
78+
blocksPerSecond: blocksPerSecond,
79+
}
80+
b.end(now, endBlock)
81+
82+
expectedBlocksPerSecond := ring.New(ringCap)
83+
expectedBlocksPerSecond.Value = 1.00
84+
expectedBlocksPerSecond = expectedBlocksPerSecond.Next()
85+
expectedBlocksPerSecond.Value = 0.2
86+
expectedBlocksPerSecond = expectedBlocksPerSecond.Next()
87+
88+
expected := syncBenchmarker{
89+
start: start,
90+
startBlock: startBlock,
91+
blocksPerSecond: expectedBlocksPerSecond,
92+
}
93+
94+
assert.Equal(t, expected, b)
95+
}
96+
97+
func Test_syncBenchmarker_average(t *testing.T) {
98+
t.Parallel()
99+
100+
testCases := map[string]struct {
101+
values []float64
102+
ringCap int
103+
average float64
104+
}{
105+
// zero size ring is not possible due to constructor check
106+
"empty ring": {
107+
ringCap: 1,
108+
},
109+
"single element in one-size ring": {
110+
values: []float64{1.1},
111+
ringCap: 1,
112+
average: 1.1,
113+
},
114+
"single element in two-size ring": {
115+
values: []float64{1.1},
116+
ringCap: 2,
117+
average: 1.1,
118+
},
119+
"two elements in two-size ring": {
120+
values: []float64{1.0, 2.0},
121+
ringCap: 2,
122+
average: 1.5,
123+
},
124+
}
125+
126+
for name, testCase := range testCases {
127+
testCase := testCase
128+
t.Run(name, func(t *testing.T) {
129+
t.Parallel()
130+
131+
blocksPerSecond := ring.New(testCase.ringCap)
132+
for _, value := range testCase.values {
133+
blocksPerSecond.Value = value
134+
blocksPerSecond = blocksPerSecond.Next()
135+
}
136+
137+
benchmarker := syncBenchmarker{
138+
blocksPerSecond: blocksPerSecond,
139+
samplesToKeep: testCase.ringCap,
140+
}
141+
142+
avg := benchmarker.average()
143+
144+
assert.Equal(t, testCase.average, avg)
145+
})
146+
}
147+
}
148+
149+
func Test_syncBenchmarker_mostRecentAverage(t *testing.T) {
150+
t.Parallel()
151+
152+
testCases := map[string]struct {
153+
values []float64
154+
ringCap int
155+
average float64
156+
}{
157+
// zero size ring is not possible due to constructor check
158+
"empty ring": {
159+
ringCap: 1,
160+
},
161+
"single element in one-size ring": {
162+
values: []float64{1.1},
163+
ringCap: 1,
164+
average: 1.1,
165+
},
166+
"single element in two-size ring": {
167+
values: []float64{1.1},
168+
ringCap: 2,
169+
average: 1.1,
170+
},
171+
"two elements in two-size ring": {
172+
values: []float64{1.0, 2.0},
173+
ringCap: 2,
174+
average: 2.0,
175+
},
176+
"three elements in two-size ring": {
177+
values: []float64{1.0, 2.0, 3.0},
178+
ringCap: 2,
179+
average: 3.0,
180+
},
181+
}
182+
183+
for name, testCase := range testCases {
184+
testCase := testCase
185+
t.Run(name, func(t *testing.T) {
186+
t.Parallel()
187+
188+
blocksPerSecond := ring.New(testCase.ringCap)
189+
for _, value := range testCase.values {
190+
blocksPerSecond.Value = value
191+
blocksPerSecond = blocksPerSecond.Next()
192+
}
193+
194+
benchmarker := syncBenchmarker{
195+
blocksPerSecond: blocksPerSecond,
196+
}
197+
198+
avg := benchmarker.mostRecentAverage()
199+
200+
assert.Equal(t, testCase.average, avg)
201+
})
202+
}
203+
}
204+
205+
func Test_syncBenchmarker(t *testing.T) {
206+
t.Parallel()
207+
208+
const samplesToKeep = 5
209+
benchmarker := newSyncBenchmarker(samplesToKeep)
210+
211+
const initialBlock = 10
212+
timeZero := time.Unix(0, 0)
213+
const timeIncrement = time.Second
214+
const baseBlocksIncrement uint64 = 1
215+
216+
startTime := timeZero
217+
endTime := startTime.Add(timeIncrement)
218+
var block uint64 = initialBlock
219+
220+
const samples = 10
221+
for i := 0; i < samples; i++ {
222+
benchmarker.begin(startTime, block)
223+
block += baseBlocksIncrement + uint64(i)
224+
benchmarker.end(endTime, block)
225+
226+
startTime = startTime.Add(timeIncrement)
227+
endTime = startTime.Add(timeIncrement)
228+
}
229+
230+
avg := benchmarker.average()
231+
const expectedAvg = 8.0
232+
assert.Equal(t, expectedAvg, avg)
233+
234+
mostRecentAvg := benchmarker.mostRecentAverage()
235+
const expectedMostRecentAvg = 10.0
236+
assert.Equal(t, expectedMostRecentAvg, mostRecentAvg)
237+
}

dot/sync/chain_sync.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ type chainSyncConfig struct {
160160

161161
func newChainSync(cfg *chainSyncConfig) *chainSync {
162162
ctx, cancel := context.WithCancel(context.Background())
163+
const syncSamplesToKeep = 30
163164
return &chainSync{
164165
ctx: ctx,
165166
cancel: cancel,
@@ -174,7 +175,7 @@ func newChainSync(cfg *chainSyncConfig) *chainSync {
174175
pendingBlocks: cfg.pendingBlocks,
175176
state: bootstrap,
176177
handler: newBootstrapSyncer(cfg.bs),
177-
benchmarker: newSyncBenchmarker(),
178+
benchmarker: newSyncBenchmarker(syncSamplesToKeep),
178179
finalisedCh: cfg.bs.GetFinalisedNotifierChannel(),
179180
minPeers: cfg.minPeers,
180181
maxWorkerRetries: uint16(cfg.maxPeers),
@@ -321,7 +322,7 @@ func (cs *chainSync) logSyncSpeed() {
321322
}
322323

323324
if cs.state == bootstrap {
324-
cs.benchmarker.begin(before.Number.Uint64())
325+
cs.benchmarker.begin(time.Now(), before.Number.Uint64())
325326
}
326327

327328
select {
@@ -345,7 +346,7 @@ func (cs *chainSync) logSyncSpeed() {
345346

346347
switch cs.state {
347348
case bootstrap:
348-
cs.benchmarker.end(after.Number.Uint64())
349+
cs.benchmarker.end(time.Now(), after.Number.Uint64())
349350
target := cs.getTarget()
350351

351352
logger.Infof(

0 commit comments

Comments
 (0)