diff --git a/arc_cache_test.go b/arc_cache_test.go index dcd9c6e..898b3d5 100644 --- a/arc_cache_test.go +++ b/arc_cache_test.go @@ -2,12 +2,19 @@ package blockstore import ( "context" + "fmt" + "math/rand" "testing" + "time" blocks "github.com/ipfs/go-block-format" cid "github.com/ipfs/go-cid" ds "github.com/ipfs/go-datastore" + delaystore "github.com/ipfs/go-datastore/delayed" syncds "github.com/ipfs/go-datastore/sync" + delay "github.com/ipfs/go-ipfs-delay" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var exampleBlock = blocks.NewBlock([]byte("foo")) @@ -26,7 +33,7 @@ func testArcCached(ctx context.Context, bs Blockstore) (*arccache, error) { return nil, err } -func createStores(t *testing.T) (*arccache, Blockstore, *callbackDatastore) { +func createStores(t testing.TB) (*arccache, Blockstore, *callbackDatastore) { cd := &callbackDatastore{f: func() {}, ds: ds.NewMapDatastore()} bs := NewBlockstore(syncds.MutexWrap(cd)) arc, err := testArcCached(context.TODO(), bs) @@ -36,6 +43,17 @@ func createStores(t *testing.T) (*arccache, Blockstore, *callbackDatastore) { return arc, bs, cd } +func createStoresWithDelay(t testing.TB, delayed delay.D) (*arccache, Blockstore, *callbackDatastore) { + cd := &callbackDatastore{f: func() {}, ds: ds.NewMapDatastore()} + slowStore := delaystore.New(cd, delayed) + bs := NewBlockstore(syncds.MutexWrap(slowStore)) + arc, err := testArcCached(context.TODO(), bs) + if err != nil { + t.Fatal(err) + } + return arc, bs, cd +} + func trap(message string, cd *callbackDatastore, t *testing.T) { cd.SetFunc(func() { t.Fatal(message) @@ -45,6 +63,84 @@ func untrap(cd *callbackDatastore) { cd.SetFunc(func() {}) } +type storeThrasher struct { + store *arccache + + trace []blocks.Block + + numBlocks int64 + numThreads int64 + + ctx context.Context + cancel context.CancelFunc +} + +func NewThrasher(store *arccache, numBlocks, numThreads int64) (*storeThrasher, []blocks.Block) { + t := &storeThrasher{ + numBlocks: numBlocks, + numThreads: numThreads, + store: store, + } + trace := make([]blocks.Block, t.numBlocks) + for i := int64(0); i < t.numBlocks; i++ { + token := make([]byte, 4) + rand.Read(token) + trace[i] = blocks.NewBlock(token) + } + t.trace = trace + t.ctx, t.cancel = context.WithCancel(context.Background()) + + return t, trace +} + +func (t *storeThrasher) Destroy() { + t.cancel() + t.store = nil +} + +func (t *storeThrasher) Start() { + for i := int64(0); i < t.numThreads; i++ { + go func() { + rs := rand.NewSource(time.Now().UnixNano()) + for { + select { + case <-t.ctx.Done(): + return + default: + idx := rs.Int63() % t.numBlocks + t.store.Put(t.trace[idx]) + } + } + }() + + go func() { + rs := rand.NewSource(time.Now().UnixNano()) + for { + select { + case <-t.ctx.Done(): + return + default: + idx := rs.Int63() % t.numBlocks + t.store.Get(t.trace[idx].Cid()) + } + } + }() + + go func() { + rs := rand.NewSource(time.Now().UnixNano()) + for { + select { + case <-t.ctx.Done(): + return + default: + idx := rs.Int63() % t.numBlocks + t.store.DeleteBlock(t.trace[idx].Cid()) + } + } + }() + } +} + func TestRemoveCacheEntryOnDelete(t *testing.T) { arc, _, cd := createStores(t) @@ -256,6 +352,190 @@ func TestPutManyCaches(t *testing.T) { arc.DeleteBlock(exampleBlock.Cid()) arc.Put(exampleBlock) - trap("PunMany has hit datastore", cd, t) + trap("PutMany has hit datastore", cd, t) arc.PutMany([]blocks.Block{exampleBlock}) } + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +func Benchmark_SimplePutGet(b *testing.B) { + arc, _, _ := createStores(b) + + trace := make([]blocks.Block, b.N) + for i := 0; i < b.N; i++ { + token := make([]byte, 4) + rand.Read(token) + trace[i] = blocks.NewBlock(token) + } + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + if i%2 == 0 { + require.NoError(b, arc.Put(trace[i])) + } + } + + for i := 0; i < b.N; i++ { + _, err := arc.Get(trace[i].Cid()) + if i%2 == 0 { + assert.NoError(b, err) + } + } +} + +func Benchmark_SimplePutDelete(b *testing.B) { + arc, _, _ := createStores(b) + + trace := make([]blocks.Block, b.N) + for i := 0; i < b.N; i++ { + token := make([]byte, 4) + rand.Read(token) + trace[i] = blocks.NewBlock(token) + } + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + require.NoError(b, arc.Put(trace[i])) + } + + for i := 0; i < b.N; i++ { + err := arc.DeleteBlock(trace[i].Cid()) + require.NoError(b, err) + } +} + +func Benchmark_ThrashPut(b *testing.B) { + table := []struct { + numBlocks int64 + threads int64 + delay time.Duration + }{ + { + numBlocks: 1_000_000, + threads: 1, + delay: time.Millisecond * 1, + }, + { + numBlocks: 1_000_000, + threads: 32, + delay: time.Millisecond * 1, + }, + { + numBlocks: 1_000_000, + threads: 64, + delay: time.Millisecond * 1, + }, + { + numBlocks: 1_000_000, + threads: 500, + delay: time.Millisecond * 1, + }, + } + + for _, test := range table { + arc, _, _ := createStoresWithDelay(b, delay.Fixed(test.delay)) + thrasher, trace := NewThrasher(arc, test.numBlocks, test.threads) + thrasher.Start() + + b.Run(fmt.Sprintf("%d_threads-%d_blocks", test.threads, test.numBlocks), func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + require.NoError(b, arc.Put(trace[i])) + } + }) + thrasher.Destroy() + } +} + +func Benchmark_ThrashGet(b *testing.B) { + table := []struct { + numBlocks int64 + threads int64 + delay time.Duration + }{ + { + numBlocks: 1_000_000, + threads: 1, + delay: time.Millisecond * 1, + }, + { + numBlocks: 1_000_000, + threads: 32, + delay: time.Millisecond * 1, + }, + { + numBlocks: 1_000_000, + threads: 64, + delay: time.Millisecond * 1, + }, + { + numBlocks: 1_000_000, + threads: 500, + delay: time.Millisecond * 1, + }, + } + + for _, test := range table { + arc, _, _ := createStoresWithDelay(b, delay.Fixed(test.delay)) + thrasher, trace := NewThrasher(arc, test.numBlocks, test.threads) + thrasher.Start() + + b.Run(fmt.Sprintf("%d_threads-%d_blocks", test.threads, test.numBlocks), func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + arc.Get(trace[i].Cid()) + } + }) + thrasher.Destroy() + } +} + +func Benchmark_ThrashDelete(b *testing.B) { + table := []struct { + numBlocks int64 + threads int64 + delay time.Duration + }{ + { + numBlocks: 1_000_000, + threads: 1, + delay: time.Millisecond * 1, + }, + { + numBlocks: 1_000_000, + threads: 32, + delay: time.Millisecond * 1, + }, + { + numBlocks: 1_000_000, + threads: 64, + delay: time.Millisecond * 1, + }, + { + numBlocks: 1_000_000, + threads: 500, + delay: time.Millisecond * 1, + }, + } + + for _, test := range table { + arc, _, _ := createStoresWithDelay(b, delay.Fixed(test.delay)) + thrasher, trace := NewThrasher(arc, test.numBlocks, test.threads) + thrasher.Start() + + b.Run(fmt.Sprintf("%d_threads-%d_blocks", test.threads, test.numBlocks), func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + arc.DeleteBlock(trace[i].Cid()) + } + }) + thrasher.Destroy() + } +} diff --git a/go.mod b/go.mod index 8c86f55..53932a0 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,20 @@ module github.com/ipfs/go-ipfs-blockstore require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/hashicorp/golang-lru v0.5.4 github.com/ipfs/bbloom v0.0.4 github.com/ipfs/go-block-format v0.0.2 github.com/ipfs/go-cid v0.0.5 github.com/ipfs/go-datastore v0.4.1 + github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8 github.com/ipfs/go-ipfs-ds-help v1.0.0 github.com/ipfs/go-ipfs-util v0.0.1 github.com/ipfs/go-log v0.0.1 github.com/ipfs/go-metrics-interface v0.0.1 github.com/multiformats/go-multihash v0.0.13 + github.com/stretchr/testify v1.3.0 + golang.org/x/net v0.0.0-20190620200207-3b0461eec859 // indirect ) go 1.13 diff --git a/go.sum b/go.sum index 6e0a1ef..e6ebf7e 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= @@ -20,6 +22,7 @@ github.com/ipfs/go-cid v0.0.5 h1:o0Ix8e/ql7Zb5UVUJEUfjsWCIY8t48++9lR8qi6oiJU= github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= github.com/ipfs/go-datastore v0.4.1 h1:W4ZfzyhNi3xmuU5dQhjfuRn/wFuqEE1KnOmmQiOevEY= github.com/ipfs/go-datastore v0.4.1/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= +github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8 h1:NAviDvJ0WXgD+yiL2Rj35AmnfgI11+pHXbdciD917U0= github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-ds-help v1.0.0 h1:bEQ8hMGs80h0sR8O4tfDgV6B01aaF9qeTrujrTLYV3g= github.com/ipfs/go-ipfs-ds-help v1.0.0/go.mod h1:ujAbkeIgkKAWtxxNkoZHWLCyk5JpPoKnGyCcsoF6ueE= @@ -82,6 +85,8 @@ golang.org/x/net v0.0.0-20190227160552-c95aed5357e7 h1:C2F/nMkR/9sfUTpvR3QrjBuTd golang.org/x/net v0.0.0-20190227160552-c95aed5357e7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=