Add four simple benchmarks for adding items to a bundle and flushing The first benchmark measures the cost of Add() only. The second measures the cost of Add() and Flush() -- waiting for all the added items to be handled. These first two benchmarks have an immediate handler (no delay). The third and fourth benchmarks use a handler that takes 1 millisecond (sleeps for 1 millisecond). The third benchmark has bundles of 1 item each, and the fourth bench is more realistic and has 25 items per bundle. Also, added one test that checks rough times are right for a handler that has a 300-millisecond delay. Change-Id: I2d9b8e2971011d1e071f2d05a4d7e2eb5cdd6fc0 Reviewed-on: https://code-review.googlesource.com/c/google-api-go-client/+/47997 Reviewed-by: kokoro <noreply+kokoro@google.com> Reviewed-by: Jean de Klerk <deklerk@google.com>
diff --git a/support/bundler/bundler_test.go b/support/bundler/bundler_test.go index c716adc..5fa34f8 100644 --- a/support/bundler/bundler_test.go +++ b/support/bundler/bundler_test.go
@@ -16,7 +16,7 @@ func TestBundlerCount1(t *testing.T) { // Unbundled case: one item per bundle. handler := &testHandler{} - b := NewBundler(int(0), handler.handle) + b := NewBundler(int(0), handler.handleImmediate) b.BundleCountThreshold = 1 b.DelayThreshold = time.Second @@ -42,7 +42,7 @@ func TestBundlerCount3(t *testing.T) { handler := &testHandler{} - b := NewBundler(int(0), handler.handle) + b := NewBundler(int(0), handler.handleImmediate) b.BundleCountThreshold = 3 b.DelayThreshold = 100 * time.Millisecond // Add 8 items. @@ -68,9 +68,41 @@ } } +// Test that items are handled correctly at roughly the right time with a "slow" +// handler (takes 300 milliseconds) and that the last bundle is automatically +// flushed. +func TestBundlerCountSlowHandler(t *testing.T) { + handler := &testHandler{} + b := NewBundler(int(0), handler.handleSlow) + b.BundleCountThreshold = 3 + b.DelayThreshold = 500 * time.Millisecond + // Add 10 items. + for i := 0; i < 10; i++ { + if err := b.Add(i, 1); err != nil { + t.Fatal(err) + } + } + time.Sleep(4 * 300 * time.Millisecond) + // We should not need to close the bundler. + + bgot := handler.bundles() + bwant := [][]int{{0, 1, 2}, {3, 4, 5}, {6, 7, 8}, {9}} + if !reflect.DeepEqual(bgot, bwant) { + t.Errorf("bundles: got %v, want %v", bgot, bwant) + } + + tgot := quantizeTimes(handler.times(), 100*time.Millisecond) + // Should handle new bundle every 300 milliseconds, and last incomplete + // bundle should get automatically flushed. + twant := []int{0, 3, 6, 9} + if !reflect.DeepEqual(tgot, twant) { + t.Errorf("times: got %v, want [0, 0, non-zero]", tgot) + } +} + func TestBundlerByteThreshold(t *testing.T) { handler := &testHandler{} - b := NewBundler(int(0), handler.handle) + b := NewBundler(int(0), handler.handleImmediate) b.BundleCountThreshold = 10 b.BundleByteThreshold = 3 add := func(i interface{}, s int) { @@ -102,7 +134,7 @@ func TestBundlerLimit(t *testing.T) { handler := &testHandler{} - b := NewBundler(int(0), handler.handle) + b := NewBundler(int(0), handler.handleImmediate) b.BundleCountThreshold = 10 b.BundleByteLimit = 3 add := func(i interface{}, s int) { @@ -312,13 +344,32 @@ return t.t } -func (t *testHandler) handle(b interface{}) { +// Handler takes no time beyond adding to a list +func (t *testHandler) handleImmediate(b interface{}) { t.mu.Lock() defer t.mu.Unlock() t.b = append(t.b, b.([]int)) t.t = append(t.t, time.Now()) } +// Handler takes 300 milliseconds +func (t *testHandler) handleSlow(b interface{}) { + t.mu.Lock() + defer t.mu.Unlock() + t.b = append(t.b, b.([]int)) + t.t = append(t.t, time.Now()) + time.Sleep(300 * time.Millisecond) +} + +// Handler takes one millisecond +func (t *testHandler) handleQuick(b interface{}) { + t.mu.Lock() + defer t.mu.Unlock() + t.b = append(t.b, b.([]int)) + t.t = append(t.t, time.Now()) + time.Sleep(time.Millisecond) +} + // Round times to the nearest q and express them as the number of q // since the first time. // E.g. if q is 100ms, then a time within 50ms of the first time @@ -354,3 +405,70 @@ } } } + +// Measure the cost of adding a bunch of items only, though some handling may be +// happening in the background +func BenchmarkBundlerAdd(bench *testing.B) { + // Unbundled case: one item per bundle. + handler := &testHandler{} + b := NewBundler(int(0), handler.handleImmediate) + b.BundleCountThreshold = 1 + b.DelayThreshold = time.Second + + for i := 0; i < bench.N; i++ { + if err := b.Add(i, 1); err != nil { + bench.Fatal(err) + } + } +} + +// Measure the cost of adding a bunch of items, and then waiting for them all to +// be handled, when handling is immediate (no delay) +func BenchmarkBundlerAddAndFlush(bench *testing.B) { + // Unbundled case: one item per bundle. + handler := &testHandler{} + b := NewBundler(int(0), handler.handleImmediate) + b.BundleCountThreshold = 1 + b.DelayThreshold = time.Second + + for i := 0; i < bench.N; i++ { + if err := b.Add(i, 1); err != nil { + bench.Fatal(err) + } + } + b.Flush() +} + +// Measure the cost of adding a bunch of items, and then waiting for them all to +// be handled, when handling a bundle (1 item only) takes one millisecond +func BenchmarkBundlerAddAndFlushSlow1(bench *testing.B) { + // Unbundled case: one item per bundle. + handler := &testHandler{} + b := NewBundler(int(0), handler.handleQuick) + b.BundleCountThreshold = 1 + b.DelayThreshold = time.Second + + for i := 0; i < bench.N; i++ { + if err := b.Add(i, 1); err != nil { + bench.Fatal(err) + } + } + b.Flush() +} + +// Measure the cost of adding a bunch of items, and then waiting for them all to +// be handled, when handling a bundle (25 items) takes one millisecond +func BenchmarkBundlerAddAndFlushSlow25(bench *testing.B) { + // More realistic: 25 items per bundle + handler := &testHandler{} + b := NewBundler(int(0), handler.handleQuick) + b.BundleCountThreshold = 25 + b.DelayThreshold = time.Second + + for i := 0; i < bench.N; i++ { + if err := b.Add(i, 1); err != nil { + bench.Fatal(err) + } + } + b.Flush() +}