diff --git a/tests/bench_test.go b/tests/bench_test.go new file mode 100644 index 0000000..cb00b92 --- /dev/null +++ b/tests/bench_test.go @@ -0,0 +1,180 @@ +package tests + +import ( + "context" + "fmt" + "sync/atomic" + "testing" + "time" + + orbitdb "berty.tech/go-orbit-db" + "berty.tech/go-orbit-db/iface" + "github.com/stretchr/testify/require" +) + +// go test -benchmem -run='^$' -bench '^BenchmarkKeyValueStore$' berty.tech/go-orbit-db/tests -v -count=1 -benchtime=1000x +func BenchmarkKeyValueStore(b *testing.B) { + tmpDir, clean := testingTempDirB(b, "db-keystore") + defer clean() + + cases := []struct{ Name, Directory string }{ + {Name: "in memory", Directory: ":memory:"}, + {Name: "persistent", Directory: tmpDir}, + } + + for _, c := range cases { + b.Run(c.Name, func(b *testing.B) { + testingKeyValueStoreB(b, c.Directory) + }) + } +} + +// go test -benchmem -run='^$' -bench '^BenchmarkKeyValueStorePB$' berty.tech/go-orbit-db/tests -v -count=1 -benchtime=1000x +func BenchmarkKeyValueStorePB(b *testing.B) { + tmpDir, clean := testingTempDirB(b, "db-keystore") + defer clean() + + cases := []struct{ Name, Directory string }{ + {Name: "in memory", Directory: ":memory:"}, + {Name: "persistent", Directory: tmpDir}, + } + + // Parallel + for _, c := range cases { + b.Run(c.Name, func(b *testing.B) { + testingKeyValueStorePB(b, c.Directory) + }) + } +} + +func setupTestingKeyValueStoreB(ctx context.Context, b *testing.B, dir string) (iface.OrbitDB, iface.KeyValueStore, func()) { + b.Helper() + + mocknet := testingMockNetB(b) + node, nodeClean := testingIPFSNodeB(ctx, b, mocknet) + + db1IPFS := testingCoreAPIB(b, node) + + odb, err := orbitdb.NewOrbitDB(ctx, db1IPFS, &orbitdb.NewOrbitDBOptions{ + Directory: &dir, + }) + require.NoError(b, err) + + db, err := odb.KeyValue(ctx, "orbit-db-tests", nil) + require.NoError(b, err) + + cleanup := func() { + nodeClean() + odb.Close() + db.Close() + } + return odb, db, cleanup +} + +func testingKeyValueStoreB(b *testing.B, dir string) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + b.Run("put", func(b *testing.B) { + _, db, cleanup := setupTestingKeyValueStoreB(ctx, b, dir) + defer cleanup() + + b.ResetTimer() + start := time.Now() + for i := 0; i < b.N; i++ { + key := fmt.Sprintf("key%d", i) + value := []byte(fmt.Sprintf("hello%d", i)) + _, err := db.Put(ctx, key, value) + require.NoError(b, err) + } + elapsed := time.Since(start) + tps := float64(b.N) / elapsed.Seconds() + b.ReportMetric(tps, "tps") + b.ReportMetric(elapsed.Seconds(), "elapsed_seconds") + }) + + b.Run("get", func(b *testing.B) { + _, db, cleanup := setupTestingKeyValueStoreB(ctx, b, dir) + defer cleanup() + + for i := 0; i < b.N; i++ { + key := fmt.Sprintf("key%d", i) + value := []byte(fmt.Sprintf("hello%d", i)) + _, err := db.Put(ctx, key, value) + require.NoError(b, err) + } + + b.ResetTimer() + start := time.Now() + for i := 0; i < b.N; i++ { + key := fmt.Sprintf("key%d", i) + value, err := db.Get(ctx, key) + require.NoError(b, err) + require.Equal(b, string(value), fmt.Sprintf("hello%d", i)) + } + elapsed := time.Since(start) + tps := float64(b.N) / elapsed.Seconds() + b.ReportMetric(tps, "tps") + b.ReportMetric(elapsed.Seconds(), "elapsed_seconds") + }) +} + +func testingKeyValueStorePB(b *testing.B, dir string) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + b.Run("pb-put", func(b *testing.B) { + _, db, cleanup := setupTestingKeyValueStoreB(ctx, b, dir) + defer cleanup() + + b.ResetTimer() + start := time.Now() + var successCount int64 + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + key := fmt.Sprintf("key%d", i) + value := []byte(fmt.Sprintf("hello%d", i)) + _, err := db.Put(ctx, key, value) + if err == nil { + atomic.AddInt64(&successCount, 1) + } + i++ + } + }) + elapsed := time.Since(start) + tps := float64(successCount) / elapsed.Seconds() + b.ReportMetric(tps, "tps") + b.ReportMetric(elapsed.Seconds(), "elapsed_seconds") + }) + + b.Run("pb-get", func(b *testing.B) { + _, db, cleanup := setupTestingKeyValueStoreB(ctx, b, dir) + defer cleanup() + + // Populate the database + for i := 0; i < b.N; i++ { + key := fmt.Sprintf("key%d", i) + value := []byte(fmt.Sprintf("hello%d", i)) + _, err := db.Put(ctx, key, value) + require.NoError(b, err) + } + + b.ResetTimer() + start := time.Now() + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + key := fmt.Sprintf("key%d", i) + value, err := db.Get(ctx, key) + require.NoError(b, err) + require.Equal(b, string(value), fmt.Sprintf("hello%d", i)) + i++ + } + }) + elapsed := time.Since(start) + tps := float64(b.N) / elapsed.Seconds() + b.ReportMetric(tps, "tps") + b.ReportMetric(elapsed.Seconds(), "elapsed_seconds") + }) +} \ No newline at end of file diff --git a/tests/benchmark_test.go b/tests/benchmark_test.go new file mode 100644 index 0000000..2d7e831 --- /dev/null +++ b/tests/benchmark_test.go @@ -0,0 +1,176 @@ +package tests + +import ( + "context" + "crypto/rand" + "encoding/base64" + "io/ioutil" + "os" + "testing" + + "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/core/peer" + + ds "github.com/ipfs/go-datastore" + dsync "github.com/ipfs/go-datastore/sync" + cfg "github.com/ipfs/kubo/config" + ipfsCore "github.com/ipfs/kubo/core" + "github.com/ipfs/kubo/core/coreapi" + coreiface "github.com/ipfs/kubo/core/coreiface" + mock "github.com/ipfs/kubo/core/mock" + "github.com/ipfs/kubo/repo" + mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/stretchr/testify/require" +) + +func testingRepoB(_ context.Context, b *testing.B) repo.Repo { + b.Helper() + + c := cfg.Config{} + priv, pub, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, rand.Reader) + require.NoError(b, err) + + pid, err := peer.IDFromPublicKey(pub) + require.NoError(b, err) + + privkeyb, err := crypto.MarshalPrivateKey(priv) + require.NoError(b, err) + + c.Pubsub.Enabled = cfg.True + c.Bootstrap = []string{} + c.Addresses.Swarm = []string{"/ip4/127.0.0.1/tcp/4001", "/ip4/127.0.0.1/udp/4001/quic"} + c.Identity.PeerID = pid.String() + c.Identity.PrivKey = base64.StdEncoding.EncodeToString(privkeyb) + c.Swarm.ResourceMgr.Enabled = cfg.False // we don'b need ressources manager for test + + return &repo.Mock{ + D: dsync.MutexWrap(ds.NewMapDatastore()), + C: c, + } +} + +func testingIPFSAPIsB(ctx context.Context, b *testing.B, count int) ([]coreiface.CoreAPI, func()) { + b.Helper() + + mn := testingMockNetB(b) + defer mn.Close() + + coreAPIs := make([]coreiface.CoreAPI, count) + cleans := make([]func(), count) + + for i := 0; i < count; i++ { + node := (*ipfsCore.IpfsNode)(nil) + + node, cleans[i] = testingIPFSNodeB(ctx, b, mn) + coreAPIs[i] = testingCoreAPIB(b, node) + } + + return coreAPIs, func() { + for i := 0; i < count; i++ { + cleans[i]() + } + } +} + +func testingIPFSAPIsNonMockedB(ctx context.Context, b *testing.B, count int) ([]coreiface.CoreAPI, func()) { + b.Helper() + + coreAPIs := make([]coreiface.CoreAPI, count) + cleans := make([]func(), count) + + for i := 0; i < count; i++ { + core, err := ipfsCore.NewNode(ctx, &ipfsCore.BuildCfg{ + Online: true, + Repo: testingRepoB(ctx, b), + ExtraOpts: map[string]bool{ + "pubsub": true, + }, + }) + require.NoError(b, err) + + coreAPIs[i] = testingCoreAPIB(b, core) + cleans[i] = func() { + core.Close() + } + } + + return coreAPIs, func() { + for i := 0; i < count; i++ { + cleans[i]() + } + } +} + +func testingIPFSNodeWithoutPubsubB(ctx context.Context, b *testing.B, m mocknet.Mocknet) (*ipfsCore.IpfsNode, func()) { + b.Helper() + + core, err := ipfsCore.NewNode(ctx, &ipfsCore.BuildCfg{ + Online: true, + Repo: testingRepoB(ctx, b), + Host: mock.MockHostOption(m), + ExtraOpts: map[string]bool{ + "pubsub": false, + }, + }) + require.NoError(b, err) + + cleanup := func() { core.Close() } + return core, cleanup +} + +func testingIPFSNodeB(ctx context.Context, b *testing.B, m mocknet.Mocknet) (*ipfsCore.IpfsNode, func()) { + b.Helper() + + core, err := ipfsCore.NewNode(ctx, &ipfsCore.BuildCfg{ + Online: true, + Repo: testingRepoB(ctx, b), + Host: mock.MockHostOption(m), + ExtraOpts: map[string]bool{ + "pubsub": true, + }, + }) + require.NoError(b, err) + + cleanup := func() { core.Close() } + return core, cleanup +} + +func testingNonMockedIPFSNodeB(ctx context.Context, b *testing.B) (*ipfsCore.IpfsNode, func()) { + b.Helper() + + core, err := ipfsCore.NewNode(ctx, &ipfsCore.BuildCfg{ + Online: true, + Repo: testingRepoB(ctx, b), + ExtraOpts: map[string]bool{ + "pubsub": true, + }, + }) + require.NoError(b, err) + + cleanup := func() { core.Close() } + return core, cleanup +} + +func testingCoreAPIB(b *testing.B, core *ipfsCore.IpfsNode) coreiface.CoreAPI { + b.Helper() + + api, err := coreapi.NewCoreAPI(core) + require.NoError(b, err) + return api +} + +func testingMockNetB(b *testing.B) mocknet.Mocknet { + mn := mocknet.New() + b.Cleanup(func() { mn.Close() }) + return mn +} + +func testingTempDirB(b *testing.B, name string) (string, func()) { + b.Helper() + + path, err := ioutil.TempDir("", name) + require.NoError(b, err) + + cleanup := func() { os.RemoveAll(path) } + return path, cleanup +}