check.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  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. cc := clientConfigFromCmd(cmd)
  99. clients := make([]*v3.Client, cfg.clients)
  100. for i := 0; i < cfg.clients; i++ {
  101. clients[i] = cc.mustClient()
  102. }
  103. ctx, cancel := context.WithTimeout(context.Background(), time.Duration(cfg.duration)*time.Second)
  104. resp, err := clients[0].Get(ctx, checkPerfPrefix, v3.WithPrefix(), v3.WithLimit(1))
  105. cancel()
  106. if err != nil {
  107. ExitWithError(ExitError, err)
  108. }
  109. if len(resp.Kvs) > 0 {
  110. ExitWithError(ExitInvalidInput, fmt.Errorf("prefix %q has keys. Delete with etcdctl del --prefix %s first.", checkPerfPrefix, checkPerfPrefix))
  111. }
  112. ksize, vsize := 256, 1024
  113. k, v := make([]byte, ksize), string(make([]byte, vsize))
  114. bar := pb.New(cfg.duration)
  115. bar.Format("Bom !")
  116. bar.Start()
  117. r := report.NewReport("%4.4f")
  118. var wg sync.WaitGroup
  119. wg.Add(len(clients))
  120. for i := range clients {
  121. go func(c *v3.Client) {
  122. defer wg.Done()
  123. for op := range requests {
  124. st := time.Now()
  125. _, derr := c.Do(context.Background(), op)
  126. r.Results() <- report.Result{Err: derr, Start: st, End: time.Now()}
  127. }
  128. }(clients[i])
  129. }
  130. go func() {
  131. cctx, ccancel := context.WithTimeout(context.Background(), time.Duration(cfg.duration)*time.Second)
  132. defer ccancel()
  133. for limit.Wait(cctx) == nil {
  134. binary.PutVarint(k, int64(rand.Int63n(math.MaxInt64)))
  135. requests <- v3.OpPut(checkPerfPrefix+string(k), v)
  136. }
  137. close(requests)
  138. }()
  139. go func() {
  140. for i := 0; i < cfg.duration; i++ {
  141. time.Sleep(time.Second)
  142. bar.Add(1)
  143. }
  144. bar.Finish()
  145. }()
  146. sc := r.Stats()
  147. wg.Wait()
  148. close(r.Results())
  149. s := <-sc
  150. ctx, cancel = context.WithTimeout(context.Background(), 30*time.Second)
  151. _, err = clients[0].Delete(ctx, checkPerfPrefix, v3.WithPrefix())
  152. cancel()
  153. if err != nil {
  154. ExitWithError(ExitError, err)
  155. }
  156. ok = true
  157. if len(s.ErrorDist) != 0 {
  158. fmt.Println("FAIL: too many errors")
  159. for k, v := range s.ErrorDist {
  160. fmt.Printf("FAIL: ERROR(%v) -> %d\n", k, v)
  161. }
  162. ok = false
  163. }
  164. if s.RPS/float64(cfg.limit) <= 0.9 {
  165. fmt.Printf("FAIL: Throughput too low: %d writes/s\n", int(s.RPS)+1)
  166. ok = false
  167. } else {
  168. fmt.Printf("PASS: Throughput is %d writes/s\n", int(s.RPS)+1)
  169. }
  170. if s.Slowest > 0.5 { // slowest request > 500ms
  171. fmt.Printf("Slowest request took too long: %fs\n", s.Slowest)
  172. ok = false
  173. } else {
  174. fmt.Printf("PASS: Slowest request took %fs\n", s.Slowest)
  175. }
  176. if s.Stddev > 0.1 { // stddev > 100ms
  177. fmt.Printf("Stddev too high: %fs\n", s.Stddev)
  178. ok = false
  179. } else {
  180. fmt.Printf("PASS: Stddev is %fs\n", s.Stddev)
  181. }
  182. if ok {
  183. fmt.Println("PASS")
  184. } else {
  185. fmt.Println("FAIL")
  186. os.Exit(ExitError)
  187. }
  188. }