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.

286 lines
6.9 KiB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
  1. package main
  2. import (
  3. "encoding/json"
  4. "flag"
  5. "fmt"
  6. "math"
  7. "os"
  8. "strings"
  9. "text/tabwriter"
  10. "time"
  11. "github.com/go-kit/kit/log/term"
  12. metrics "github.com/rcrowley/go-metrics"
  13. tmrpc "github.com/tendermint/tendermint/rpc/client"
  14. "github.com/tendermint/tmlibs/log"
  15. )
  16. var version = "0.3.0"
  17. var logger = log.NewNopLogger()
  18. type statistics struct {
  19. TxsThroughput metrics.Histogram `json:"txs_per_sec"`
  20. BlocksThroughput metrics.Histogram `json:"blocks_per_sec"`
  21. }
  22. func main() {
  23. var duration, txsRate, connections, txSize int
  24. var verbose bool
  25. var outputFormat, broadcastTxMethod string
  26. flag.IntVar(&connections, "c", 1, "Connections to keep open per endpoint")
  27. flag.IntVar(&duration, "T", 10, "Exit after the specified amount of time in seconds")
  28. flag.IntVar(&txsRate, "r", 1000, "Txs per second to send in a connection")
  29. flag.IntVar(&txSize, "s", 250, "The size of a transaction in bytes.")
  30. flag.StringVar(&outputFormat, "output-format", "plain", "Output format: plain or json")
  31. flag.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)")
  32. flag.BoolVar(&verbose, "v", false, "Verbose output")
  33. flag.Usage = func() {
  34. fmt.Println(`Tendermint blockchain benchmarking tool.
  35. Usage:
  36. tm-bench [-c 1] [-T 10] [-r 1000] [endpoints] [-output-format <plain|json> [-broadcast-tx-method <async|sync|commit>]]
  37. Examples:
  38. tm-bench localhost:26657`)
  39. fmt.Println("Flags:")
  40. flag.PrintDefaults()
  41. }
  42. flag.Parse()
  43. if flag.NArg() == 0 {
  44. flag.Usage()
  45. os.Exit(1)
  46. }
  47. if verbose {
  48. if outputFormat == "json" {
  49. fmt.Fprintln(os.Stderr, "Verbose mode not supported with json output.")
  50. os.Exit(1)
  51. }
  52. // Color errors red
  53. colorFn := func(keyvals ...interface{}) term.FgBgColor {
  54. for i := 1; i < len(keyvals); i += 2 {
  55. if _, ok := keyvals[i].(error); ok {
  56. return term.FgBgColor{Fg: term.White, Bg: term.Red}
  57. }
  58. }
  59. return term.FgBgColor{}
  60. }
  61. logger = log.NewTMLoggerWithColorFn(log.NewSyncWriter(os.Stdout), colorFn)
  62. fmt.Printf("Running %ds test @ %s\n", duration, flag.Arg(0))
  63. }
  64. if broadcastTxMethod != "async" &&
  65. broadcastTxMethod != "sync" &&
  66. broadcastTxMethod != "commit" {
  67. fmt.Fprintln(
  68. os.Stderr,
  69. "broadcast-tx-method should be either 'sync', 'async' or 'commit'.",
  70. )
  71. os.Exit(1)
  72. }
  73. var (
  74. endpoints = strings.Split(flag.Arg(0), ",")
  75. client = tmrpc.NewHTTP(endpoints[0], "/websocket")
  76. initialHeight = latestBlockHeight(client)
  77. )
  78. logger.Info("Latest block height", "h", initialHeight)
  79. // record time start
  80. timeStart := time.Now()
  81. logger.Info("Time started", "t", timeStart)
  82. transacters := startTransacters(
  83. endpoints,
  84. connections,
  85. txsRate,
  86. txSize,
  87. "broadcast_tx_"+broadcastTxMethod,
  88. )
  89. select {
  90. case <-time.After(time.Duration(duration) * time.Second):
  91. for _, t := range transacters {
  92. t.Stop()
  93. }
  94. timeStop := time.Now()
  95. logger.Info("Time stopped", "t", timeStop)
  96. stats, err := calculateStatistics(
  97. client,
  98. initialHeight,
  99. timeStart,
  100. timeStop,
  101. duration,
  102. )
  103. if err != nil {
  104. fmt.Fprintln(os.Stderr, err)
  105. os.Exit(1)
  106. }
  107. printStatistics(stats, outputFormat)
  108. return
  109. }
  110. }
  111. func latestBlockHeight(client tmrpc.Client) 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. // calculateStatistics calculates the tx / second, and blocks / second based
  120. // off of the number the transactions and number of blocks that occurred from
  121. // the start block, and the end time.
  122. func calculateStatistics(
  123. client tmrpc.Client,
  124. minHeight int64,
  125. timeStart, timeStop time.Time,
  126. duration int,
  127. ) (*statistics, error) {
  128. stats := &statistics{
  129. BlocksThroughput: metrics.NewHistogram(metrics.NewUniformSample(1000)),
  130. TxsThroughput: metrics.NewHistogram(metrics.NewUniformSample(1000)),
  131. }
  132. // get blocks between minHeight and last height
  133. // This returns max(minHeight,(last_height - 20)) to last_height
  134. info, err := client.BlockchainInfo(minHeight, 0)
  135. if err != nil {
  136. return nil, err
  137. }
  138. var (
  139. blockMetas = info.BlockMetas
  140. lastHeight = info.LastHeight
  141. diff = lastHeight - minHeight
  142. offset = len(blockMetas)
  143. )
  144. for offset < int(diff) {
  145. // get blocks between minHeight and last height
  146. info, err := client.BlockchainInfo(minHeight, lastHeight-int64(offset))
  147. if err != nil {
  148. return nil, err
  149. }
  150. blockMetas = append(blockMetas, info.BlockMetas...)
  151. offset = len(blockMetas)
  152. }
  153. var (
  154. numBlocksPerSec = make(map[int64]int64)
  155. numTxsPerSec = make(map[int64]int64)
  156. )
  157. // because during some seconds blocks won't be created...
  158. for i := int64(0); i < int64(duration); i++ {
  159. numBlocksPerSec[i] = 0
  160. numTxsPerSec[i] = 0
  161. }
  162. // iterates from max height to min height
  163. for _, blockMeta := range blockMetas {
  164. // check if block was created after timeStart
  165. if blockMeta.Header.Time.Before(timeStart) {
  166. break
  167. }
  168. // check if block was created before timeStop
  169. if blockMeta.Header.Time.After(timeStop) {
  170. continue
  171. }
  172. sec := secondsSinceTimeStart(timeStart, blockMeta.Header.Time)
  173. // increase number of blocks for that second
  174. numBlocksPerSec[sec]++
  175. // increase number of txs for that second
  176. numTxsPerSec[sec] += blockMeta.Header.NumTxs
  177. }
  178. for _, n := range numBlocksPerSec {
  179. stats.BlocksThroughput.Update(n)
  180. }
  181. for _, n := range numTxsPerSec {
  182. stats.TxsThroughput.Update(n)
  183. }
  184. return stats, nil
  185. }
  186. func secondsSinceTimeStart(timeStart, timePassed time.Time) int64 {
  187. return int64(math.Round(timePassed.Sub(timeStart).Seconds()))
  188. }
  189. func startTransacters(
  190. endpoints []string,
  191. connections,
  192. txsRate int,
  193. txSize int,
  194. broadcastTxMethod string,
  195. ) []*transacter {
  196. transacters := make([]*transacter, len(endpoints))
  197. for i, e := range endpoints {
  198. t := newTransacter(e, connections, txsRate, txSize, broadcastTxMethod)
  199. t.SetLogger(logger)
  200. if err := t.Start(); err != nil {
  201. fmt.Fprintln(os.Stderr, err)
  202. os.Exit(1)
  203. }
  204. transacters[i] = t
  205. }
  206. return transacters
  207. }
  208. func printStatistics(stats *statistics, outputFormat string) {
  209. if outputFormat == "json" {
  210. result, err := json.Marshal(struct {
  211. TxsThroughput float64 `json:"txs_per_sec_avg"`
  212. BlocksThroughput float64 `json:"blocks_per_sec_avg"`
  213. }{stats.TxsThroughput.Mean(), stats.BlocksThroughput.Mean()})
  214. if err != nil {
  215. fmt.Fprintln(os.Stderr, err)
  216. os.Exit(1)
  217. }
  218. fmt.Println(string(result))
  219. } else {
  220. w := tabwriter.NewWriter(os.Stdout, 0, 0, 5, ' ', 0)
  221. fmt.Fprintln(w, "Stats\tAvg\tStdDev\tMax\tTotal\t")
  222. fmt.Fprintln(
  223. w,
  224. fmt.Sprintf(
  225. "Txs/sec\t%.0f\t%.0f\t%d\t%d\t",
  226. stats.TxsThroughput.Mean(),
  227. stats.TxsThroughput.StdDev(),
  228. stats.TxsThroughput.Max(),
  229. stats.TxsThroughput.Sum(),
  230. ),
  231. )
  232. fmt.Fprintln(
  233. w,
  234. fmt.Sprintf("Blocks/sec\t%.3f\t%.3f\t%d\t%d\t",
  235. stats.BlocksThroughput.Mean(),
  236. stats.BlocksThroughput.StdDev(),
  237. stats.BlocksThroughput.Max(),
  238. stats.BlocksThroughput.Sum(),
  239. ),
  240. )
  241. w.Flush()
  242. }
  243. }