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.

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