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.

184 lines
4.4 KiB

  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. "os"
  6. "strings"
  7. "text/tabwriter"
  8. "time"
  9. "github.com/go-kit/kit/log/term"
  10. metrics "github.com/rcrowley/go-metrics"
  11. tmtypes "github.com/tendermint/tendermint/types"
  12. "github.com/tendermint/tmlibs/log"
  13. "github.com/tendermint/tools/tm-monitor/monitor"
  14. "math/rand"
  15. "crypto/md5"
  16. )
  17. var version = "0.1.0"
  18. var logger = log.NewNopLogger()
  19. type statistics struct {
  20. BlockTimeSample metrics.Histogram
  21. TxThroughputSample metrics.Histogram
  22. BlockLatency metrics.Histogram
  23. }
  24. func main() {
  25. var duration, txsRate, connections int
  26. var verbose bool
  27. flag.IntVar(&connections, "c", 1, "Connections to keep open per endpoint")
  28. flag.IntVar(&duration, "T", 10, "Exit after the specified amount of time in seconds")
  29. flag.IntVar(&txsRate, "r", 1000, "Txs per second to send in a connection")
  30. flag.BoolVar(&verbose, "v", false, "Verbose output")
  31. flag.Usage = func() {
  32. fmt.Println(`Tendermint blockchain benchmarking tool.
  33. Usage:
  34. tm-bench [-c 1] [-T 10] [-r 1000] [endpoints]
  35. Examples:
  36. tm-bench localhost:46657`)
  37. fmt.Println("Flags:")
  38. flag.PrintDefaults()
  39. }
  40. flag.Parse()
  41. if flag.NArg() == 0 {
  42. flag.Usage()
  43. os.Exit(1)
  44. }
  45. if verbose {
  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. }
  57. fmt.Printf("Running %ds test @ %s\n", duration, flag.Arg(0))
  58. endpoints := strings.Split(flag.Arg(0), ",")
  59. blockCh := make(chan tmtypes.Header, 100)
  60. blockLatencyCh := make(chan float64, 100)
  61. rand.Seed(time.Now().Unix())
  62. nodes := startNodes(endpoints, blockCh, blockLatencyCh)
  63. transacters := startTransacters(endpoints, connections, txsRate)
  64. stats := &statistics{
  65. BlockTimeSample: metrics.NewHistogram(metrics.NewUniformSample(1000)),
  66. TxThroughputSample: metrics.NewHistogram(metrics.NewUniformSample(1000)),
  67. BlockLatency: metrics.NewHistogram(metrics.NewUniformSample(1000)),
  68. }
  69. lastBlockHeight := -1
  70. durationTimer := time.After(time.Duration(duration) * time.Second)
  71. ticker := time.NewTicker(1 * time.Second)
  72. var blocks, txs int
  73. for {
  74. select {
  75. case b := <-blockCh:
  76. if lastBlockHeight < b.Height {
  77. blocks++
  78. txs += b.NumTxs
  79. lastBlockHeight = b.Height
  80. }
  81. case l := <-blockLatencyCh:
  82. stats.BlockLatency.Update(int64(l))
  83. case <-ticker.C:
  84. stats.BlockTimeSample.Update(int64(blocks))
  85. stats.TxThroughputSample.Update(int64(txs))
  86. blocks = 0
  87. txs = 0
  88. case <-durationTimer:
  89. for _, t := range transacters {
  90. t.Stop()
  91. }
  92. printStatistics(stats)
  93. for _, n := range nodes {
  94. n.Stop()
  95. }
  96. return
  97. }
  98. }
  99. }
  100. func startNodes(endpoints []string, blockCh chan<- tmtypes.Header, blockLatencyCh chan<- float64) []*monitor.Node {
  101. nodes := make([]*monitor.Node, len(endpoints))
  102. for i, e := range endpoints {
  103. n := monitor.NewNode(e)
  104. n.SetLogger(logger.With("node", e))
  105. n.SendBlocksTo(blockCh)
  106. n.SendBlockLatenciesTo(blockLatencyCh)
  107. if err := n.Start(); err != nil {
  108. fmt.Println(err)
  109. os.Exit(1)
  110. }
  111. nodes[i] = n
  112. }
  113. return nodes
  114. }
  115. func startTransacters(endpoints []string, connections int, txsRate int) []*transacter {
  116. var hostHash [16]byte
  117. if hostName , err := os.Hostname(); err != nil {
  118. hostHash = md5.Sum([]byte("127.0.0.1"))
  119. } else {
  120. hostHash = md5.Sum([]byte(hostName))
  121. }
  122. transacters := make([]*transacter, len(endpoints))
  123. for i, e := range endpoints {
  124. t := newTransacter(e, connections, txsRate, hostHash)
  125. t.SetLogger(logger)
  126. if err := t.Start(); err != nil {
  127. fmt.Println(err)
  128. os.Exit(1)
  129. }
  130. transacters[i] = t
  131. }
  132. return transacters
  133. }
  134. func printStatistics(stats *statistics) {
  135. w := tabwriter.NewWriter(os.Stdout, 0, 0, 5, ' ', 0)
  136. fmt.Fprintln(w, "Stats\tAvg\tStdev\tMax\t")
  137. fmt.Fprintln(w, fmt.Sprintf("Block latency\t%.2fms\t%.2fms\t%dms\t",
  138. stats.BlockLatency.Mean()/1000000.0,
  139. stats.BlockLatency.StdDev()/1000000.0,
  140. stats.BlockLatency.Max()/1000000))
  141. fmt.Fprintln(w, fmt.Sprintf("Blocks/sec\t%.3f\t%.3f\t%d\t",
  142. stats.BlockTimeSample.Mean(),
  143. stats.BlockTimeSample.StdDev(),
  144. stats.BlockTimeSample.Max()))
  145. fmt.Fprintln(w, fmt.Sprintf("Txs/sec\t%.0f\t%.0f\t%d\t",
  146. stats.TxThroughputSample.Mean(),
  147. stats.TxThroughputSample.StdDev(),
  148. stats.TxThroughputSample.Max()))
  149. w.Flush()
  150. }