Skip to content

Commit 67c6be6

Browse files
Kirill-ChurkinLeonidVas
authored andcommitted
bench: add benchmark test functionality for Tarantool
User setup Tarantool single node or cluster and try to understand "How many specific traffic Tarantool can handle on this hardware" The same official things are for redis, postgresql and aerospike. Cartridge bench module makes some load for Tarantool. user@cartridge-cli % ./cartridge bench Tarantool 2.8.2 (Binary) f4897ffe-98dd-40fc-a6f2-21ca8bb52fe7 Parameters: URL: 127.0.0.1:3301 user: guest connections: 10 simultaneous requests: 10 duration: 10 seconds key size: 10 bytes data size: 20 bytes Data schema | key | value ------------------------------------------ | random(10) | random(20) Benchmark start ... Benchmark stop Results: Success operations: 1169481 Failed operations: 0 Request count: 1170485 Time (seconds): 10.000551801 Requests per second: 117042 Part of tarantool#645
1 parent 479a592 commit 67c6be6

File tree

11 files changed

+356
-21
lines changed

11 files changed

+356
-21
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
3939

4040
### Added
4141

42+
- Tarantool benchmark tool (early alpha, API can be changed in the near future).
4243
- Ability to reverse search in ``cartridge enter`` and ``cartridge connect`` commands.
4344
- Added support for functionality from Golang 1.17.
4445

cli/bench/bench.go

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package bench
2+
3+
import (
4+
bctx "context"
5+
"fmt"
6+
"math/rand"
7+
"sync"
8+
"time"
9+
10+
"github.com/FZambia/tarantool"
11+
"github.com/tarantool/cartridge-cli/cli/common"
12+
"github.com/tarantool/cartridge-cli/cli/context"
13+
)
14+
15+
// printResults outputs benchmark foramatted results.
16+
func printResults(results Results) {
17+
fmt.Printf("\nResults:\n")
18+
fmt.Printf("\tSuccess operations: %d\n", results.successResultCount)
19+
fmt.Printf("\tFailed operations: %d\n", results.failedResultCount)
20+
fmt.Printf("\tRequest count: %d\n", results.handledRequestsCount)
21+
fmt.Printf("\tTime (seconds): %f\n", results.duration)
22+
fmt.Printf("\tRequests per second: %d\n\n", results.requestsPerSecond)
23+
}
24+
25+
// spacePreset prepares space for a benchmark.
26+
func spacePreset(ctx context.BenchCtx, tarantoolConnection *tarantool.Connection) error {
27+
dropBenchmarkSpace(tarantoolConnection)
28+
return createBenchmarkSpace(tarantoolConnection)
29+
}
30+
31+
// incrementRequest increases the counter of successful/failed requests depending on the presence of an error.
32+
func incrementRequest(err error, results *Results) {
33+
if err == nil {
34+
results.successResultCount++
35+
} else {
36+
results.failedResultCount++
37+
}
38+
results.handledRequestsCount++
39+
}
40+
41+
// requestsLoop continuously executes the insert query until the benchmark time runs out.
42+
func requestsLoop(ctx context.BenchCtx, tarantoolConnection *tarantool.Connection, results *Results, backgroundCtx bctx.Context) {
43+
for {
44+
select {
45+
case <-backgroundCtx.Done():
46+
return
47+
default:
48+
_, err := tarantoolConnection.Exec(
49+
tarantool.Insert(
50+
benchSpaceName,
51+
[]interface{}{common.RandomString(ctx.KeySize), common.RandomString(ctx.DataSize)}))
52+
incrementRequest(err, results)
53+
}
54+
}
55+
}
56+
57+
// connectionLoop runs "ctx.SimultaneousRequests" requests execution threads through the same connection.
58+
func connectionLoop(ctx context.BenchCtx, tarantoolConnection *tarantool.Connection, results *Results, backgroundCtx bctx.Context) {
59+
var connectionWait sync.WaitGroup
60+
for i := 0; i < ctx.SimultaneousRequests; i++ {
61+
connectionWait.Add(1)
62+
go func() {
63+
defer connectionWait.Done()
64+
requestsLoop(ctx, tarantoolConnection, results, backgroundCtx)
65+
}()
66+
}
67+
68+
connectionWait.Wait()
69+
}
70+
71+
// Main benchmark function.
72+
func Run(ctx context.BenchCtx) error {
73+
rand.Seed(time.Now().UnixNano())
74+
75+
// Connect to tarantool and preset space for benchmark.
76+
tarantoolConnection, err := tarantool.Connect(ctx.URL, tarantool.Opts{
77+
User: ctx.User,
78+
Password: ctx.Password,
79+
})
80+
if err != nil {
81+
return fmt.Errorf(
82+
"Couldn't connect to Tarantool %s.",
83+
ctx.URL)
84+
}
85+
defer tarantoolConnection.Close()
86+
87+
printConfig(ctx, tarantoolConnection)
88+
89+
if err := spacePreset(ctx, tarantoolConnection); err != nil {
90+
return err
91+
}
92+
93+
/// Сreate a "connectionPool" before starting the benchmark to exclude the connection establishment time from measurements.
94+
connectionPool := make([]*tarantool.Connection, ctx.Connections)
95+
for i := 0; i < ctx.Connections; i++ {
96+
connectionPool[i], err = tarantool.Connect(ctx.URL, tarantool.Opts{
97+
User: ctx.User,
98+
Password: ctx.Password,
99+
})
100+
if err != nil {
101+
return err
102+
}
103+
defer connectionPool[i].Close()
104+
}
105+
106+
fmt.Println("Benchmark start")
107+
fmt.Println("...")
108+
109+
// The "context" will be used to stop all "connectionLoop" when the time is out.
110+
backgroundCtx, cancel := bctx.WithCancel(bctx.Background())
111+
var waitGroup sync.WaitGroup
112+
results := Results{}
113+
114+
startTime := time.Now()
115+
timer := time.NewTimer(time.Duration(ctx.Duration * int(time.Second)))
116+
117+
// Start detached connections.
118+
for i := 0; i < ctx.Connections; i++ {
119+
waitGroup.Add(1)
120+
go func(connection *tarantool.Connection) {
121+
defer waitGroup.Done()
122+
connectionLoop(ctx, connection, &results, backgroundCtx)
123+
}(connectionPool[i])
124+
}
125+
// Sends "signal" to all "connectionLoop" and waits for them to complete.
126+
<-timer.C
127+
cancel()
128+
waitGroup.Wait()
129+
130+
results.duration = time.Since(startTime).Seconds()
131+
results.requestsPerSecond = int(float64(results.handledRequestsCount) / results.duration)
132+
133+
dropBenchmarkSpace(tarantoolConnection)
134+
fmt.Println("Benchmark stop")
135+
136+
printResults(results)
137+
return nil
138+
}

