You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

187 lines
4.6 KiB

  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. "os"
  6. "strings"
  7. "sync"
  8. "time"
  9. "github.com/go-kit/kit/log/term"
  10. cmn "github.com/tendermint/tendermint/libs/common"
  11. "github.com/tendermint/tendermint/libs/log"
  12. "github.com/tendermint/tendermint/rpc/client"
  13. tmrpc "github.com/tendermint/tendermint/rpc/client"
  14. )
  15. var logger = log.NewNopLogger()
  16. func main() {
  17. var durationInt, txsRate, connections, txSize int
  18. var verbose bool
  19. var outputFormat, broadcastTxMethod string
  20. flagSet := flag.NewFlagSet("tm-bench", flag.ExitOnError)
  21. flagSet.IntVar(&connections, "c", 1, "Connections to keep open per endpoint")
  22. flagSet.IntVar(&durationInt, "T", 10, "Exit after the specified amount of time in seconds")
  23. flagSet.IntVar(&txsRate, "r", 1000, "Txs per second to send in a connection")
  24. flagSet.IntVar(&txSize, "s", 250, "The size of a transaction in bytes, must be greater than or equal to 40.")
  25. flagSet.StringVar(&outputFormat, "output-format", "plain", "Output format: plain or json")
  26. flagSet.StringVar(&broadcastTxMethod, "broadcast-tx-method", "async", "Broadcast method: async (no guarantees; fastest), sync (ensures tx is checked) or commit (ensures tx is checked and committed; slowest)")
  27. flagSet.BoolVar(&verbose, "v", false, "Verbose output")
  28. flagSet.Usage = func() {
  29. fmt.Println(`Tendermint blockchain benchmarking tool.
  30. Usage:
  31. tm-bench [-c 1] [-T 10] [-r 1000] [-s 250] [endpoints] [-output-format <plain|json> [-broadcast-tx-method <async|sync|commit>]]
  32. Examples:
  33. tm-bench localhost:26657`)
  34. fmt.Println("Flags:")
  35. flagSet.PrintDefaults()
  36. }
  37. flagSet.Parse(os.Args[1:])
  38. if flagSet.NArg() == 0 {
  39. flagSet.Usage()
  40. os.Exit(1)
  41. }
  42. if verbose {
  43. if outputFormat == "json" {
  44. printErrorAndExit("Verbose mode not supported with json output.")
  45. }
  46. // Color errors red
  47. colorFn := func(keyvals ...interface{}) term.FgBgColor {
  48. for i := 1; i < len(keyvals); i += 2 {
  49. if _, ok := keyvals[i].(error); ok {
  50. return term.FgBgColor{Fg: term.White, Bg: term.Red}
  51. }
  52. }
  53. return term.FgBgColor{}
  54. }
  55. logger = log.NewTMLoggerWithColorFn(log.NewSyncWriter(os.Stdout), colorFn)
  56. fmt.Printf("Running %ds test @ %s\n", durationInt, flagSet.Arg(0))
  57. }
  58. if txSize < 40 {
  59. printErrorAndExit("The size of a transaction must be greater than or equal to 40.")
  60. }
  61. if broadcastTxMethod != "async" &&
  62. broadcastTxMethod != "sync" &&
  63. broadcastTxMethod != "commit" {
  64. printErrorAndExit("broadcast-tx-method should be either 'sync', 'async' or 'commit'.")
  65. }
  66. var (
  67. endpoints = strings.Split(flagSet.Arg(0), ",")
  68. client = tmrpc.NewHTTP(endpoints[0], "/websocket")
  69. initialHeight = latestBlockHeight(client)
  70. )
  71. logger.Info("Latest block height", "h", initialHeight)
  72. transacters := startTransacters(
  73. endpoints,
  74. connections,
  75. txsRate,
  76. txSize,
  77. "broadcast_tx_"+broadcastTxMethod,
  78. )
  79. // Stop upon receiving SIGTERM or CTRL-C.
  80. cmn.TrapSignal(logger, func() {
  81. for _, t := range transacters {
  82. t.Stop()
  83. }
  84. })
  85. // Wait until transacters have begun until we get the start time.
  86. timeStart := time.Now()
  87. logger.Info("Time last transacter started", "t", timeStart)
  88. duration := time.Duration(durationInt) * time.Second
  89. timeEnd := timeStart.Add(duration)
  90. logger.Info("End time for calculation", "t", timeEnd)
  91. <-time.After(duration)
  92. for i, t := range transacters {
  93. t.Stop()
  94. numCrashes := countCrashes(t.connsBroken)
  95. if numCrashes != 0 {
  96. fmt.Printf("%d connections crashed on transacter #%d\n", numCrashes, i)
  97. }
  98. }
  99. logger.Debug("Time all transacters stopped", "t", time.Now())
  100. stats, err := calculateStatistics(
  101. client,
  102. initialHeight,
  103. timeStart,
  104. durationInt,
  105. )
  106. if err != nil {
  107. printErrorAndExit(err.Error())
  108. }
  109. printStatistics(stats, outputFormat)
  110. }
  111. func latestBlockHeight(client client.StatusClient) int64 {
  112. status, err := client.Status()
  113. if err != nil {
  114. fmt.Fprintln(os.Stderr, err)
  115. os.Exit(1)
  116. }
  117. return status.SyncInfo.LatestBlockHeight
  118. }
  119. func countCrashes(crashes []bool) int {
  120. count := 0
  121. for i := 0; i < len(crashes); i++ {
  122. if crashes[i] {
  123. count++
  124. }
  125. }
  126. return count
  127. }
  128. func startTransacters(
  129. endpoints []string,
  130. connections,
  131. txsRate int,
  132. txSize int,
  133. broadcastTxMethod string,
  134. ) []*transacter {
  135. transacters := make([]*transacter, len(endpoints))
  136. wg := sync.WaitGroup{}
  137. wg.Add(len(endpoints))
  138. for i, e := range endpoints {
  139. t := newTransacter(e, connections, txsRate, txSize, broadcastTxMethod)
  140. t.SetLogger(logger)
  141. go func(i int) {
  142. defer wg.Done()
  143. if err := t.Start(); err != nil {
  144. fmt.Fprintln(os.Stderr, err)
  145. os.Exit(1)
  146. }
  147. transacters[i] = t
  148. }(i)
  149. }
  150. wg.Wait()
  151. return transacters
  152. }
  153. func printErrorAndExit(err string) {
  154. fmt.Fprintln(os.Stderr, err)
  155. os.Exit(1)
  156. }