├── .gitignore ├── Makefile ├── README.md ├── config └── config.go ├── doc ├── mo1.png ├── mo2.png └── one.png ├── docker ├── docker-compose.yml ├── redis-cluster-linux │ ├── Makefile │ └── docker-compose.yml └── redis-cluster-mac │ ├── Makefile │ └── docker-compose.yml ├── go.mod ├── go.sum ├── internal └── datasize │ ├── datasize.go │ └── datasize_test.go ├── main.go ├── models └── result.go ├── scripts └── build-all.sh ├── tester ├── context.go ├── multi.go └── remote.go ├── utils └── utils.go └── wares ├── logger.go └── redis.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | release 4 | vendor/* 5 | !vendor/*.md -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | build: 3 | bash ./scripts/build-all.sh 4 | 5 | up-redis: 6 | docker-compose -f docker/docker-compose.yml -p redisbench up -d 7 | 8 | down-redis: 9 | docker-compose -f docker/docker-compose.yml -p redisbench down 10 | 11 | run: 12 | go run main.go -a localhost:6379 -c 10 -n 5000 -d 1000 13 | 14 | run1: 15 | go run main.go -a localhost:6379 -c 10 -n 2000 -d 1000 -ma localhost:9001,localhost:9002 -mo 1 16 | 17 | run2: 18 | go run main.go -a localhost:6379 -c 10 -n 2000 -d 1000 -ma localhost:9001,localhost:9002 -mo 2 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Redis & Redis Cluster benchmark Tool 2 | 3 | - Written in Golang 4 | - Can test redis single instance 5 | - Can test redis cluster 6 | - Can take advantage of multi-core 7 | - Supports running on multiple machines at the same time, for testing a large redis cluster (The same hardware of machines are needed for ) 8 | 9 | ## Warning 10 | 11 | Testing data keys named like `benchmark-set.*`, make sure they are not conflicting with your keys. 12 | 13 | ## Help 14 | 15 | ```console 16 | $ ./redisbench -h 17 | Usage of redisbench: 18 | -a string 19 | Redis instance address or Cluster addresses. IP:PORT[,IP:PORT] 20 | -c int 21 | Clients number for concurrence (default 1) 22 | -d int 23 | Data size in bytes (default 1000) 24 | -db int 25 | Choose a db, only for non-cluster (default 0) 26 | -ma string 27 | addresses for run multiple testers at the same time 28 | -mo int 29 | the order current tester is in multiple testers 30 | -n int 31 | Testing times at every client (default 3000) 32 | -p string 33 | The password for auth, only for non-cluster 34 | ``` 35 | 36 | ## Example 37 | 38 | ``` 39 | ./redisbench -a localhost:6379 -c 10 -n 5000 -d 1000 40 | ``` 41 | 42 | ![](doc/one.png) 43 | 44 | ### Use multiple testing nodes 45 | 46 | ```sh 47 | ./redisbench -a localhost:6379 -c 10 -n 2000 -d 1000 -ma localhost:9001,localhost:9002 -mo 1 48 | ``` 49 | 50 | ![](doc/mo1.png) 51 | 52 | ```sh 53 | ./redisbench -a localhost:6379 -c 10 -n 2000 -d 1000 -ma localhost:9001,localhost:9002 -mo 2 54 | ``` 55 | 56 | ![](doc/mo2.png) -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | // RedisAddr : Redis address or Cluster addresses 8 | var RedisAddr string 9 | 10 | // RedisPassword : Redis password 11 | var RedisPassword string 12 | 13 | // RedisDB : Redis db 14 | var RedisDB int 15 | 16 | // ClientNum : Client number for concurrence 17 | var ClientNum int 18 | 19 | // TestTimes : Test times of every client 20 | var TestTimes int 21 | 22 | // DataSize : Set data size at once 23 | var DataSize int 24 | 25 | // MultiAddr : Run multi testers at the same time 26 | // while single machine can not hold the testing 27 | var MultiAddr string 28 | 29 | // MultiOrder : The order current tester is 30 | var MultiOrder int 31 | 32 | // Parse configure from command line flags 33 | func Parse() { 34 | flag.StringVar(&RedisAddr, "a", "localhost:6379", "Redis instance address or Cluster addresses. IP:PORT[,IP:PORT]") 35 | flag.StringVar(&RedisPassword, "p", "", "The password for auth, only for non-cluster") 36 | flag.IntVar(&RedisDB, "db", 0, "Choose a db, only for non-cluster (default 0)") 37 | flag.IntVar(&ClientNum, "c", 1, "Clients number for concurrence") 38 | flag.IntVar(&TestTimes, "n", 3000, "Testing times at every client") 39 | flag.IntVar(&DataSize, "d", 1000, "Data size in bytes") 40 | flag.StringVar(&MultiAddr, "ma", "", "addresses for run multiple testers at the same time") 41 | flag.IntVar(&MultiOrder, "mo", 0, "the order current tester is in multiple testers") 42 | flag.Parse() 43 | } 44 | -------------------------------------------------------------------------------- /doc/mo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/panjiang/redisbench/98ed4dd65581d4d139af05aa87a405e6f6716d51/doc/mo1.png -------------------------------------------------------------------------------- /doc/mo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/panjiang/redisbench/98ed4dd65581d4d139af05aa87a405e6f6716d51/doc/mo2.png -------------------------------------------------------------------------------- /doc/one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/panjiang/redisbench/98ed4dd65581d4d139af05aa87a405e6f6716d51/doc/one.png -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | redis: 5 | image: redis:5 6 | ports: 7 | - "6379:6379" 8 | volumes: 9 | - ~/docker/redisbench/redis:/data 10 | command: redis-server --appendonly yes -------------------------------------------------------------------------------- /docker/redis-cluster-linux/Makefile: -------------------------------------------------------------------------------- 1 | 2 | create-cluster: 3 | docker-compose exec redis1 redis-cli --cluster create \ 4 | 127.0.0.1:6001 127.0.0.1:6002 127.0.0.1:6003 \ 5 | 127.0.0.1:6004 127.0.0.1:6005 127.0.0.1:6006 \ 6 | --cluster-replicas 1 7 | 8 | up: 9 | docker-compose up --force-recreate -d 10 | 11 | down: 12 | docker-compose down 13 | 14 | cli: 15 | docker-compose exec redis1 redis-cli -c -p 6001 16 | 17 | nodes: 18 | docker-compose exec redis1 redis-cli -p 6001 cluster nodes -------------------------------------------------------------------------------- /docker/redis-cluster-linux/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | redis1: 5 | image: redis:5 6 | network_mode: host 7 | volumes: 8 | - ~/docker/redis-cluster/redis1:/data 9 | command: redis-server --port 6001 --appendonly yes --cluster-enabled yes 10 | 11 | redis2: 12 | image: redis:5 13 | network_mode: host 14 | volumes: 15 | - ~/docker/redis-cluster/redis2:/data 16 | command: redis-server --port 6002 --appendonly yes --cluster-enabled yes 17 | 18 | redis3: 19 | image: redis:5 20 | network_mode: host 21 | volumes: 22 | - ~/docker/redis-cluster/redis3:/data 23 | command: redis-server --port 6003 --appendonly yes --cluster-enabled yes 24 | 25 | redis4: 26 | image: redis:5 27 | network_mode: host 28 | volumes: 29 | - ~/docker/redis-cluster/redis4:/data 30 | command: redis-server --port 6004 --appendonly yes --cluster-enabled yes 31 | 32 | redis5: 33 | image: redis:5 34 | network_mode: host 35 | volumes: 36 | - ~/docker/redis-cluster/redis5:/data 37 | command: redis-server --port 6005 --appendonly yes --cluster-enabled yes 38 | 39 | redis6: 40 | image: redis:5 41 | network_mode: host 42 | volumes: 43 | - ~/docker/redis-cluster/redis6:/data 44 | command: redis-server --port 6006 --appendonly yes --cluster-enabled yes -------------------------------------------------------------------------------- /docker/redis-cluster-mac/Makefile: -------------------------------------------------------------------------------- 1 | 2 | create-cluster: 3 | docker-compose exec redis1 redis-cli --cluster create \ 4 | 127.0.0.1:6001 127.0.0.1:6002 127.0.0.1:6003 \ 5 | 127.0.0.1:6004 127.0.0.1:6005 127.0.0.1:6006 \ 6 | --cluster-replicas 1 7 | 8 | up: 9 | docker-compose up --force-recreate -d 10 | 11 | down: 12 | docker-compose down 13 | 14 | logs: 15 | docker-compose logs -f --tail 50 16 | 17 | cli: 18 | docker-compose exec redis1 redis-cli -c -p 6001 19 | 20 | nodes: 21 | docker-compose exec redis1 redis-cli -p 6001 cluster nodes -------------------------------------------------------------------------------- /docker/redis-cluster-mac/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | redis1: 5 | image: redis:5 6 | ports: 7 | - '6001:6001' 8 | - '6002:6002' 9 | - '6003:6003' 10 | - '6004:6004' 11 | - '6005:6005' 12 | - '6006:6006' 13 | volumes: 14 | - ~/docker/redis-cluster/redis1:/data 15 | command: redis-server --port 6001 --appendonly yes --cluster-enabled yes 16 | 17 | redis2: 18 | image: redis:5 19 | network_mode: "service:redis1" 20 | volumes: 21 | - ~/docker/redis-cluster/redis2:/data 22 | command: redis-server --port 6002 --appendonly yes --cluster-enabled yes 23 | 24 | redis3: 25 | image: redis:5 26 | network_mode: "service:redis1" 27 | volumes: 28 | - ~/docker/redis-cluster/redis3:/data 29 | command: redis-server --port 6003 --appendonly yes --cluster-enabled yes 30 | 31 | redis4: 32 | image: redis:5 33 | network_mode: "service:redis1" 34 | volumes: 35 | - ~/docker/redis-cluster/redis4:/data 36 | command: redis-server --port 6004 --appendonly yes --cluster-enabled yes 37 | 38 | redis5: 39 | image: redis:5 40 | network_mode: "service:redis1" 41 | volumes: 42 | - ~/docker/redis-cluster/redis5:/data 43 | command: redis-server --port 6005 --appendonly yes --cluster-enabled yes 44 | 45 | redis6: 46 | image: redis:5 47 | network_mode: "service:redis1" 48 | volumes: 49 | - ~/docker/redis-cluster/redis6:/data 50 | command: redis-server --port 6006 --appendonly yes --cluster-enabled yes -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/panjiang/redisbench 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/davecgh/go-spew v1.1.1 // indirect 7 | github.com/go-redis/redis/v8 v8.10.0 8 | github.com/kr/pretty v0.1.0 // indirect 9 | github.com/rs/zerolog v1.22.0 10 | github.com/stretchr/testify v1.7.0 11 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect 12 | ) 13 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= 2 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 3 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 8 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 9 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 10 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 11 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 12 | github.com/go-redis/redis/v8 v8.10.0 h1:OZwrQKuZqdJ4QIM8wn8rnuz868Li91xA3J2DEq+TPGA= 13 | github.com/go-redis/redis/v8 v8.10.0/go.mod h1:vXLTvigok0VtUX0znvbcEW1SOt4OA9CU1ZfnOtKOaiM= 14 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 15 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 16 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 17 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 18 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 19 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 20 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= 21 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 22 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 23 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 24 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 25 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 26 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 27 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 28 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 29 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 30 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 31 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 32 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 33 | github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= 34 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 35 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 36 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 37 | github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4= 38 | github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= 39 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 40 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 41 | github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ= 42 | github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= 43 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 44 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 45 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 46 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= 47 | github.com/rs/zerolog v1.22.0 h1:XrVUjV4K+izZpKXZHlPrYQiDtmdGiCylnT4i43AAWxg= 48 | github.com/rs/zerolog v1.22.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM= 49 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 50 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 51 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 52 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 53 | go.opentelemetry.io/otel v0.20.0 h1:eaP0Fqu7SXHwvjiqDq83zImeehOHX8doTvU9AwXON8g= 54 | go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= 55 | go.opentelemetry.io/otel/metric v0.20.0 h1:4kzhXFP+btKm4jwxpjIqjs41A7MakRFUS86bqLHTIw8= 56 | go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= 57 | go.opentelemetry.io/otel/oteltest v0.20.0 h1:HiITxCawalo5vQzdHfKeZurV8x7ljcqAgiWzF6Vaeaw= 58 | go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= 59 | go.opentelemetry.io/otel/trace v0.20.0 h1:1DL6EXUdcg95gukhuRRvLDO/4X5THh/5dIV52lqtnbw= 60 | go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= 61 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 62 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 63 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 64 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 65 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 66 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 67 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 68 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 69 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 70 | golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U= 71 | golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 72 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 73 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 74 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 75 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 76 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 77 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 78 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 79 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 80 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 81 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 82 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 83 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 84 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= 85 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 86 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 87 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 88 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 89 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 90 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 91 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 92 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 93 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 94 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 95 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 96 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 97 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 98 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 99 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 100 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 101 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 102 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 103 | google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= 104 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 105 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 106 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 107 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 108 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 109 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 110 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 111 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 112 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 113 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 114 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 115 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 116 | -------------------------------------------------------------------------------- /internal/datasize/datasize.go: -------------------------------------------------------------------------------- 1 | package datasize 2 | 3 | import "fmt" 4 | 5 | type ByteSize uint64 6 | 7 | func (b ByteSize) String() string { 8 | const unit = 1024 9 | if b < unit { 10 | return fmt.Sprintf("%dB", b) 11 | } 12 | div, exp := int64(unit), 0 13 | for n := b / unit; n >= unit; n /= unit { 14 | div *= unit 15 | exp++ 16 | } 17 | return fmt.Sprintf("%d%c", 18 | int64(float64(b)/float64(div)), "KMGTPE"[exp]) 19 | } 20 | -------------------------------------------------------------------------------- /internal/datasize/datasize_test.go: -------------------------------------------------------------------------------- 1 | package datasize 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestByteSize(t *testing.T) { 10 | require.Equal(t, "1023B", ByteSize(1023).String()) 11 | require.Equal(t, "1K", ByteSize(1024).String()) 12 | require.Equal(t, "1K", ByteSize(1025).String()) 13 | require.Equal(t, "1M", ByteSize(1024*1024).String()) 14 | } 15 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "time" 8 | 9 | "github.com/rs/zerolog" 10 | "github.com/rs/zerolog/log" 11 | 12 | "github.com/panjiang/redisbench/config" 13 | "github.com/panjiang/redisbench/internal/datasize" 14 | "github.com/panjiang/redisbench/models" 15 | "github.com/panjiang/redisbench/tester" 16 | "github.com/panjiang/redisbench/utils" 17 | "github.com/panjiang/redisbench/wares" 18 | 19 | "github.com/go-redis/redis/v8" 20 | ) 21 | 22 | func init() { 23 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) 24 | } 25 | 26 | const ( 27 | keyPrefix = "benchmark-set" 28 | ) 29 | 30 | func executeSet(id int, times int, size int, redisClient redis.UniversalClient) { 31 | defer tester.Wg.Done() 32 | val := utils.RandSeq(size) 33 | var err error 34 | for i := 0; i < times; i++ { 35 | key := fmt.Sprintf("%s.%d.%d", keyPrefix, id, i) 36 | err = redisClient.Set(context.Background(), key, val, 0).Err() 37 | utils.FatalErr(err) 38 | } 39 | } 40 | 41 | func executeDel(id int, times int, redisClient redis.UniversalClient) { 42 | defer tester.Wg.Done() 43 | for i := 0; i < times; i++ { 44 | key := fmt.Sprintf("%s.%d.%d", keyPrefix, id, i) 45 | redisClient.Del(context.Background(), key) 46 | } 47 | } 48 | 49 | func main() { 50 | // Parse config arguments from command-line 51 | config.Parse() 52 | if config.MultiAddr != "" { 53 | tester.RPCRun() 54 | } 55 | 56 | tester.Wg.Wait() 57 | 58 | // Print test initial information 59 | totalTimes := int64(config.ClientNum * config.TestTimes) 60 | totalSize := datasize.ByteSize(config.ClientNum * config.TestTimes * config.DataSize) 61 | log.Info().Str("addr", config.RedisAddr).Msg("Redis") 62 | 63 | log.Info(). 64 | Int("clientNum", config.ClientNum). 65 | Int("testTimes", config.TestTimes). 66 | Stringer("dataSize", datasize.ByteSize(config.DataSize)). 67 | Msg("Config") 68 | 69 | log.Info(). 70 | Int64("times", totalTimes). 71 | Stringer("size", totalSize). 72 | Msg("Total") 73 | 74 | // Create a new redis client 75 | redisClient, err := wares.NewUniversalRedisClient() 76 | utils.FatalErr(err) 77 | 78 | // Run certain number clients for testing 79 | log.Info().Msg("Testing...") 80 | t1 := time.Now() 81 | for i := 0; i < config.ClientNum; i++ { 82 | tester.Wg.Add(1) 83 | go executeSet(i, config.TestTimes, config.DataSize, redisClient) 84 | } 85 | tester.Wg.Wait() 86 | t2 := time.Now() 87 | 88 | // Calculate the duration 89 | dur := t2.Sub(t1) 90 | order := 1 91 | if tester.Multi != nil { 92 | order = tester.Multi.Order 93 | } 94 | result := &models.NodeResult{Order: order, TotalTimes: totalTimes, TsBeg: t1, TsEnd: t2, TotalDur: dur} 95 | if tester.Multi == nil { 96 | log.Info(). 97 | Int64("times", result.TotalTimes). 98 | Stringer("duration", result.TotalDur). 99 | Int64("tps", tester.CalTps(result.TotalTimes, result.TotalDur)). 100 | Msg("* Result") 101 | 102 | } else { 103 | if !tester.Multi.IsMaster() { 104 | // Notice master to settle 105 | tester.Multi.NoticeMasterSettle(result) 106 | log.Info().Msg("* See summary info on node 1") 107 | } else { 108 | tester.Wg.Add(1) // Wait all others nodes settling call 109 | tester.Multi.NodeSettle(result) 110 | 111 | tester.Wg.Wait() 112 | time.Sleep(time.Second) 113 | // Summary all nodes result include self 114 | summary := tester.Multi.Summary() 115 | 116 | // Print testing result 117 | log.Info(). 118 | Int64("times", summary.TotalTimes). 119 | Stringer("duration", summary.TotalDur). 120 | Int("tps", summary.TPS). 121 | Msg("* Summary") 122 | } 123 | } 124 | 125 | log.Debug().Msg("Deleting testing data...") 126 | for i := 0; i < config.ClientNum; i++ { 127 | tester.Wg.Add(1) 128 | go executeDel(i, config.TestTimes, redisClient) 129 | } 130 | tester.Wg.Wait() 131 | log.Debug().Msg("Over") 132 | } 133 | -------------------------------------------------------------------------------- /models/result.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | // SummaryResult : Summary result of all nodes 6 | type SummaryResult struct { 7 | TotalTimes int64 8 | TotalDur time.Duration 9 | TPS int 10 | } 11 | 12 | // NodeResult : Only one node's result 13 | type NodeResult struct { 14 | Order int 15 | TotalTimes int64 16 | TotalDur time.Duration 17 | TsBeg time.Time 18 | TsEnd time.Time 19 | } 20 | -------------------------------------------------------------------------------- /scripts/build-all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | APP="redisbench" 6 | PLATFORMS="darwin freebsd linux windows" 7 | ARCHS="amd64" 8 | 9 | for OS in ${PLATFORMS[@]}; do 10 | for ARCH in ${ARCHS[@]}; do 11 | NAME="${APP}-${OS}-${ARCH}" 12 | if [[ "${OS}" == "windows" ]]; then 13 | NAME="${NAME}.exe" 14 | fi 15 | 16 | env GOOS=${OS} GOARCH=${ARCH} go build -o release/${NAME} . 17 | done 18 | done -------------------------------------------------------------------------------- /tester/context.go: -------------------------------------------------------------------------------- 1 | package tester 2 | 3 | import "sync" 4 | 5 | // Wg : Global wait group 6 | var Wg sync.WaitGroup 7 | 8 | // Multi : multiple tester contronl handler 9 | var Multi *MultiTester 10 | -------------------------------------------------------------------------------- /tester/multi.go: -------------------------------------------------------------------------------- 1 | package tester 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/rpc" 7 | "strings" 8 | "time" 9 | 10 | "github.com/rs/zerolog/log" 11 | 12 | "github.com/panjiang/redisbench/config" 13 | "github.com/panjiang/redisbench/models" 14 | "github.com/panjiang/redisbench/utils" 15 | ) 16 | 17 | // MasterNodeOrder : The order of master node's 18 | const MasterNodeOrder int = 1 19 | 20 | func NodeName(order int, addr string) string { 21 | return fmt.Sprintf("#%d@%s", order, addr) 22 | } 23 | 24 | func CalTps(times int64, dur time.Duration) int64 { 25 | return int64(float64(times) / dur.Seconds()) 26 | } 27 | 28 | // MultiTester : Multiple testers class 29 | type MultiTester struct { 30 | Order int // Current order 31 | Addr string // Current address 32 | Addrs map[int]string // All addresses 33 | Nodes map[int]*rpc.Client // Registered orders 34 | Results map[int]*models.NodeResult // Every tester result 35 | } 36 | 37 | // IsMaster : master receive all nodes connection 38 | // And notice them to do some actions 39 | func (mt *MultiTester) IsMaster() bool { 40 | return mt.Order == MasterNodeOrder 41 | } 42 | 43 | func (mt *MultiTester) connectToNodes() error { 44 | // Register to others nodes blocking 45 | for order, client := range mt.Nodes { 46 | if client != nil { 47 | continue 48 | } 49 | addr := mt.Addrs[order] 50 | // log.Printf("connect to node: %s#%d", addr, order) 51 | client, err := rpc.DialHTTP("tcp", addr) 52 | if err != nil { 53 | return err 54 | } 55 | mt.Nodes[order] = client 56 | log.Info().Str("node", NodeName(order, addr)).Msg("Connected") 57 | } 58 | return nil 59 | } 60 | 61 | // MustConnectToNodes : Master connects to all others nodes 62 | func (mt *MultiTester) MustConnectToNodes() { 63 | for { 64 | err := mt.connectToNodes() 65 | if err == nil { 66 | break 67 | } 68 | time.Sleep(time.Second) 69 | } 70 | } 71 | 72 | // NoticeNodesToStart : Master notices all nodes to start 73 | func (mt *MultiTester) NoticeNodesToStart() { 74 | for _, client := range mt.Nodes { 75 | err := client.Call("RPC.Start", MasterNodeOrder, nil) 76 | utils.FatalErr(err) 77 | } 78 | } 79 | 80 | // NoticeMasterSettle : Nodes notice master settle 81 | func (mt *MultiTester) NoticeMasterSettle(result *models.NodeResult) { 82 | client := mt.Nodes[1] 83 | err := client.Call("RPC.Settle", result, nil) 84 | utils.FatalErr(err) 85 | } 86 | 87 | // NodeSettle : One node settle method 88 | func (mt *MultiTester) NodeSettle(result *models.NodeResult) { 89 | log.Info(). 90 | Str("node", NodeName(result.Order, mt.Addrs[result.Order])). 91 | Stringer("duration", result.TotalDur). 92 | Int64("tps", CalTps(result.TotalTimes, result.TotalDur)). 93 | Msg("* Settle") 94 | mt.Results[result.Order] = result 95 | 96 | for _, result := range mt.Results { 97 | if result == nil { 98 | return 99 | } 100 | } 101 | 102 | Wg.Done() 103 | } 104 | 105 | // Summary : Summary all nodes' results after them run over 106 | func (mt *MultiTester) Summary() *models.SummaryResult { 107 | summary := new(models.SummaryResult) 108 | 109 | tsMin := mt.Results[MasterNodeOrder].TsBeg 110 | tsMax := mt.Results[MasterNodeOrder].TsEnd 111 | for _, result := range mt.Results { 112 | summary.TotalTimes += result.TotalTimes 113 | if result.TsBeg.Before(tsMin) { 114 | tsMin = result.TsBeg 115 | } 116 | if result.TsEnd.After(tsMax) { 117 | tsMax = result.TsEnd 118 | } 119 | } 120 | summary.TotalDur = tsMax.Sub(tsMin) 121 | summary.TPS = int(float64(summary.TotalTimes) / summary.TotalDur.Seconds()) 122 | return summary 123 | } 124 | 125 | // NewMultiTester : Create a new MultiTester pointer 126 | func NewMultiTester() (*MultiTester, error) { 127 | if config.MultiAddr == "" { 128 | return nil, errors.New("invalid multi addresses has been set") 129 | } 130 | addrsArr := strings.Split(config.MultiAddr, ",") 131 | 132 | if config.MultiOrder <= 0 || config.MultiOrder > len(addrsArr) { 133 | return nil, errors.New("invalid order while multi test has been set") 134 | } 135 | 136 | multi := &MultiTester{Order: config.MultiOrder} 137 | multi.Addr = addrsArr[config.MultiOrder-1] 138 | multi.Addrs = make(map[int]string) 139 | multi.Nodes = make(map[int]*rpc.Client) 140 | multi.Results = make(map[int]*models.NodeResult) 141 | for i, addr := range addrsArr { 142 | multi.Addrs[i+1] = addr 143 | multi.Results[i+1] = nil 144 | if i+1 == multi.Order { 145 | continue 146 | } 147 | 148 | // Others nodes only connect master node 149 | if !multi.IsMaster() && i+1 != MasterNodeOrder { 150 | continue 151 | } 152 | 153 | multi.Nodes[i+1] = nil 154 | } 155 | return multi, nil 156 | } 157 | -------------------------------------------------------------------------------- /tester/remote.go: -------------------------------------------------------------------------------- 1 | package tester 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "net/rpc" 7 | 8 | "github.com/panjiang/redisbench/models" 9 | "github.com/panjiang/redisbench/utils" 10 | "github.com/rs/zerolog/log" 11 | ) 12 | 13 | // RPC : RPC class between nodes 14 | type RPC int 15 | 16 | // Start : Master notices others nodes start testing 17 | func (t *RPC) Start(order int, reply *int) error { 18 | if order != 1 { 19 | log.Fatal().Msg("Invalid master order") 20 | } 21 | Wg.Done() 22 | return nil 23 | } 24 | 25 | // Settle : Others nodes notice master result for settling 26 | func (t *RPC) Settle(result *models.NodeResult, reply *int) error { 27 | Multi.NodeSettle(result) 28 | return nil 29 | } 30 | 31 | // RPCRun : Register rpc handler, and connect to related nodes 32 | func RPCRun() { 33 | var err error 34 | Multi, err = NewMultiTester() 35 | utils.FatalErr(err) 36 | 37 | // All nodes handle RPC, 38 | // for receiving start sign 39 | rpc.Register(new(RPC)) 40 | rpc.HandleHTTP() 41 | 42 | ln, err := net.Listen("tcp", Multi.Addr) 43 | utils.FatalErr(err) 44 | 45 | Wg.Add(1) // Wait start sign from master 46 | go http.Serve(ln, nil) 47 | log.Info().Str("node", NodeName(Multi.Order, Multi.Addr)).Msg("Listened") 48 | 49 | Multi.MustConnectToNodes() 50 | 51 | if Multi.IsMaster() { 52 | // Master connect to all nodes, 53 | // then notice all start testing 54 | Wg.Done() // Don't need wait rpc connect 55 | Multi.NoticeNodesToStart() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "log" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | // FatalErr : If the error is not nil fatal it 10 | func FatalErr(err error) { 11 | if err != nil { 12 | log.Fatalln(err) 13 | } 14 | } 15 | 16 | var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 17 | 18 | // RandSeq : Create a string sequence with random chars 19 | func RandSeq(n int) string { 20 | b := make([]rune, n) 21 | for i := range b { 22 | b[i] = letters[rand.Intn(len(letters))] 23 | } 24 | return string(b) 25 | } 26 | 27 | // NowTs : Return timestamp now (second level) 28 | func NowTs() int64 { 29 | return time.Now().UnixNano() / int64(time.Second) 30 | } 31 | 32 | // NowMilliTs : Return timestamp now (millisecond level) 33 | func NowMilliTs() int64 { 34 | return time.Now().UnixNano() / int64(time.Millisecond) 35 | } 36 | -------------------------------------------------------------------------------- /wares/logger.go: -------------------------------------------------------------------------------- 1 | package wares 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | ) 8 | 9 | type logWriter struct { 10 | } 11 | 12 | func (writer logWriter) Write(bytes []byte) (int, error) { 13 | return fmt.Print(time.Now().UTC().Format("2006-01-02 15:04:05.999") + " " + string(bytes)) 14 | } 15 | 16 | // InitLogger : Initial logger with a new formatting 17 | func InitLogger() { 18 | log.SetFlags(0) 19 | log.SetOutput(new(logWriter)) 20 | } 21 | -------------------------------------------------------------------------------- /wares/redis.go: -------------------------------------------------------------------------------- 1 | package wares 2 | 3 | import ( 4 | "context" 5 | "runtime" 6 | "strings" 7 | "time" 8 | 9 | "github.com/panjiang/redisbench/config" 10 | 11 | "github.com/go-redis/redis/v8" 12 | ) 13 | 14 | // NewUniversalRedisClient Creates a new universal redis client, no matter single instance or redis cluster 15 | func NewUniversalRedisClient() (redis.UniversalClient, error) { 16 | addrsArray := strings.Split(config.RedisAddr, ",") 17 | client := redis.NewUniversalClient(&redis.UniversalOptions{ 18 | Addrs: addrsArray, 19 | Password: config.RedisPassword, 20 | DB: config.RedisDB, 21 | DialTimeout: time.Second * 3, 22 | PoolSize: 100 * runtime.NumCPU(), 23 | }) 24 | 25 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) 26 | defer cancel() 27 | _, err := client.Ping(ctx).Result() 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | return client, nil 33 | } 34 | --------------------------------------------------------------------------------