cli/bench/config.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package bench
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"text/tabwriter"
7+
8+
"github.com/FZambia/tarantool"
9+
"github.com/tarantool/cartridge-cli/cli/context"
10+
)
11+
12+
var (
13+
benchSpaceName = "__benchmark_space__"
14+
benchSpacePrimaryIndexName = "__bench_primary_key__"
15+
)
16+
17+
// printConfig output formatted config parameters.
18+
func printConfig(ctx context.BenchCtx, tarantoolConnection *tarantool.Connection) {
19+
fmt.Printf("%s\n", tarantoolConnection.Greeting().Version)
20+
fmt.Printf("Parameters:\n")
21+
fmt.Printf("\tURL: %s\n", ctx.URL)
22+
fmt.Printf("\tuser: %s\n", ctx.User)
23+
fmt.Printf("\tconnections: %d\n", ctx.Connections)
24+
fmt.Printf("\tsimultaneous requests: %d\n", ctx.SimultaneousRequests)
25+
fmt.Printf("\tduration: %d seconds\n", ctx.Duration)
26+
fmt.Printf("\tkey size: %d bytes\n", ctx.KeySize)
27+
fmt.Printf("\tdata size: %d bytes\n", ctx.DataSize)
28+
fmt.Printf("Data schema\n")
29+
w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0)
30+
fmt.Fprintf(w, "|\tkey\t|\tvalue\n")
31+
fmt.Fprintf(w, "|\t------------------------------\t|\t------------------------------\n")
32+
fmt.Fprintf(w, "|\trandom(%d)\t|\trandom(%d)\n", ctx.KeySize, ctx.DataSize)
33+
w.Flush()
34+
}

