vendored redbench

This commit is contained in:
Josh Baker 2017-03-31 08:31:33 -07:00
parent c8b44b47cc
commit 6008a78281
4 changed files with 440 additions and 300 deletions

View File

@ -1,12 +1,7 @@
package main
import (
"bufio"
"errors"
"fmt"
"io"
"io/ioutil"
"math"
"math/rand"
"net"
"os"
@ -15,6 +10,7 @@ import (
"sync/atomic"
"time"
"github.com/tidwall/redbench"
"github.com/tidwall/tile38/core"
)
@ -134,19 +130,22 @@ func parseArgs() bool {
return true
}
func fillOpts() *Options {
return &Options{
JSON: json,
CSV: csv,
Clients: clients,
Pipeline: pipeline,
Quiet: quiet,
Requests: requests,
}
func fillOpts() *redbench.Options {
opts := *redbench.DefaultOptions
opts.CSV = csv
opts.Clients = clients
opts.Pipeline = pipeline
opts.Quiet = quiet
opts.Requests = requests
opts.Stderr = os.Stderr
opts.Stdout = os.Stdout
return &opts
}
func randPoint() (lat, lon float64) {
return rand.Float64()*180 - 90, rand.Float64()*360 - 180
}
func randRect() (minlat, minlon, maxlat, maxlon float64) {
for {
minlat, minlon = randPoint()
@ -156,6 +155,14 @@ func randRect() (minlat, minlon, maxlat, maxlon float64) {
}
}
}
func prepFn(conn net.Conn) bool {
if json {
conn.Write([]byte("output json\r\n"))
resp := make([]byte, 100)
conn.Read(resp)
}
return true
}
func main() {
rand.Seed(time.Now().UnixNano())
if !parseArgs() {
@ -165,35 +172,35 @@ func main() {
for _, test := range strings.Split(tests, ",") {
switch strings.ToUpper(strings.TrimSpace(test)) {
case "PING":
RedisBench("PING", addr, fillOpts(),
func(buf []byte, _ ServerType) []byte {
return AppendCommand(buf, "PING")
redbench.Bench("PING", addr, fillOpts(), prepFn,
func(buf []byte) []byte {
return redbench.AppendCommand(buf, "PING")
},
)
case "SET":
if redis {
RedisBench("SET", addr, fillOpts(),
func(buf []byte, _ ServerType) []byte {
return AppendCommand(buf, "SET", "key:__rand_int__", "xxx")
redbench.Bench("SET", addr, fillOpts(), prepFn,
func(buf []byte) []byte {
return redbench.AppendCommand(buf, "SET", "key:__rand_int__", "xxx")
},
)
} else {
var i int64
RedisBench("SET (point)", addr, fillOpts(),
func(buf []byte, _ ServerType) []byte {
redbench.Bench("SET (point)", addr, fillOpts(), prepFn,
func(buf []byte) []byte {
i := atomic.AddInt64(&i, 1)
lat, lon := randPoint()
return AppendCommand(buf, "SET", "key:bench", "id:"+strconv.FormatInt(i, 10), "POINT",
return redbench.AppendCommand(buf, "SET", "key:bench", "id:"+strconv.FormatInt(i, 10), "POINT",
strconv.FormatFloat(lat, 'f', 5, 64),
strconv.FormatFloat(lon, 'f', 5, 64),
)
},
)
RedisBench("SET (rect)", addr, fillOpts(),
func(buf []byte, _ ServerType) []byte {
redbench.Bench("SET (rect)", addr, fillOpts(), prepFn,
func(buf []byte) []byte {
i := atomic.AddInt64(&i, 1)
minlat, minlon, maxlat, maxlon := randRect()
return AppendCommand(buf, "SET", "key:bench", "id:"+strconv.FormatInt(i, 10), "BOUNDS",
return redbench.AppendCommand(buf, "SET", "key:bench", "id:"+strconv.FormatInt(i, 10), "BOUNDS",
strconv.FormatFloat(minlat, 'f', 5, 64),
strconv.FormatFloat(minlon, 'f', 5, 64),
strconv.FormatFloat(maxlat, 'f', 5, 64),
@ -201,65 +208,65 @@ func main() {
)
},
)
RedisBench("SET (string)", addr, fillOpts(),
func(buf []byte, _ ServerType) []byte {
redbench.Bench("SET (string)", addr, fillOpts(), prepFn,
func(buf []byte) []byte {
i := atomic.AddInt64(&i, 1)
return AppendCommand(buf, "SET", "key:bench", "id:"+strconv.FormatInt(i, 10), "STRING", "xxx")
return redbench.AppendCommand(buf, "SET", "key:bench", "id:"+strconv.FormatInt(i, 10), "STRING", "xxx")
},
)
}
case "GET":
if redis {
RedisBench("GET", addr, fillOpts(),
func(buf []byte, _ ServerType) []byte {
return AppendCommand(buf, "GET", "key:__rand_int__")
redbench.Bench("GET", addr, fillOpts(), prepFn,
func(buf []byte) []byte {
return redbench.AppendCommand(buf, "GET", "key:__rand_int__")
},
)
} else {
var i int64
RedisBench("GET (point)", addr, fillOpts(),
func(buf []byte, _ ServerType) []byte {
redbench.Bench("GET (point)", addr, fillOpts(), prepFn,
func(buf []byte) []byte {
i := atomic.AddInt64(&i, 1)
return AppendCommand(buf, "GET", "key:bench", "id:"+strconv.FormatInt(i, 10), "POINT")
return redbench.AppendCommand(buf, "GET", "key:bench", "id:"+strconv.FormatInt(i, 10), "POINT")
},
)
RedisBench("GET (rect)", addr, fillOpts(),
func(buf []byte, _ ServerType) []byte {
redbench.Bench("GET (rect)", addr, fillOpts(), prepFn,
func(buf []byte) []byte {
i := atomic.AddInt64(&i, 1)
return AppendCommand(buf, "GET", "key:bench", "id:"+strconv.FormatInt(i, 10), "BOUNDS")
return redbench.AppendCommand(buf, "GET", "key:bench", "id:"+strconv.FormatInt(i, 10), "BOUNDS")
},
)
RedisBench("GET (string)", addr, fillOpts(),
func(buf []byte, _ ServerType) []byte {
redbench.Bench("GET (string)", addr, fillOpts(), prepFn,
func(buf []byte) []byte {
i := atomic.AddInt64(&i, 1)
return AppendCommand(buf, "GET", "key:bench", "id:"+strconv.FormatInt(i, 10), "OBJECT")
return redbench.AppendCommand(buf, "GET", "key:bench", "id:"+strconv.FormatInt(i, 10), "OBJECT")
},
)
}
case "SEARCH":
if !redis {
RedisBench("SEARCH (nearby 1km)", addr, fillOpts(),
func(buf []byte, _ ServerType) []byte {
redbench.Bench("SEARCH (nearby 1km)", addr, fillOpts(), prepFn,
func(buf []byte) []byte {
lat, lon := randPoint()
return AppendCommand(buf, "NEARBY", "key:bench", "COUNT", "POINT",
return redbench.AppendCommand(buf, "NEARBY", "key:bench", "COUNT", "POINT",
strconv.FormatFloat(lat, 'f', 5, 64),
strconv.FormatFloat(lon, 'f', 5, 64),
"1000")
},
)
RedisBench("SEARCH (nearby 10km)", addr, fillOpts(),
func(buf []byte, _ ServerType) []byte {
redbench.Bench("SEARCH (nearby 10km)", addr, fillOpts(), prepFn,
func(buf []byte) []byte {
lat, lon := randPoint()
return AppendCommand(buf, "NEARBY", "key:bench", "COUNT", "POINT",
return redbench.AppendCommand(buf, "NEARBY", "key:bench", "COUNT", "POINT",
strconv.FormatFloat(lat, 'f', 5, 64),
strconv.FormatFloat(lon, 'f', 5, 64),
"10000")
},
)
RedisBench("SEARCH (nearby 100km)", addr, fillOpts(),
func(buf []byte, _ ServerType) []byte {
redbench.Bench("SEARCH (nearby 100km)", addr, fillOpts(), prepFn,
func(buf []byte) []byte {
lat, lon := randPoint()
return AppendCommand(buf, "NEARBY", "key:bench", "COUNT", "POINT",
return redbench.AppendCommand(buf, "NEARBY", "key:bench", "COUNT", "POINT",
strconv.FormatFloat(lat, 'f', 5, 64),
strconv.FormatFloat(lon, 'f', 5, 64),
"100000")
@ -269,253 +276,3 @@ func main() {
}
}
}
func readResp(rd *bufio.Reader, n int) error {
for i := 0; i < n; i++ {
line, err := rd.ReadBytes('\n')
if err != nil {
return err
}
switch line[0] {
default:
return errors.New("invalid server response")
case '+', ':':
case '-':
//panic(string(line))
case '$':
n, err := strconv.ParseInt(string(line[1:len(line)-2]), 10, 64)
if err != nil {
return err
}
if _, err = io.CopyN(ioutil.Discard, rd, n+2); err != nil {
return err
}
case '*':
n, err := strconv.ParseInt(string(line[1:len(line)-2]), 10, 64)
if err != nil {
return err
}
readResp(rd, int(n))
}
}
return nil
}
type ServerType int
const (
Redis = 1
Tile38 = 2
)
type Options struct {
Requests int
Clients int
Pipeline int
Quiet bool
CSV bool
JSON bool
}
func RedisBench(
name string,
addr string,
opts *Options,
fill func(buf []byte, server ServerType) []byte,
) {
var server ServerType
var totalPayload uint64
var count uint64
var duration int64
rpc := opts.Requests / opts.Clients
rpcex := opts.Requests % opts.Clients
var tstop int64
remaining := int64(opts.Clients)
errs := make([]error, opts.Clients)
durs := make([][]time.Duration, opts.Clients)
conns := make([]net.Conn, opts.Clients)
// create all clients
for i := 0; i < opts.Clients; i++ {
crequests := rpc
if i == opts.Clients-1 {
crequests += rpcex
}
durs[i] = make([]time.Duration, crequests)
for j := 0; j < len(durs[i]); j++ {
durs[i][j] = -1
}
conn, err := net.Dial("tcp", addr)
if err != nil {
if i == 0 {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
return
}
errs[i] = err
}
conns[i] = conn
if conn != nil {
if i == 0 {
conn.Write([]byte("info server\r\n"))
resp := make([]byte, 500)
conn.Read(resp)
if strings.Contains(string(resp), "redis_version") {
if strings.Contains(string(resp), "tile38_version") {
server = Tile38
} else {
server = Redis
}
}
}
if opts.JSON {
conn.Write([]byte("output json\r\n"))
resp := make([]byte, 100)
conn.Read(resp)
}
}
}
tstart := time.Now()
for i := 0; i < opts.Clients; i++ {
crequests := rpc
if i == opts.Clients-1 {
crequests += rpcex
}
go func(conn net.Conn, client, crequests int) {
defer func() {
atomic.AddInt64(&remaining, -1)
}()
if conn == nil {
return
}
err := func() error {
var buf []byte
rd := bufio.NewReader(conn)
for i := 0; i < crequests; i += opts.Pipeline {
n := opts.Pipeline
if i+n > crequests {
n = crequests - i
}
buf = buf[:0]
for i := 0; i < n; i++ {
buf = fill(buf, server)
}
atomic.AddUint64(&totalPayload, uint64(len(buf)))
start := time.Now()
_, err := conn.Write(buf)
if err != nil {
return err
}
if err := readResp(rd, n); err != nil {
return err
}
stop := time.Since(start)
for j := 0; j < n; j++ {
durs[client][i+j] = stop / time.Duration(n)
}
atomic.AddInt64(&duration, int64(stop))
atomic.AddUint64(&count, uint64(n))
atomic.StoreInt64(&tstop, int64(time.Since(tstart)))
}
return nil
}()
if err != nil {
errs[client] = err
}
}(conns[i], i, crequests)
}
var die bool
for {
remaining := int(atomic.LoadInt64(&remaining)) // active clients
count := int(atomic.LoadUint64(&count)) // completed requests
real := time.Duration(atomic.LoadInt64(&tstop)) // real duration
totalPayload := int(atomic.LoadUint64(&totalPayload)) // size of all bytes sent
more := remaining > 0
var realrps float64
if real > 0 {
realrps = float64(count) / (float64(real) / float64(time.Second))
}
if !opts.CSV {
fmt.Printf("\r%s: %.2f", name, realrps)
if more {
fmt.Printf("\r")
} else if opts.Quiet {
fmt.Printf(" requests per second\n")
} else {
fmt.Printf("\r====== %s ======\n", name)
fmt.Printf(" %d requests completed in %.2f seconds\n", opts.Requests, float64(real)/float64(time.Second))
fmt.Printf(" %d parallel clients\n", opts.Clients)
fmt.Printf(" %d bytes payload\n", totalPayload/opts.Requests)
fmt.Printf(" keep alive: 1\n")
fmt.Printf("\n")
var limit time.Duration
var lastper float64
for {
limit += time.Millisecond
var hits, count int
for i := 0; i < len(durs); i++ {
for j := 0; j < len(durs[i]); j++ {
dur := durs[i][j]
if dur == -1 {
continue
}
if dur < limit {
hits++
}
count++
}
}
per := float64(hits) / float64(count)
if math.Floor(per*10000) == math.Floor(lastper*10000) {
continue
}
lastper = per
fmt.Printf("%.2f%% <= %d milliseconds\n", per*100, (limit-time.Millisecond)/time.Millisecond)
if per == 1.0 {
break
}
}
fmt.Printf("%.2f requests per second\n\n", realrps)
}
}
if !more {
if opts.CSV {
fmt.Printf("\"%s\",\"%.2f\"\n", name, realrps)
}
for _, err := range errs {
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
die = true
if count == 0 {
break
}
}
}
break
}
time.Sleep(time.Second / 5)
}
// close clients
for i := 0; i < len(conns); i++ {
if conns[i] != nil {
conns[i].Close()
}
}
if die {
os.Exit(1)
}
}
func AppendCommand(buf []byte, args ...string) []byte {
buf = append(buf, '*')
buf = strconv.AppendInt(buf, int64(len(args)), 10)
buf = append(buf, '\r', '\n')
for _, arg := range args {
buf = append(buf, '$')
buf = strconv.AppendInt(buf, int64(len(arg)), 10)
buf = append(buf, '\r', '\n')
buf = append(buf, arg...)
buf = append(buf, '\r', '\n')
}
return buf
}

19
vendor/github.com/tidwall/redbench/LICENSE generated vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2017 Josh Baker
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

93
vendor/github.com/tidwall/redbench/README.md generated vendored Normal file
View File

@ -0,0 +1,93 @@
# Redbench
[![GoDoc](https://img.shields.io/badge/api-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/tidwall/redbench)
Redbench is a Go package that allows for bootstrapping benchmarks
for servers using a custom implementation of the Redis protocol. It provides
the same inputs and outputs as the
[redis-benchmark](https://redis.io/topics/benchmarks) tool. The purpose of
this library is to provide benchmarking for
[Redcon](https://github.com/tidwall/redcon) compatible servers such as
[Tile38](https://github.com/tidwall/tile38), but also works well for Redis
operations that are not covered by the `redis-benchmark` tool such as the
`GEO*` commands, custom lua scripts, or [Redis Modules](http://antirez.com/news/106).
## Getting Started
### Installing
To start using Redbench, install Go and run `go get`:
```sh
$ go get -u github.com/tidwall/redbench
```
This will retrieve the library.
### Example
The following example will run a benchmark for the `PING,SET,GET,GEOADD,GEORADIUS`
commands on a server at 127.0.0.1:6379.
```go
package main
import (
"math/rand"
"strconv"
"time"
"github.com/tidwall/redbench"
)
func main() {
redbench.Bench("PING", "127.0.0.1:6379", nil, nil, func(buf []byte) []byte {
return redbench.AppendCommand(buf, "PING")
})
redbench.Bench("SET", "127.0.0.1:6379", nil, nil, func(buf []byte) []byte {
return redbench.AppendCommand(buf, "SET", "key:string", "val")
})
redbench.Bench("GET", "127.0.0.1:6379", nil, nil, func(buf []byte) []byte {
return redbench.AppendCommand(buf, "GET", "key:string")
})
rand.Seed(time.Now().UnixNano())
redbench.Bench("GEOADD", "127.0.0.1:6379", nil, nil, func(buf []byte) []byte {
return redbench.AppendCommand(buf, "GEOADD", "key:geo",
strconv.FormatFloat(rand.Float64()*360-180, 'f', 7, 64),
strconv.FormatFloat(rand.Float64()*170-85, 'f', 7, 64),
strconv.Itoa(rand.Int()))
})
redbench.Bench("GEORADIUS", "127.0.0.1:6379", nil, nil, func(buf []byte) []byte {
return redbench.AppendCommand(buf, "GEORADIUS", "key:geo",
strconv.FormatFloat(rand.Float64()*360-180, 'f', 7, 64),
strconv.FormatFloat(rand.Float64()*170-85, 'f', 7, 64),
"10", "km")
})
}
```
The operations is like as executing, but without the `GEO*` commands.
```
$ redis-benchmark -t PING,SET,GET
```
### Custom Options
```go
type Options struct {
Requests int
Clients int
Pipeline int
Quiet bool
CSV bool
Stdout io.Writer
Stderr io.Writer
}
```
## Contact
Josh Baker [@tidwall](http://twitter.com/tidwall)
## License
Redbench source code is available under the MIT [License](/LICENSE).

271
vendor/github.com/tidwall/redbench/bench.go generated vendored Normal file
View File

@ -0,0 +1,271 @@
package redbench
import (
"bufio"
"errors"
"fmt"
"io"
"io/ioutil"
"math"
"net"
"os"
"strconv"
"sync/atomic"
"time"
)
func readResp(rd *bufio.Reader, n int) error {
for i := 0; i < n; i++ {
line, err := rd.ReadBytes('\n')
if err != nil {
return err
}
switch line[0] {
default:
return errors.New("invalid server response")
case '+', ':':
case '-':
case '$':
n, err := strconv.ParseInt(string(line[1:len(line)-2]), 10, 64)
if err != nil {
return err
}
if _, err = io.CopyN(ioutil.Discard, rd, n+2); err != nil {
return err
}
case '*':
n, err := strconv.ParseInt(string(line[1:len(line)-2]), 10, 64)
if err != nil {
return err
}
readResp(rd, int(n))
}
}
return nil
}
// Options represents various options used by the Bench() function.
type Options struct {
Requests int
Clients int
Pipeline int
Quiet bool
CSV bool
Stdout io.Writer
Stderr io.Writer
}
// DefaultsOptions are the default options used by the Bench() function.
var DefaultOptions = &Options{
Requests: 100000,
Clients: 50,
Pipeline: 1,
Quiet: false,
CSV: false,
Stdout: os.Stdout,
Stderr: os.Stderr,
}
// Bench performs a benchmark on the server at the specified address.
func Bench(
name string,
addr string,
opts *Options,
prep func(conn net.Conn) bool,
fill func(buf []byte) []byte,
) {
if opts == nil {
opts = DefaultOptions
}
if opts.Stderr == nil {
opts.Stderr = ioutil.Discard
}
if opts.Stdout == nil {
opts.Stdout = ioutil.Discard
}
var totalPayload uint64
var count uint64
var duration int64
rpc := opts.Requests / opts.Clients
rpcex := opts.Requests % opts.Clients
var tstop int64
remaining := int64(opts.Clients)
errs := make([]error, opts.Clients)
durs := make([][]time.Duration, opts.Clients)
conns := make([]net.Conn, opts.Clients)
// create all clients
for i := 0; i < opts.Clients; i++ {
crequests := rpc
if i == opts.Clients-1 {
crequests += rpcex
}
durs[i] = make([]time.Duration, crequests)
for j := 0; j < len(durs[i]); j++ {
durs[i][j] = -1
}
conn, err := net.Dial("tcp", addr)
if err != nil {
if i == 0 {
fmt.Fprintf(opts.Stderr, "%s\n", err.Error())
return
}
errs[i] = err
}
if conn != nil && prep != nil {
if !prep(conn) {
conn.Close()
conn = nil
}
}
conns[i] = conn
}
tstart := time.Now()
for i := 0; i < opts.Clients; i++ {
crequests := rpc
if i == opts.Clients-1 {
crequests += rpcex
}
go func(conn net.Conn, client, crequests int) {
defer func() {
atomic.AddInt64(&remaining, -1)
}()
if conn == nil {
return
}
err := func() error {
var buf []byte
rd := bufio.NewReader(conn)
for i := 0; i < crequests; i += opts.Pipeline {
n := opts.Pipeline
if i+n > crequests {
n = crequests - i
}
buf = buf[:0]
for i := 0; i < n; i++ {
buf = fill(buf)
}
atomic.AddUint64(&totalPayload, uint64(len(buf)))
start := time.Now()
_, err := conn.Write(buf)
if err != nil {
return err
}
if err := readResp(rd, n); err != nil {
return err
}
stop := time.Since(start)
for j := 0; j < n; j++ {
durs[client][i+j] = stop / time.Duration(n)
}
atomic.AddInt64(&duration, int64(stop))
atomic.AddUint64(&count, uint64(n))
atomic.StoreInt64(&tstop, int64(time.Since(tstart)))
}
return nil
}()
if err != nil {
errs[client] = err
}
}(conns[i], i, crequests)
}
var die bool
for {
remaining := int(atomic.LoadInt64(&remaining)) // active clients
count := int(atomic.LoadUint64(&count)) // completed requests
real := time.Duration(atomic.LoadInt64(&tstop)) // real duration
totalPayload := int(atomic.LoadUint64(&totalPayload)) // size of all bytes sent
more := remaining > 0
var realrps float64
if real > 0 {
realrps = float64(count) / (float64(real) / float64(time.Second))
}
if !opts.CSV {
fmt.Fprintf(opts.Stdout, "\r%s: %.2f", name, realrps)
if more {
fmt.Fprintf(opts.Stdout, "\r")
} else if opts.Quiet {
fmt.Fprintf(opts.Stdout, " requests per second\n")
} else {
fmt.Fprintf(opts.Stdout, "\r====== %s ======\n", name)
fmt.Fprintf(opts.Stdout, " %d requests completed in %.2f seconds\n", opts.Requests, float64(real)/float64(time.Second))
fmt.Fprintf(opts.Stdout, " %d parallel clients\n", opts.Clients)
fmt.Fprintf(opts.Stdout, " %d bytes payload\n", totalPayload/opts.Requests)
fmt.Fprintf(opts.Stdout, " keep alive: 1\n")
fmt.Fprintf(opts.Stdout, "\n")
var limit time.Duration
var lastper float64
for {
limit += time.Millisecond
var hits, count int
for i := 0; i < len(durs); i++ {
for j := 0; j < len(durs[i]); j++ {
dur := durs[i][j]
if dur == -1 {
continue
}
if dur < limit {
hits++
}
count++
}
}
per := float64(hits) / float64(count)
if math.Floor(per*10000) == math.Floor(lastper*10000) {
continue
}
lastper = per
fmt.Fprintf(opts.Stdout, "%.2f%% <= %d milliseconds\n", per*100, (limit-time.Millisecond)/time.Millisecond)
if per == 1.0 {
break
}
}
fmt.Fprintf(opts.Stdout, "%.2f requests per second\n\n", realrps)
}
}
if !more {
if opts.CSV {
fmt.Fprintf(opts.Stdout, "\"%s\",\"%.2f\"\n", name, realrps)
}
for _, err := range errs {
if err != nil {
fmt.Fprintf(opts.Stderr, "%s\n", err)
die = true
if count == 0 {
break
}
}
}
break
}
time.Sleep(time.Second / 5)
}
// close clients
for i := 0; i < len(conns); i++ {
if conns[i] != nil {
conns[i].Close()
}
}
if die {
os.Exit(1)
}
}
// AppendCommand will append a Redis command to the byte slice and
// returns a modifed slice.
func AppendCommand(buf []byte, args ...string) []byte {
buf = append(buf, '*')
buf = strconv.AppendInt(buf, int64(len(args)), 10)
buf = append(buf, '\r', '\n')
for _, arg := range args {
buf = append(buf, '$')
buf = strconv.AppendInt(buf, int64(len(arg)), 10)
buf = append(buf, '\r', '\n')
buf = append(buf, arg...)
buf = append(buf, '\r', '\n')
}
return buf
}