check.go 5.2 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, ccancel := context.WithTimeout(context.Background(), time.Duration(cfg.duration)*time.Second)
  131. defer ccancel()
  132. for limit.Wait(cctx) == nil {
  133. binary.PutVarint(k, int64(rand.Int63n(math.MaxInt64)))
  134. requests <- v3.OpPut(checkPerfPrefix+string(k), v)
  135. }
  136. close(requests)
  137. }()
  138. go func() {
  139. for i := 0; i < cfg.duration; i++ {
  140. time.Sleep(time.Second)
  141. bar.Add(1)
  142. }
  143. bar.Finish()
  144. }()
  145. sc := r.Stats()
  146. wg.Wait()
  147. close(r.Results())
  148. s := <-sc
  149. ctx, cancel = context.WithTimeout(context.Background(), 30*time.Second)
  150. _, err = clients[0].Delete(ctx, checkPerfPrefix, v3.WithPrefix())
  151. cancel()
  152. if err != nil {
  153. ExitWithError(ExitError, err)
  154. }
  155. ok = true
  156. if len(s.ErrorDist) != 0 {
  157. fmt.Println("FAIL: too many errors")
  158. for k, v := range s.ErrorDist {
  159. fmt.Printf("FAIL: ERROR(%v) -> %d\n", k, v)
  160. }
  161. ok = false
  162. }
  163. if s.RPS/float64(cfg.limit) <= 0.9 {
  164. fmt.Printf("FAIL: Throughput too low: %d writes/s\n", int(s.RPS)+1)
  165. ok = false
  166. } else {
  167. fmt.Printf("PASS: Throughput is %d writes/s\n", int(s.RPS)+1)
  168. }
  169. if s.Slowest > 0.5 { // slowest request > 500ms
  170. fmt.Printf("Slowest request took too long: %fs\n", s.Slowest)
  171. ok = false
  172. } else {
  173. fmt.Printf("PASS: Slowest request took %fs\n", s.Slowest)
  174. }
  175. if s.Stddev > 0.1 { // stddev > 100ms
  176. fmt.Printf("Stddev too high: %fs\n", s.Stddev)
  177. ok = false
  178. } else {
  179. fmt.Printf("PASS: Stddev is %fs\n", s.Stddev)
  180. }
  181. if ok {
  182. fmt.Println("PASS")
  183. } else {
  184. fmt.Println("FAIL")
  185. os.Exit(ExitError)
  186. }
  187. }