cli/bench/space.go

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package bench
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
7+
"github.com/FZambia/tarantool"
8+
)
9+
10+
// createBenchmarkSpace creates benchmark space with formatting and primary index.
11+
func createBenchmarkSpace(tarantoolConnection *tarantool.Connection) error {
12+
// Creating space.
13+
createCommand := "return box.schema.space.create(...).name"
14+
_, err := tarantoolConnection.Exec(tarantool.Eval(createCommand, []interface{}{benchSpaceName, map[string]bool{"if_not_exists": true}}))
15+
if err != nil {
16+
return err
17+
}
18+
19+
// Formatting space.
20+
formatCommand := fmt.Sprintf("box.space.%s:format", benchSpaceName)
21+
_, err = tarantoolConnection.Exec(tarantool.Call(formatCommand, [][]map[string]string{
22+
{
23+
{"name": "key", "type": "string"},
24+
{"name": "value", "type": "string"},
25+
},
26+
}))
27+
if err != nil {
28+
return err
29+
}
30+
31+
// Creating primary index.
32+
createIndexCommand := fmt.Sprintf("box.space.%s:create_index", benchSpaceName)
33+
_, err = tarantoolConnection.Exec(tarantool.Call(createIndexCommand, []interface{}{
34+
benchSpacePrimaryIndexName,
35+
map[string]interface{}{
36+
"parts": []string{"key"},
37+
"if_not_exists": true,
38+
},
39+
}))
40+
return err
41+
}
42+
43+
// dropBenchmarkSpace deletes benchmark space.
44+
func dropBenchmarkSpace(tarantoolConnection *tarantool.Connection) error {
45+
checkCommand := fmt.Sprintf("return box.space.%s.index[0].name", benchSpaceName)
46+
indexName, err := tarantoolConnection.Exec(tarantool.Eval(checkCommand, []interface{}{}))
47+
if err != nil {
48+
return err
49+
}
50+
if reflect.ValueOf(indexName.Data).Index(0).Elem().String() == benchSpacePrimaryIndexName {
51+
dropCommand := fmt.Sprintf("box.space.%s:drop", benchSpaceName)
52+
_, err := tarantoolConnection.Exec(tarantool.Call(dropCommand, []interface{}{}))
53+
return err
54+
}
55+
return nil
56+
}

cli/bench/types.go

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package bench
2+
3+
// Results describes set of benchmark results.
4+
type Results struct {
5+
handledRequestsCount int // Count of all executed requests.
6+
successResultCount int // Count of successful request in all connections.
7+
failedResultCount int // Count of failed request in all connections.
8+
duration float64 // Benchmark duration.
9+
requestsPerSecond int // Cumber of requests per second - the main measured value.
10+
}

cli/commands/bench.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package commands
2+
3+
import (
4+
"github.com/apex/log"
5+
"github.com/spf13/cobra"
6+
"github.com/tarantool/cartridge-cli/cli/bench"
7+
)
8+
9+
func init() {
10+
var benchCmd = &cobra.Command{
11+
Use: "bench",
12+
Short: "Util for running benchmarks for Tarantool",
13+
Long: "Benchmark utility that simulates running commands done by N clients at the same time sending M simultaneous queries",
14+
Run: func(cmd *cobra.Command, args []string) {
15+
if err := bench.Run(ctx.Bench); err != nil {
16+
log.Fatalf(err.Error())
17+
}
18+
},
19+
}
20+
rootCmd.AddCommand(benchCmd)
21+
22+
configureFlags(benchCmd)
23+
24+
benchCmd.Flags().StringVar(&ctx.Bench.URL, "url", "127.0.0.1:3301", "Tarantool address")
25+
benchCmd.Flags().StringVar(&ctx.Bench.User, "user", "guest", "Tarantool user for connection")
26+
benchCmd.Flags().StringVar(&ctx.Bench.Password, "password", "", "Tarantool password for connection")
27+
28+
benchCmd.Flags().IntVar(&ctx.Bench.Connections, "connections", 10, "Number of concurrent connections")
29+
benchCmd.Flags().IntVar(&ctx.Bench.SimultaneousRequests, "requests", 10, "Number of simultaneous requests per connection")
30+
benchCmd.Flags().IntVar(&ctx.Bench.Duration, "duration", 10, "Duration of benchmark test (seconds)")
31+
benchCmd.Flags().IntVar(&ctx.Bench.KeySize, "keysize", 10, "Size of key part of benchmark data (bytes)")
32+
benchCmd.Flags().IntVar(&ctx.Bench.DataSize, "datasize", 20, "Size of value part of benchmark data (bytes)")
33+
}

cli/context/context.go

+12
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type Ctx struct {
2121
Replicasets ReplicasetsCtx
2222
Connect ConnectCtx
2323
Failover FailoverCtx
24+
Bench BenchCtx
2425
}
2526

