Skip to content

Commit c23fc23

Browse files
committed
fix load gen worker tick interval math to avoid div by zero
1 parent 406aeab commit c23fc23

File tree

2 files changed

+74
-2
lines changed

2 files changed

+74
-2
lines changed

testbed/testbed/load_generator.go

+16-2
Original file line numberDiff line numberDiff line change
@@ -213,12 +213,14 @@ func (ps *ProviderSender) generate() {
213213

214214
var workers sync.WaitGroup
215215

216+
tickDuration := ps.perWorkerTickDuration(numWorkers)
217+
216218
for i := 0; i < numWorkers; i++ {
217219
workers.Add(1)
218220

219221
go func() {
220-
defer workers.Done()
221-
t := time.NewTicker(time.Second / time.Duration(ps.options.DataItemsPerSecond/ps.options.ItemsPerBatch/numWorkers))
222+
223+
t := time.NewTicker(tickDuration)
222224
defer t.Stop()
223225

224226
var prevErr error
@@ -338,3 +340,15 @@ func (ps *ProviderSender) generateLog() error {
338340
}
339341
}
340342
}
343+
344+
// perWorkerTickDuration calculates the tick interval each worker must observe in order to
345+
// produce the desired average DataItemsPerSecond given the constraints of ItemsPerBatch and numWorkers.
346+
//
347+
// Of particular note are cases when the batchesPerSecond required of each worker is less than one due to a high
348+
// number of workers relative to the desired DataItemsPerSecond. If the total batchesPerSecond is less than the
349+
// number of workers then we are dealing with fractional batches per second per worker, so we need float arithmetic.
350+
func (ps *ProviderSender) perWorkerTickDuration(numWorkers int) time.Duration {
351+
batchesPerSecond := float64(ps.options.DataItemsPerSecond) / float64(ps.options.ItemsPerBatch)
352+
batchesPerSecondPerWorker := batchesPerSecond / float64(numWorkers)
353+
return time.Duration(float64(time.Second) / batchesPerSecondPerWorker)
354+
}
+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package testbed
2+
3+
import (
4+
"testing"
5+
"time"
6+
)
7+
8+
func Test_perWorkerTickDuration(t *testing.T) {
9+
for _, test := range []struct {
10+
name string
11+
expectedTickDuration time.Duration
12+
dataItemsPerSecond, itemsPerBatch, numWorkers int
13+
}{
14+
// Because of the way perWorkerTickDuration calculates the tick interval using dataItemsPerSecond,
15+
// it is important to test its behavior with respect to a one-second Duration in particular.
16+
{
17+
name: "less than one second",
18+
expectedTickDuration: 100 * time.Millisecond,
19+
dataItemsPerSecond: 100,
20+
itemsPerBatch: 5,
21+
numWorkers: 2,
22+
},
23+
{
24+
name: "exactly one second",
25+
expectedTickDuration: time.Second,
26+
dataItemsPerSecond: 100,
27+
itemsPerBatch: 5,
28+
numWorkers: 20,
29+
},
30+
{
31+
name: "more than one second",
32+
expectedTickDuration: 5 * time.Second,
33+
dataItemsPerSecond: 100,
34+
itemsPerBatch: 5,
35+
numWorkers: 100,
36+
},
37+
{
38+
name: "default batch size and worker count",
39+
expectedTickDuration: 8103727, // ~8.1ms
40+
dataItemsPerSecond: 1234,
41+
itemsPerBatch: 10,
42+
numWorkers: 1,
43+
},
44+
} {
45+
t.Run(test.name, func(t *testing.T) {
46+
subject := &ProviderSender{
47+
options: LoadOptions{
48+
DataItemsPerSecond: test.dataItemsPerSecond,
49+
ItemsPerBatch: test.itemsPerBatch,
50+
},
51+
}
52+
actual := subject.perWorkerTickDuration(test.numWorkers)
53+
if actual != test.expectedTickDuration {
54+
t.Errorf("got %v; want %v", actual, test.expectedTickDuration)
55+
}
56+
})
57+
}
58+
}

0 commit comments

Comments
 (0)