Skip to content

Commit

Permalink
bench: add benchmark test functionality for Tarantool
Browse files Browse the repository at this point in the history
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 #645
  • Loading branch information
Kirill-Churkin authored and LeonidVas committed Dec 7, 2021
1 parent 479a592 commit 67c6be6
Show file tree
Hide file tree
Showing 11 changed files with 356 additions and 21 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### Added

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

Expand Down
138 changes: 138 additions & 0 deletions cli/bench/bench.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package bench

import (
bctx "context"
"fmt"
"math/rand"
"sync"
"time"

"github.com/FZambia/tarantool"
"github.com/tarantool/cartridge-cli/cli/common"
"github.com/tarantool/cartridge-cli/cli/context"
)

// printResults outputs benchmark foramatted results.
func printResults(results Results) {
fmt.Printf("\nResults:\n")
fmt.Printf("\tSuccess operations: %d\n", results.successResultCount)
fmt.Printf("\tFailed operations: %d\n", results.failedResultCount)
fmt.Printf("\tRequest count: %d\n", results.handledRequestsCount)
fmt.Printf("\tTime (seconds): %f\n", results.duration)
fmt.Printf("\tRequests per second: %d\n\n", results.requestsPerSecond)
}

// spacePreset prepares space for a benchmark.
func spacePreset(ctx context.BenchCtx, tarantoolConnection *tarantool.Connection) error {
dropBenchmarkSpace(tarantoolConnection)
return createBenchmarkSpace(tarantoolConnection)
}

// incrementRequest increases the counter of successful/failed requests depending on the presence of an error.
func incrementRequest(err error, results *Results) {
if err == nil {
results.successResultCount++
} else {
results.failedResultCount++
}
results.handledRequestsCount++
}

// requestsLoop continuously executes the insert query until the benchmark time runs out.
func requestsLoop(ctx context.BenchCtx, tarantoolConnection *tarantool.Connection, results *Results, backgroundCtx bctx.Context) {
for {
select {
case <-backgroundCtx.Done():
return
default:
_, err := tarantoolConnection.Exec(
tarantool.Insert(
benchSpaceName,
[]interface{}{common.RandomString(ctx.KeySize), common.RandomString(ctx.DataSize)}))
incrementRequest(err, results)
}
}
}

// connectionLoop runs "ctx.SimultaneousRequests" requests execution threads through the same connection.
func connectionLoop(ctx context.BenchCtx, tarantoolConnection *tarantool.Connection, results *Results, backgroundCtx bctx.Context) {
var connectionWait sync.WaitGroup
for i := 0; i < ctx.SimultaneousRequests; i++ {
connectionWait.Add(1)
go func() {
defer connectionWait.Done()
requestsLoop(ctx, tarantoolConnection, results, backgroundCtx)
}()
}

connectionWait.Wait()
}

// Main benchmark function.
func Run(ctx context.BenchCtx) error {
rand.Seed(time.Now().UnixNano())

// Connect to tarantool and preset space for benchmark.
tarantoolConnection, err := tarantool.Connect(ctx.URL, tarantool.Opts{
User: ctx.User,
Password: ctx.Password,
})
if err != nil {
return fmt.Errorf(
"Couldn't connect to Tarantool %s.",
ctx.URL)
}
defer tarantoolConnection.Close()

printConfig(ctx, tarantoolConnection)

if err := spacePreset(ctx, tarantoolConnection); err != nil {
return err
}

/// Сreate a "connectionPool" before starting the benchmark to exclude the connection establishment time from measurements.
connectionPool := make([]*tarantool.Connection, ctx.Connections)
for i := 0; i < ctx.Connections; i++ {
connectionPool[i], err = tarantool.Connect(ctx.URL, tarantool.Opts{
User: ctx.User,
Password: ctx.Password,
})
if err != nil {
return err
}
defer connectionPool[i].Close()
}

fmt.Println("Benchmark start")
fmt.Println("...")

// The "context" will be used to stop all "connectionLoop" when the time is out.
backgroundCtx, cancel := bctx.WithCancel(bctx.Background())
var waitGroup sync.WaitGroup
results := Results{}

startTime := time.Now()
timer := time.NewTimer(time.Duration(ctx.Duration * int(time.Second)))

// Start detached connections.
for i := 0; i < ctx.Connections; i++ {
waitGroup.Add(1)
go func(connection *tarantool.Connection) {
defer waitGroup.Done()
connectionLoop(ctx, connection, &results, backgroundCtx)
}(connectionPool[i])
}
// Sends "signal" to all "connectionLoop" and waits for them to complete.
<-timer.C
cancel()
waitGroup.Wait()

results.duration = time.Since(startTime).Seconds()
results.requestsPerSecond = int(float64(results.handledRequestsCount) / results.duration)

dropBenchmarkSpace(tarantoolConnection)
fmt.Println("Benchmark stop")

printResults(results)
return nil
}
34 changes: 34 additions & 0 deletions cli/bench/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package bench

