|
@@ -15,6 +15,7 @@
|
|
|
package command
|
|
package command
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
|
|
+ "context"
|
|
|
"fmt"
|
|
"fmt"
|
|
|
"log"
|
|
"log"
|
|
|
"sync"
|
|
"sync"
|
|
@@ -23,25 +24,18 @@ import (
|
|
|
"github.com/coreos/etcd/clientv3"
|
|
"github.com/coreos/etcd/clientv3"
|
|
|
|
|
|
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/cobra"
|
|
|
|
|
+ "golang.org/x/time/rate"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
+// shared flags
|
|
|
var (
|
|
var (
|
|
|
- rounds int // total number of rounds the operation needs to be performed
|
|
|
|
|
- totalClientConnections int // total number of client connections to be made with server
|
|
|
|
|
- noOfPrefixes int // total number of prefixes which will be watched upon
|
|
|
|
|
- watchPerPrefix int // number of watchers per prefix
|
|
|
|
|
- reqRate int // put request per second
|
|
|
|
|
- totalKeys int // total number of keys for operation
|
|
|
|
|
- runningTime time.Duration // time for which operation should be performed
|
|
|
|
|
|
|
+ totalClientConnections int // total number of client connections to be made with server
|
|
|
|
|
+ endpoints []string
|
|
|
|
|
+ dialTimeout time.Duration
|
|
|
|
|
+ rounds int // total number of rounds to run; set to <= 0 to run forever.
|
|
|
|
|
+ reqRate int // maximum number of requests per second.
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
-// GlobalFlags are flags that defined globally
|
|
|
|
|
-// and are inherited to all sub-commands.
|
|
|
|
|
-type GlobalFlags struct {
|
|
|
|
|
- Endpoints []string
|
|
|
|
|
- DialTimeout time.Duration
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
type roundClient struct {
|
|
type roundClient struct {
|
|
|
c *clientv3.Client
|
|
c *clientv3.Client
|
|
|
progress int
|
|
progress int
|
|
@@ -61,16 +55,21 @@ func newClient(eps []string, timeout time.Duration) *clientv3.Client {
|
|
|
return c
|
|
return c
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func doRounds(rcs []roundClient, rounds int) {
|
|
|
|
|
|
|
+func doRounds(rcs []roundClient, rounds int, requests int) {
|
|
|
var mu sync.Mutex
|
|
var mu sync.Mutex
|
|
|
var wg sync.WaitGroup
|
|
var wg sync.WaitGroup
|
|
|
|
|
|
|
|
wg.Add(len(rcs))
|
|
wg.Add(len(rcs))
|
|
|
finished := make(chan struct{})
|
|
finished := make(chan struct{})
|
|
|
|
|
+ limiter := rate.NewLimiter(rate.Limit(reqRate), reqRate)
|
|
|
for i := range rcs {
|
|
for i := range rcs {
|
|
|
go func(rc *roundClient) {
|
|
go func(rc *roundClient) {
|
|
|
defer wg.Done()
|
|
defer wg.Done()
|
|
|
- for rc.progress < rounds {
|
|
|
|
|
|
|
+ for rc.progress < rounds || rounds <= 0 {
|
|
|
|
|
+ if err := limiter.WaitN(context.Background(), requests/len(rcs)); err != nil {
|
|
|
|
|
+ log.Panicf("rate limiter error %v", err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
for rc.acquire() != nil { /* spin */
|
|
for rc.acquire() != nil { /* spin */
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -85,7 +84,7 @@ func doRounds(rcs []roundClient, rounds int) {
|
|
|
finished <- struct{}{}
|
|
finished <- struct{}{}
|
|
|
|
|
|
|
|
mu.Lock()
|
|
mu.Lock()
|
|
|
- for rc.release() != nil {
|
|
|
|
|
|
|
+ for rc.release() != nil { /* spin */
|
|
|
mu.Unlock()
|
|
mu.Unlock()
|
|
|
mu.Lock()
|
|
mu.Lock()
|
|
|
}
|
|
}
|
|
@@ -95,7 +94,7 @@ func doRounds(rcs []roundClient, rounds int) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
start := time.Now()
|
|
start := time.Now()
|
|
|
- for i := 1; i < len(rcs)*rounds+1; i++ {
|
|
|
|
|
|
|
+ for i := 1; i < len(rcs)*rounds+1 || rounds <= 0; i++ {
|
|
|
select {
|
|
select {
|
|
|
case <-finished:
|
|
case <-finished:
|
|
|
if i%100 == 0 {
|
|
if i%100 == 0 {
|