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.

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