blob: c63b9196cbe5fc8f40b3f416fc93363c94a978c5 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This benchmark measures time spent on local (non-synced) operations.
//
// All tests use 9-byte keys.
// "Tiny" benchmarks use testStruct values with empty []byte.
// "Huge" benchmarks use testStruct values with 100K-byte []byte.
// Scan and Exec benchmarks iterate once through all b.N records.
//
// To run: jiri go test v.io/v23/syncbase -test.bench=. -test.run=Benchmark -alsologtostderr=false -stderrthreshold=3
//
// Results (2016-01-15, MacBook Pro, 2.8 GHz Intel Core i7):
// BenchmarkTinyPut-8 1000 1260983 ns/op
// BenchmarkTinyGet-8 2000 772075 ns/op
// BenchmarkTinyDelete-8 2000 1497883 ns/op
// BenchmarkTinyScan-8 50 25266715 ns/op
// BenchmarkTinyExec-8 300 6076609 ns/op
// BenchmarkTinyWatchPuts-8 10 179642079 ns/op
// BenchmarkHugePut-8 1000 2916679 ns/op
// BenchmarkHugeGet-8 1000 1982792 ns/op
// BenchmarkHugeDelete-8 1000 1606726 ns/op
// BenchmarkHugeScan-8 10 133327996 ns/op
// BenchmarkHugeExec-8 20 104448117 ns/op
// BenchmarkHugeWatchPuts-8 3 384569733 ns/op
package syncbase_test
import (
"fmt"
"math/rand"
"testing"
"v.io/v23/context"
wire "v.io/v23/services/syncbase"
"v.io/v23/services/watch"
"v.io/v23/syncbase"
"v.io/v23/syncbase/util"
_ "v.io/x/ref/runtime/factories/roaming"
tu "v.io/x/ref/services/syncbase/testutil"
)
// prepare creates hierarchy "{a,d}/{u,c}" and returns some handles along with a
// cleanup function.
func prepare(b *testing.B) (*context.T, syncbase.Database, syncbase.Collection, func()) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
d := syncbase.NewService(sName).DatabaseForId(wire.Id{"a", "d"}, nil)
if err := d.Create(ctx, nil); err != nil {
b.Fatalf("can't create database: %v", err)
}
c := d.CollectionForId(wire.Id{"u", "c"})
if err := c.Create(ctx, nil); err != nil {
b.Fatalf("can't create collection: %v", err)
}
return ctx, d, c, func() {
b.StopTimer()
cleanup()
b.StartTimer()
}
}
type blobStruct struct {
String string
BlobRef wire.BlobRef
}
type testStruct struct {
A string
B int
C *blobStruct
D []byte
}
func makeKey(i int) string {
return fmt.Sprintf("%09d", i)
}
func makeTestStruct() interface{} {
return testStruct{A: "hello, world!", B: 42}
}
func makeTestStruct100K() interface{} {
var byteSlice []byte
r := rand.New(rand.NewSource(23917))
for i := 0; i < 100*1000; i++ {
byteSlice = append(byteSlice, byte(r.Intn(256)))
}
return testStruct{A: "hello, world!", B: 42, D: byteSlice}
}
func writeRows(b *testing.B, ctx *context.T, c syncbase.Collection, value interface{}) {
writeRowsCustom(b, ctx, c, value, b.N)
}
func writeRowsCustom(b *testing.B, ctx *context.T, c syncbase.Collection, value interface{}, n int) {
for i := 0; i < n; i++ {
if err := c.Put(ctx, makeKey(i), value); err != nil {
b.Fatal(err)
}
}
}
func runPutBenchmark(b *testing.B, value interface{}) {
ctx, _, c, cleanup := prepare(b)
defer cleanup()
b.ResetTimer()
writeRows(b, ctx, c, value)
}
func runGetBenchmark(b *testing.B, value interface{}) {
ctx, _, c, cleanup := prepare(b)
defer cleanup()
writeRows(b, ctx, c, value)
b.ResetTimer()
for i := 0; i < b.N; i++ {
var got testStruct
if err := c.Get(ctx, makeKey(i), &got); err != nil {
b.Fatalf("c.Get failed: %v", err)
}
}
}
func runDeleteBenchmark(b *testing.B, value interface{}) {
ctx, _, c, cleanup := prepare(b)
defer cleanup()
writeRows(b, ctx, c, value)
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := c.Delete(ctx, makeKey(i)); err != nil {
b.Fatalf("c.Delete failed: %v", err)
}
}
}
const numRows = 100
// Measures how long it takes to process 'numRows' rows using scan.
func runScanBenchmark(b *testing.B, value interface{}) {
ctx, _, c, cleanup := prepare(b)
defer cleanup()
b.ResetTimer()
for i := 0; i < b.N; i++ {
b.StopTimer()
// TODO(sadovsky): Write rows just once, and clear any read caches on every
// iteration.
writeRowsCustom(b, ctx, c, value, numRows)
b.StartTimer()
s := c.Scan(ctx, syncbase.Prefix(""))
var got testStruct
for s.Advance() {
s.Value(&got)
}
if s.Err() != nil {
b.Fatalf("stream error: %s", s.Err())
}
}
}
// Measures how long it takes to process 'numRows' rows using exec.
func runExecBenchmark(b *testing.B, value interface{}) {
ctx, d, c, cleanup := prepare(b)
defer cleanup()
b.ResetTimer()
for i := 0; i < b.N; i++ {
b.StopTimer()
// TODO(sadovsky): Write rows just once, and clear any read caches on every
// iteration.
writeRowsCustom(b, ctx, c, value, numRows)
b.StartTimer()
_, s, err := d.Exec(ctx, "select v from c")
if err != nil {
b.Fatalf("exec error: %s", s.Err())
}
for s.Advance() {
s.ResultCount()
}
if s.Err() != nil {
b.Fatalf("stream error: %s", s.Err())
}
}
}
// Measures how long it takes to put and get notified about 'numRows' rows.
func runWatchPutsBenchmark(b *testing.B, value interface{}) {
b.Skip("Hangs on occasion, for unknown reasons - v.io/i/1134")
ctx, d, c, cleanup := prepare(b)
defer cleanup()
b.ResetTimer()
for i := 0; i < b.N; i++ {
w := d.Watch(ctx, watch.ResumeMarker("now"), []wire.CollectionRowPattern{
util.RowPrefixPattern(wire.Id{"u", "c"}, ""),
})
if w.Err() != nil {
b.Fatalf("watch error: %v", w.Err())
}
done := make(chan struct{})
go func() {
seen := 0
for seen < numRows && w.Advance() {
seen++
w.Change()
}
if w.Err() != nil {
b.Fatalf("stream error: %v", w.Err())
}
close(done)
}()
writeRowsCustom(b, ctx, c, value, numRows)
<-done
w.Cancel()
}
}
// Measures how long it takes to put and get notified about a single value.
func runWatchOnePutBenchmark(b *testing.B, value interface{}) {
b.Skip("Hangs on occasion, for unknown reasons - v.io/i/1134")
ctx, d, c, cleanup := prepare(b)
defer cleanup()
w := d.Watch(ctx, watch.ResumeMarker("now"), []wire.CollectionRowPattern{
util.RowPrefixPattern(wire.Id{"u", "c"}, ""),
})
if w.Err() != nil {
b.Fatalf("watch error: %v", w.Err())
}
row := make(chan struct{})
go func() {
seen := 0
for seen < b.N && w.Advance() {
seen++
w.Change()
row <- struct{}{}
}
if w.Err() != nil {
b.Fatalf("stream error: %v", w.Err())
}
}()
b.ResetTimer()
for i := 0; i < b.N; i++ {
writeRowsCustom(b, ctx, c, value, 1)
<-row
}
w.Cancel()
}
func BenchmarkTinyPut(b *testing.B) {
runPutBenchmark(b, makeTestStruct())
}
func BenchmarkTinyGet(b *testing.B) {
runGetBenchmark(b, makeTestStruct())
}
func BenchmarkTinyDelete(b *testing.B) {
runDeleteBenchmark(b, makeTestStruct())
}
func BenchmarkTinyScan(b *testing.B) {
runScanBenchmark(b, makeTestStruct())
}
func BenchmarkTinyExec(b *testing.B) {
runExecBenchmark(b, makeTestStruct())
}
func BenchmarkTinyWatchPuts(b *testing.B) {
runWatchPutsBenchmark(b, makeTestStruct())
}
func BenchmarkTinyWatchOnePut(b *testing.B) {
runWatchOnePutBenchmark(b, makeTestStruct())
}
func BenchmarkHugePut(b *testing.B) {
runPutBenchmark(b, makeTestStruct100K())
}
func BenchmarkHugeGet(b *testing.B) {
runGetBenchmark(b, makeTestStruct100K())
}
func BenchmarkHugeDelete(b *testing.B) {
runDeleteBenchmark(b, makeTestStruct100K())
}
func BenchmarkHugeScan(b *testing.B) {
runScanBenchmark(b, makeTestStruct100K())
}
func BenchmarkHugeExec(b *testing.B) {
runExecBenchmark(b, makeTestStruct100K())
}
func BenchmarkHugeWatchPuts(b *testing.B) {
runWatchPutsBenchmark(b, makeTestStruct100K())
}
func BenchmarkHugeWatchOnePut(b *testing.B) {
runWatchOnePutBenchmark(b, makeTestStruct100K())
}