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.

193 lines
4.7 KiB

  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. "os"
  6. "os/signal"
  7. "strings"
  8. "sync"
  9. "syscall"
  10. "time"
  11. "github.com/go-kit/kit/log/term"
  12. "github.com/tendermint/tendermint/libs/log"
  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. // Quit when interrupted or received SIGTERM.
  80. c := make(chan os.Signal, 1)
  81. signal.Notify(c, os.Interrupt, syscall.SIGTERM)
  82. go func() {
  83. for sig := range c {
  84. fmt.Printf("captured %v, exiting...\n", sig)
  85. for _, t := range transacters {
  86. t.Stop()
  87. }
  88. os.Exit(1)
  89. }
  90. }()
  91. // Wait until transacters have begun until we get the start time.
  92. timeStart := time.Now()
  93. logger.Info("Time last transacter started", "t", timeStart)
  94. duration := time.Duration(durationInt) * time.Second
  95. timeEnd := timeStart.Add(duration)
  96. logger.Info("End time for calculation", "t", timeEnd)
  97. <-time.After(duration)
  98. for i, t := range transacters {
  99. t.Stop()
  100. numCrashes := countCrashes(t.connsBroken)
  101. if numCrashes != 0 {
  102. fmt.Printf("%d connections crashed on transacter #%d\n", numCrashes, i)
  103. }
  104. }
  105. logger.Debug("Time all transacters stopped", "t", time.Now())
  106. stats, err := calculateStatistics(
  107. client,
  108. initialHeight,
  109. timeStart,
  110. durationInt,
  111. )
  112. if err != nil {
  113. printErrorAndExit(err.Error())
  114. }
  115. printStatistics(stats, outputFormat)
  116. }
  117. func latestBlockHeight(client tmrpc.Client) int64 {
  118. status, err := client.Status()
  119. if err != nil {
  120. fmt.Fprintln(os.Stderr, err)
  121. os.Exit(1)
  122. }
  123. return status.SyncInfo.LatestBlockHeight
  124. }
  125. func countCrashes(crashes []bool) int {
  126. count := 0
  127. for i := 0; i < len(crashes); i++ {
  128. if crashes[i] {
  129. count++
  130. }
  131. }
  132. return count
  133. }
  134. func startTransacters(
  135. endpoints []string,
  136. connections,
  137. txsRate int,
  138. txSize int,
  139. broadcastTxMethod string,
  140. ) []*transacter {
  141. transacters := make([]*transacter, len(endpoints))
  142. wg := sync.WaitGroup{}
  143. wg.Add(len(endpoints))
  144. for i, e := range endpoints {
  145. t := newTransacter(e, connections, txsRate, txSize, broadcastTxMethod)
  146. t.SetLogger(logger)
  147. go func(i int) {
  148. defer wg.Done()
  149. if err := t.Start(); err != nil {
  150. fmt.Fprintln(os.Stderr, err)
  151. os.Exit(1)
  152. }
  153. transacters[i] = t
  154. }(i)
  155. }
  156. wg.Wait()
  157. return transacters
  158. }
  159. func printErrorAndExit(err string) {
  160. fmt.Fprintln(os.Stderr, err)
  161. os.Exit(1)
  162. }