2627
type ProjectCtx struct {
@@ -173,3 +174,14 @@ type FailoverCtx struct {
173174
ParamsJSON string
174175
ProviderParamsJSON string
175176
}
177+
178+
type BenchCtx struct {
179+
URL string // URL - the URL of the tarantool used for testing
180+
User string // User - username to connect to the tarantool.
181+
Password string // Password to connect to the tarantool.
182+
Connections int // Connections describes the number of connection to be used in the test.
183+
SimultaneousRequests int // SimultaneousRequests describes the number of parallel requests from one connection.
184+
Duration int // Duration describes test duration in seconds.
185+
KeySize int // DataSize describes the size of key part of benchmark data (bytes).
186+
DataSize int // DataSize describes the size of value part of benchmark data (bytes).
187+
}

go.mod

+24-21
Original file line numberDiff line numberDiff line change
@@ -4,62 +4,65 @@ go 1.17
44

55
require (
66
github.com/FZambia/tarantool v0.2.1
7-
github.com/Microsoft/go-winio v0.4.17 // indirect
8-
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
97
github.com/adam-hanna/arrayOperations v0.2.6
108
github.com/alecthomas/participle/v2 v2.0.0-alpha4
119
github.com/apex/log v1.4.0
1210
github.com/avast/retry-go v3.0.0+incompatible
1311
github.com/briandowns/spinner v1.11.1
1412
github.com/c-bata/go-prompt v0.2.5
15-
github.com/containerd/containerd v1.5.8 // indirect
1613
github.com/dave/jennifer v1.4.1
14+
github.com/docker/docker v20.10.11+incompatible
15+
github.com/fatih/color v1.7.0
16+
github.com/fatih/structs v1.1.0
17+
github.com/hashicorp/go-version v1.2.0
18+
github.com/hpcloud/tail v1.0.0
19+
github.com/magefile/mage v1.11.0
20+
github.com/mattn/go-isatty v0.0.12
21+
github.com/mitchellh/mapstructure v1.4.1
22+
github.com/otiai10/copy v1.2.0
23+
github.com/pmezard/go-difflib v1.0.0
24+
github.com/shirou/gopsutil v3.21.2+incompatible
25+
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749
26+
github.com/spf13/cobra v1.0.1-0.20200815144417-81e0311edd0b
27+
github.com/spf13/pflag v1.0.5
28+
github.com/stretchr/testify v1.6.1
29+
github.com/vmihailenco/msgpack/v5 v5.1.0
30+
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb
31+
gopkg.in/yaml.v2 v2.4.0
32+
)
33+
34+
require (
35+
github.com/Microsoft/go-winio v0.4.17 // indirect
36+
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
37+
github.com/containerd/containerd v1.5.8 // indirect
1738
github.com/davecgh/go-spew v1.1.1 // indirect
1839
github.com/docker/distribution v2.7.1+incompatible // indirect
19-
github.com/docker/docker v20.10.7+incompatible
2040
github.com/docker/go-connections v0.4.0 // indirect
2141
github.com/docker/go-units v0.4.0 // indirect
22-
github.com/fatih/color v1.7.0
23-
github.com/fatih/structs v1.1.0
2442
github.com/go-ole/go-ole v1.2.5 // indirect
2543
github.com/gogo/protobuf v1.3.2 // indirect
2644
github.com/golang/protobuf v1.5.0 // indirect
27-
github.com/hashicorp/go-version v1.2.0
28-
github.com/hpcloud/tail v1.0.0
2945
github.com/inconshreveable/mousetrap v1.0.0 // indirect
30-
github.com/magefile/mage v1.11.0
3146
github.com/mattn/go-colorable v0.1.7 // indirect
32-
github.com/mattn/go-isatty v0.0.12
3347
github.com/mattn/go-runewidth v0.0.9 // indirect
3448
github.com/mattn/go-tty v0.0.3 // indirect
35-
github.com/mitchellh/mapstructure v1.4.1
3649
github.com/morikuni/aec v1.0.0 // indirect
3750
github.com/opencontainers/go-digest v1.0.0 // indirect
3851
github.com/opencontainers/image-spec v1.0.2 // indirect
39-
github.com/otiai10/copy v1.2.0
4052
github.com/pkg/errors v0.9.1 // indirect
4153
github.com/pkg/term v1.2.0-beta.2 // indirect
42-
github.com/pmezard/go-difflib v1.0.0
43-
github.com/shirou/gopsutil v3.21.2+incompatible
44-
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749
4554
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect
4655
github.com/sirupsen/logrus v1.8.1 // indirect
47-
github.com/spf13/cobra v1.0.1-0.20200815144417-81e0311edd0b
48-
github.com/spf13/pflag v1.0.5
49-
github.com/stretchr/testify v1.6.1
5056
github.com/tklauser/go-sysconf v0.3.4 // indirect
5157
github.com/tklauser/numcpus v0.2.1 // indirect
52-
github.com/vmihailenco/msgpack/v5 v5.1.0
5358
github.com/vmihailenco/tagparser v0.1.2 // indirect
54-
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb
5559
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
5660
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
5761
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a // indirect
5862
google.golang.org/grpc v1.39.0 // indirect
5963
google.golang.org/protobuf v1.27.1 // indirect
6064
gopkg.in/fsnotify.v1 v1.4.7 // indirect
6165
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
62-
gopkg.in/yaml.v2 v2.4.0
6366
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
6467
)
6568

0 commit comments

Comments
 (0)