import (
"fmt"
"os"
"text/tabwriter"

"github.com/FZambia/tarantool"
"github.com/tarantool/cartridge-cli/cli/context"
)

var (
benchSpaceName = "__benchmark_space__"
benchSpacePrimaryIndexName = "__bench_primary_key__"
)

// printConfig output formatted config parameters.
func printConfig(ctx context.BenchCtx, tarantoolConnection *tarantool.Connection) {
fmt.Printf("%s\n", tarantoolConnection.Greeting().Version)
fmt.Printf("Parameters:\n")
fmt.Printf("\tURL: %s\n", ctx.URL)
fmt.Printf("\tuser: %s\n", ctx.User)
fmt.Printf("\tconnections: %d\n", ctx.Connections)
fmt.Printf("\tsimultaneous requests: %d\n", ctx.SimultaneousRequests)
fmt.Printf("\tduration: %d seconds\n", ctx.Duration)
fmt.Printf("\tkey size: %d bytes\n", ctx.KeySize)
fmt.Printf("\tdata size: %d bytes\n", ctx.DataSize)
fmt.Printf("Data schema\n")
w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0)
fmt.Fprintf(w, "|\tkey\t|\tvalue\n")
fmt.Fprintf(w, "|\t------------------------------\t|\t------------------------------\n")
fmt.Fprintf(w, "|\trandom(%d)\t|\trandom(%d)\n", ctx.KeySize, ctx.DataSize)
w.Flush()
}
56 changes: 56 additions & 0 deletions cli/bench/space.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package bench

import (
"fmt"
"reflect"

"github.com/FZambia/tarantool"
)

// createBenchmarkSpace creates benchmark space with formatting and primary index.
func createBenchmarkSpace(tarantoolConnection *tarantool.Connection) error {
// Creating space.
createCommand := "return box.schema.space.create(...).name"
_, err := tarantoolConnection.Exec(tarantool.Eval(createCommand, []interface{}{benchSpaceName, map[string]bool{"if_not_exists": true}}))
if err != nil {
return err
}

// Formatting space.
formatCommand := fmt.Sprintf("box.space.%s:format", benchSpaceName)
_, err = tarantoolConnection.Exec(tarantool.Call(formatCommand, [][]map[string]string{
{
{"name": "key", "type": "string"},
{"name": "value", "type": "string"},
},
}))
if err != nil {
return err
}

// Creating primary index.
createIndexCommand := fmt.Sprintf("box.space.%s:create_index", benchSpaceName)
_, err = tarantoolConnection.Exec(tarantool.Call(createIndexCommand, []interface{}{
benchSpacePrimaryIndexName,
map[string]interface{}{
"parts": []string{"key"},
"if_not_exists": true,
},
}))
return err
}

// dropBenchmarkSpace deletes benchmark space.
func dropBenchmarkSpace(tarantoolConnection *tarantool.Connection) error {
checkCommand := fmt.Sprintf("return box.space.%s.index[0].name", benchSpaceName)
indexName, err := tarantoolConnection.Exec(tarantool.Eval(checkCommand, []interface{}{}))
if err != nil {
return err
}
if reflect.ValueOf(indexName.Data).Index(0).Elem().String() == benchSpacePrimaryIndexName {
dropCommand := fmt.Sprintf("box.space.%s:drop", benchSpaceName)
_, err := tarantoolConnection.Exec(tarantool.Call(dropCommand, []interface{}{}))
return err
}
return nil
}
10 changes: 10 additions & 0 deletions cli/bench/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package bench

// Results describes set of benchmark results.
type Results struct {
handledRequestsCount int // Count of all executed requests.
successResultCount int // Count of successful request in all connections.
failedResultCount int // Count of failed request in all connections.
duration float64 // Benchmark duration.
requestsPerSecond int // Cumber of requests per second - the main measured value.
}
33 changes: 33 additions & 0 deletions cli/commands/bench.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package commands

import (
"github.com/apex/log"
"github.com/spf13/cobra"
"github.com/tarantool/cartridge-cli/cli/bench"
)

