check.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. // Copyright 2017 The etcd Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package command
  15. import (
  16. "context"
  17. "encoding/binary"
  18. "fmt"
  19. "math"
  20. "math/rand"
  21. "os"
  22. "sync"
  23. "time"
  24. v3 "github.com/coreos/etcd/clientv3"
  25. "github.com/coreos/etcd/pkg/report"
  26. "github.com/spf13/cobra"
  27. "golang.org/x/time/rate"
  28. "gopkg.in/cheggaaa/pb.v1"
  29. )
  30. var (
  31. checkPerfLoad string
  32. checkPerfPrefix string
  33. )
  34. type checkPerfCfg struct {
  35. limit int
  36. clients int
  37. duration int
  38. }
  39. var checkPerfCfgMap = map[string]checkPerfCfg{
  40. // TODO: support read limit
  41. "s": {
  42. limit: 150,
  43. clients: 50,
  44. duration: 60,
  45. },
  46. "m": {
  47. limit: 1000,
  48. clients: 200,
  49. duration: 60,
  50. },
  51. "l": {
  52. limit: 8000,
  53. clients: 500,
  54. duration: 60,
  55. },
  56. "xl": {
  57. limit: 15000,
  58. clients: 1000,
  59. duration: 60,
  60. },
  61. }
  62. // NewCheckCommand returns the cobra command for "check".
  63. func NewCheckCommand() *cobra.Command {
  64. cc := &cobra.Command{
  65. Use: "check <subcommand>",
  66. Short: "commands for checking properties of the etcd cluster",
  67. }
  68. cc.AddCommand(NewCheckPerfCommand())
  69. return cc
  70. }
  71. // NewCheckPerfCommand returns the cobra command for "check perf".
  72. func NewCheckPerfCommand() *cobra.Command {
  73. cmd := &cobra.Command{
  74. Use: "perf [options]",
  75. Short: "Check the performance of the etcd cluster",
  76. Run: newCheckPerfCommand,
  77. }
  78. // TODO: support customized configuration
  79. cmd.Flags().StringVar(&checkPerfLoad, "load", "s", "The performance check's workload model. Accepted workloads: s(small), m(medium), l(large), xl(xLarge)")
  80. cmd.Flags().StringVar(&checkPerfPrefix, "prefix", "/etcdctl-check-perf/", "The prefix for writing the performance check's keys.")
  81. return cmd
  82. }
  83. // newCheckPerfCommand executes the "check perf" command.
  84. func newCheckPerfCommand(cmd *cobra.Command, args []string) {
  85. var checkPerfAlias = map[string]string{
  86. "s": "s", "small": "s",
  87. "m": "m", "medium": "m",
  88. "l": "l", "large": "l",
  89. "xl": "xl", "xLarge": "xl",
  90. }
  91. model, ok := checkPerfAlias[checkPerfLoad]
  92. if !ok {
  93. ExitWithError(ExitBadFeature, fmt.Errorf("unknown load option %v", checkPerfLoad))
  94. }
  95. cfg := checkPerfCfgMap[model]
  96. requests := make(chan v3.Op, cfg.clients)
  97. limit := rate.NewLimiter(rate.Limit(cfg.limit), 1)
  98. var clients []*v3.Client
  99. for i := 0; i < cfg.clients; i++ {
  100. clients = append(clients, mustClientFromCmd(cmd))
  101. }
  102. ctx, cancel := context.WithTimeout(context.Background(), time.Duration(cfg.duration)*time.Second)
  103. resp, err := clients[0].Get(ctx, checkPerfPrefix, v3.WithPrefix(), v3.WithLimit(1))
  104. cancel()
  105. if err != nil {
  106. ExitWithError(ExitError, err)
  107. }
  108. if len(resp.Kvs) > 0 {
  109. ExitWithError(ExitInvalidInput, fmt.Errorf("prefix %q has keys. Delete with etcdctl del --prefix %s first.", checkPerfPrefix, checkPerfPrefix))
  110. }
  111. ksize, vsize := 256, 1024
  112. k, v := make([]byte, ksize), string(make([]byte, vsize))
  113. bar := pb.New(cfg.duration)
  114. bar.Format("Bom !")
  115. bar.Start()
  116. r := report.NewReport("%4.4f")
  117. var wg sync.WaitGroup
  118. wg.Add(len(clients))
  119. for i := range clients {
  120. go func(c *v3.Client) {
  121. defer wg.Done()
  122. for op := range requests {
  123. st := time.Now()
  124. _, derr := c.Do(context.Background(), op)
  125. r.Results() <- report.Result{Err: derr, Start: st, End: time.Now()}
  126. }
  127. }(clients[i])
  128. }
  129. go func() {
  130. cctx, _ := context.WithTimeout(context.Background(), time.Duration(cfg.duration)*time.Second)
  131. for limit.Wait(cctx) == nil {
  132. binary.PutVarint(k, int64(rand.Int63n(math.MaxInt64)))
  133. requests <- v3.OpPut(checkPerfPrefix+string(k), v)
  134. }
  135. close(requests)
  136. }()
  137. go func() {
  138. for i := 0; i < cfg.duration; i++ {
  139. time.Sleep(time.Second)
  140. bar.Add(1)
  141. }
  142. bar.Finish()
  143. }()
  144. sc := r.Stats()
  145. wg.Wait()
  146. close(r.Results())
  147. s := <-sc
  148. ctx, cancel = context.WithTimeout(context.Background(), 30*time.Second)
  149. _, err = clients[0].Delete(ctx, checkPerfPrefix, v3.WithPrefix())
  150. cancel()
  151. if err != nil {
  152. ExitWithError(ExitError, err)
  153. }
  154. ok = true
  155. if len(s.ErrorDist) != 0 {
  156. fmt.Println("FAIL: too many errors")
  157. for k, v := range s.ErrorDist {
  158. fmt.Printf("FAIL: ERROR(%v) -> %d\n", k, v)
  159. }
  160. ok = false
  161. }
  162. if s.RPS/float64(cfg.limit) <= 0.9 {
  163. fmt.Printf("FAIL: Throughput too low: %d writes/s\n", int(s.RPS)+1)
  164. ok = false
  165. } else {
  166. fmt.Printf("PASS: Throughput is %d writes/s\n", int(s.RPS)+1)
  167. }
  168. if s.Slowest > 0.5 { // slowest request > 500ms
  169. fmt.Printf("Slowest request took too long: %fs\n", s.Slowest)
  170. ok = false
  171. } else {
  172. fmt.Printf("PASS: Slowest request took %fs\n", s.Slowest)
  173. }
  174. if s.Stddev > 0.1 { // stddev > 100ms
  175. fmt.Printf("Stddev too high: %fs\n", s.Stddev)
  176. ok = false
  177. } else {
  178. fmt.Printf("PASS: Stddev is %fs\n", s.Stddev)
  179. }
  180. if ok {
  181. fmt.Println("PASS")
  182. } else {
  183. fmt.Println("FAIL")
  184. os.Exit(ExitError)
  185. }
  186. }