func init() {
var benchCmd = &cobra.Command{
Use: "bench",
Short: "Util for running benchmarks for Tarantool",
Long: "Benchmark utility that simulates running commands done by N clients at the same time sending M simultaneous queries",
Run: func(cmd *cobra.Command, args []string) {
if err := bench.Run(ctx.Bench); err != nil {
log.Fatalf(err.Error())
}
},
}
rootCmd.AddCommand(benchCmd)

configureFlags(benchCmd)

benchCmd.Flags().StringVar(&ctx.Bench.URL, "url", "127.0.0.1:3301", "Tarantool address")
benchCmd.Flags().StringVar(&ctx.Bench.User, "user", "guest", "Tarantool user for connection")
benchCmd.Flags().StringVar(&ctx.Bench.Password, "password", "", "Tarantool password for connection")

benchCmd.Flags().IntVar(&ctx.Bench.Connections, "connections", 10, "Number of concurrent connections")
benchCmd.Flags().IntVar(&ctx.Bench.SimultaneousRequests, "requests", 10, "Number of simultaneous requests per connection")
benchCmd.Flags().IntVar(&ctx.Bench.Duration, "duration", 10, "Duration of benchmark test (seconds)")
benchCmd.Flags().IntVar(&ctx.Bench.KeySize, "keysize", 10, "Size of key part of benchmark data (bytes)")
benchCmd.Flags().IntVar(&ctx.Bench.DataSize, "datasize", 20, "Size of value part of benchmark data (bytes)")
}
12 changes: 12 additions & 0 deletions cli/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type Ctx struct {
Replicasets ReplicasetsCtx
Connect ConnectCtx
Failover FailoverCtx
Bench BenchCtx
}

type ProjectCtx struct {
Expand Down Expand Up @@ -173,3 +174,14 @@ type FailoverCtx struct {
ParamsJSON string
ProviderParamsJSON string
}

type BenchCtx struct {
URL string // URL - the URL of the tarantool used for testing
User string // User - username to connect to the tarantool.
Password string // Password to connect to the tarantool.
Connections int // Connections describes the number of connection to be used in the test.
SimultaneousRequests int // SimultaneousRequests describes the number of parallel requests from one connection.
Duration int // Duration describes test duration in seconds.
KeySize int // DataSize describes the size of key part of benchmark data (bytes).
DataSize int // DataSize describes the size of value part of benchmark data (bytes).
}
45 changes: 24 additions & 21 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,62 +4,65 @@ go 1.17

require (
github.com/FZambia/tarantool v0.2.1
github.com/Microsoft/go-winio v0.4.17 // indirect
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
github.com/adam-hanna/arrayOperations v0.2.6
github.com/alecthomas/participle/v2 v2.0.0-alpha4
github.com/apex/log v1.4.0
github.com/avast/retry-go v3.0.0+incompatible
github.com/briandowns/spinner v1.11.1
github.com/c-bata/go-prompt v0.2.5
github.com/containerd/containerd v1.5.8 // indirect
github.com/dave/jennifer v1.4.1
github.com/docker/docker v20.10.11+incompatible
github.com/fatih/color v1.7.0
github.com/fatih/structs v1.1.0
github.com/hashicorp/go-version v1.2.0
github.com/hpcloud/tail v1.0.0
github.com/magefile/mage v1.11.0
github.com/mattn/go-isatty v0.0.12
github.com/mitchellh/mapstructure v1.4.1
github.com/otiai10/copy v1.2.0
github.com/pmezard/go-difflib v1.0.0
github.com/shirou/gopsutil v3.21.2+incompatible
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749
github.com/spf13/cobra v1.0.1-0.20200815144417-81e0311edd0b
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.6.1
github.com/vmihailenco/msgpack/v5 v5.1.0
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb
gopkg.in/yaml.v2 v2.4.0
)

require (
github.com/Microsoft/go-winio v0.4.17 // indirect
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
github.com/containerd/containerd v1.5.8 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v20.10.7+incompatible
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/fatih/color v1.7.0
github.com/fatih/structs v1.1.0
github.com/go-ole/go-ole v1.2.5 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.0 // indirect
github.com/hashicorp/go-version v1.2.0
github.com/hpcloud/tail v1.0.0
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/magefile/mage v1.11.0
github.com/mattn/go-colorable v0.1.7 // indirect
github.com/mattn/go-isatty v0.0.12
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/mattn/go-tty v0.0.3 // indirect
github.com/mitchellh/mapstructure v1.4.1
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/otiai10/copy v1.2.0
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/term v1.2.0-beta.2 // indirect
github.com/pmezard/go-difflib v1.0.0
github.com/shirou/gopsutil v3.21.2+incompatible
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/spf13/cobra v1.0.1-0.20200815144417-81e0311edd0b
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.6.1
github.com/tklauser/go-sysconf v0.3.4 // indirect
github.com/tklauser/numcpus v0.2.1 // indirect
github.com/vmihailenco/msgpack/v5 v5.1.0
github.com/vmihailenco/tagparser v0.1.2 // indirect
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a // indirect
google.golang.org/grpc v1.39.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/fsnotify.v1 v1.4.7 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)

Expand Down
Loading

0 comments on commit 67c6be6

Please sign in to comment.