2017-03-16 20:53:09 +04:00
package main
2017-03-13 19:10:51 +04:00
import (
2018-05-03 05:08:19 -04:00
"encoding/json"
2017-03-13 19:10:51 +04:00
"flag"
"fmt"
2018-06-22 16:16:27 -07:00
"math"
2017-03-13 19:10:51 +04:00
"os"
"strings"
2018-06-23 01:57:50 +02:00
"text/tabwriter"
2017-03-13 19:10:51 +04:00
"time"
2017-03-23 16:13:27 +04:00
"github.com/go-kit/kit/log/term"
2017-03-17 00:56:22 +04:00
metrics "github.com/rcrowley/go-metrics"
2018-05-04 16:13:42 +04:00
tmrpc "github.com/tendermint/tendermint/rpc/client"
2018-06-23 01:57:50 +02:00
2017-07-28 18:13:39 -04:00
"github.com/tendermint/tmlibs/log"
2017-03-13 19:10:51 +04:00
)
2018-04-03 13:42:57 +02:00
var version = "0.3.0"
2017-03-13 19:10:51 +04:00
2017-03-23 16:13:27 +04:00
var logger = log . NewNopLogger ( )
2017-03-17 00:56:22 +04:00
type statistics struct {
2018-05-04 16:13:42 +04:00
TxsThroughput metrics . Histogram ` json:"txs_per_sec" `
BlocksThroughput metrics . Histogram ` json:"blocks_per_sec" `
2017-03-17 00:56:22 +04:00
}
2017-03-13 19:10:51 +04:00
func main ( ) {
2018-06-22 18:22:30 -07:00
var duration , txsRate , connections , txSize int
2017-03-23 16:13:27 +04:00
var verbose bool
2018-05-04 16:35:39 +04:00
var outputFormat , broadcastTxMethod string
2017-03-13 19:10:51 +04:00
2017-03-17 13:13:06 +04:00
flag . IntVar ( & connections , "c" , 1 , "Connections to keep open per endpoint" )
2017-03-13 19:10:51 +04:00
flag . IntVar ( & duration , "T" , 10 , "Exit after the specified amount of time in seconds" )
flag . IntVar ( & txsRate , "r" , 1000 , "Txs per second to send in a connection" )
2018-06-22 18:22:30 -07:00
flag . IntVar ( & txSize , "s" , 250 , "The size of a transaction in bytes." )
2018-05-03 05:08:19 -04:00
flag . StringVar ( & outputFormat , "output-format" , "plain" , "Output format: plain or json" )
2018-05-04 16:35:39 +04:00
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)" )
2017-03-23 16:13:27 +04:00
flag . BoolVar ( & verbose , "v" , false , "Verbose output" )
2017-03-13 19:10:51 +04:00
flag . Usage = func ( ) {
2017-03-17 01:12:37 +04:00
fmt . Println ( ` Tendermint blockchain benchmarking tool .
2017-03-13 19:10:51 +04:00
Usage :
2018-05-04 16:35:39 +04:00
tm - bench [ - c 1 ] [ - T 10 ] [ - r 1000 ] [ endpoints ] [ - output - format < plain | json > [ - broadcast - tx - method < async | sync | commit > ] ]
2017-03-13 19:10:51 +04:00
Examples :
2018-06-22 11:55:07 -04:00
tm - bench localhost : 26657 ` )
2017-03-13 19:10:51 +04:00
fmt . Println ( "Flags:" )
flag . PrintDefaults ( )
}
flag . Parse ( )
if flag . NArg ( ) == 0 {
flag . Usage ( )
os . Exit ( 1 )
}
2017-03-23 16:13:27 +04:00
if verbose {
2018-05-03 05:08:19 -04:00
if outputFormat == "json" {
2018-05-04 16:13:42 +04:00
fmt . Fprintln ( os . Stderr , "Verbose mode not supported with json output." )
2018-05-03 05:08:19 -04:00
os . Exit ( 1 )
}
2017-03-23 16:13:27 +04:00
// Color errors red
colorFn := func ( keyvals ... interface { } ) term . FgBgColor {
for i := 1 ; i < len ( keyvals ) ; i += 2 {
if _ , ok := keyvals [ i ] . ( error ) ; ok {
return term . FgBgColor { Fg : term . White , Bg : term . Red }
}
}
return term . FgBgColor { }
}
2017-07-28 18:13:39 -04:00
logger = log . NewTMLoggerWithColorFn ( log . NewSyncWriter ( os . Stdout ) , colorFn )
2017-03-23 16:13:27 +04:00
2018-05-03 05:08:19 -04:00
fmt . Printf ( "Running %ds test @ %s\n" , duration , flag . Arg ( 0 ) )
}
2017-03-13 19:10:51 +04:00
2018-06-23 01:57:50 +02:00
if broadcastTxMethod != "async" &&
broadcastTxMethod != "sync" &&
broadcastTxMethod != "commit" {
fmt . Fprintln (
os . Stderr ,
"broadcast-tx-method should be either 'sync', 'async' or 'commit'." ,
)
2018-05-04 16:35:39 +04:00
os . Exit ( 1 )
}
2018-06-23 01:57:50 +02:00
var (
endpoints = strings . Split ( flag . Arg ( 0 ) , "," )
client = tmrpc . NewHTTP ( endpoints [ 0 ] , "/websocket" )
initialHeight = latestBlockHeight ( client )
)
2018-06-22 16:16:27 -07:00
logger . Info ( "Latest block height" , "h" , initialHeight )
2018-05-04 16:13:42 +04:00
// record time start
timeStart := time . Now ( )
logger . Info ( "Time started" , "t" , timeStart )
2017-03-17 00:56:22 +04:00
2018-06-23 01:57:50 +02:00
transacters := startTransacters (
endpoints ,
connections ,
txsRate ,
2018-06-22 18:22:30 -07:00
txSize ,
2018-06-23 01:57:50 +02:00
"broadcast_tx_" + broadcastTxMethod ,
)
2017-03-17 00:56:22 +04:00
2018-05-04 16:13:42 +04:00
select {
case <- time . After ( time . Duration ( duration ) * time . Second ) :
for _ , t := range transacters {
t . Stop ( )
}
2018-06-23 01:57:50 +02:00
2018-05-04 16:13:42 +04:00
timeStop := time . Now ( )
logger . Info ( "Time stopped" , "t" , timeStop )
2018-06-23 01:57:50 +02:00
stats , err := calculateStatistics (
client ,
initialHeight ,
timeStart ,
timeStop ,
duration ,
)
if err != nil {
fmt . Fprintln ( os . Stderr , err )
os . Exit ( 1 )
}
2018-05-04 16:13:42 +04:00
printStatistics ( stats , outputFormat )
return
}
}
func latestBlockHeight ( client tmrpc . Client ) int64 {
status , err := client . Status ( )
if err != nil {
fmt . Fprintln ( os . Stderr , err )
os . Exit ( 1 )
}
return status . SyncInfo . LatestBlockHeight
}
2018-06-23 01:57:50 +02:00
// calculateStatistics calculates the tx / second, and blocks / second based
// off of the number the transactions and number of blocks that occurred from
// the start block, and the end time.
func calculateStatistics (
client tmrpc . Client ,
minHeight int64 ,
timeStart , timeStop time . Time ,
duration int ,
) ( * statistics , error ) {
2017-03-17 00:56:22 +04:00
stats := & statistics {
2018-05-04 16:13:42 +04:00
BlocksThroughput : metrics . NewHistogram ( metrics . NewUniformSample ( 1000 ) ) ,
TxsThroughput : metrics . NewHistogram ( metrics . NewUniformSample ( 1000 ) ) ,
2017-03-13 19:10:51 +04:00
}
2018-05-04 16:13:42 +04:00
// get blocks between minHeight and last height
2018-06-22 16:16:27 -07:00
// This returns max(minHeight,(last_height - 20)) to last_height
2018-05-04 16:13:42 +04:00
info , err := client . BlockchainInfo ( minHeight , 0 )
if err != nil {
2018-06-23 01:57:50 +02:00
return nil , err
2018-05-04 16:13:42 +04:00
}
2018-06-23 01:57:50 +02:00
var (
blockMetas = info . BlockMetas
lastHeight = info . LastHeight
diff = lastHeight - minHeight
offset = len ( blockMetas )
)
2018-06-04 12:37:37 -07:00
2018-06-23 01:57:50 +02:00
for offset < int ( diff ) {
2018-06-04 12:37:37 -07:00
// get blocks between minHeight and last height
2018-06-22 16:16:27 -07:00
info , err := client . BlockchainInfo ( minHeight , lastHeight - int64 ( offset ) )
2018-06-04 12:37:37 -07:00
if err != nil {
2018-06-23 01:57:50 +02:00
return nil , err
2018-06-04 12:37:37 -07:00
}
blockMetas = append ( blockMetas , info . BlockMetas ... )
offset = len ( blockMetas )
}
2018-06-23 01:57:50 +02:00
var (
numBlocksPerSec = make ( map [ int64 ] int64 )
numTxsPerSec = make ( map [ int64 ] int64 )
)
2018-05-10 17:08:49 +04:00
// because during some seconds blocks won't be created...
for i := int64 ( 0 ) ; i < int64 ( duration ) ; i ++ {
numBlocksPerSec [ i ] = 0
numTxsPerSec [ i ] = 0
}
2018-06-22 16:16:27 -07:00
// iterates from max height to min height
2018-06-04 12:37:37 -07:00
for _ , blockMeta := range blockMetas {
2018-05-08 16:23:00 +04:00
// check if block was created after timeStart
if blockMeta . Header . Time . Before ( timeStart ) {
2018-06-22 16:16:27 -07:00
break
2018-05-08 16:23:00 +04:00
}
2018-05-04 16:13:42 +04:00
// check if block was created before timeStop
if blockMeta . Header . Time . After ( timeStop ) {
2018-06-22 16:16:27 -07:00
continue
2018-05-04 16:13:42 +04:00
}
sec := secondsSinceTimeStart ( timeStart , blockMeta . Header . Time )
2017-03-17 00:56:22 +04:00
2018-05-04 16:13:42 +04:00
// increase number of blocks for that second
numBlocksPerSec [ sec ] ++
// increase number of txs for that second
numTxsPerSec [ sec ] += blockMeta . Header . NumTxs
2017-03-13 19:10:51 +04:00
}
2018-05-04 16:13:42 +04:00
for _ , n := range numBlocksPerSec {
stats . BlocksThroughput . Update ( n )
}
2017-03-13 19:10:51 +04:00
2018-05-04 16:13:42 +04:00
for _ , n := range numTxsPerSec {
stats . TxsThroughput . Update ( n )
2017-03-13 19:10:51 +04:00
}
2018-06-23 01:57:50 +02:00
return stats , nil
2018-05-04 16:13:42 +04:00
}
func secondsSinceTimeStart ( timeStart , timePassed time . Time ) int64 {
2018-06-22 16:16:27 -07:00
return int64 ( math . Round ( timePassed . Sub ( timeStart ) . Seconds ( ) ) )
2017-03-17 00:56:22 +04:00
}
2018-06-23 01:57:50 +02:00
func startTransacters (
endpoints [ ] string ,
connections ,
txsRate int ,
2018-06-22 18:22:30 -07:00
txSize int ,
2018-06-23 01:57:50 +02:00
broadcastTxMethod string ,
) [ ] * transacter {
2017-03-17 00:56:22 +04:00
transacters := make ( [ ] * transacter , len ( endpoints ) )
for i , e := range endpoints {
2018-06-22 18:22:30 -07:00
t := newTransacter ( e , connections , txsRate , txSize , broadcastTxMethod )
2017-03-23 16:13:27 +04:00
t . SetLogger ( logger )
2017-03-17 00:56:22 +04:00
if err := t . Start ( ) ; err != nil {
2018-05-04 16:13:42 +04:00
fmt . Fprintln ( os . Stderr , err )
2017-03-23 16:13:27 +04:00
os . Exit ( 1 )
2017-03-17 00:56:22 +04:00
}
transacters [ i ] = t
2017-03-13 19:10:51 +04:00
}
2017-03-17 00:56:22 +04:00
return transacters
2017-03-13 19:10:51 +04:00
}
2018-05-03 05:08:19 -04:00
func printStatistics ( stats * statistics , outputFormat string ) {
if outputFormat == "json" {
2018-05-04 16:13:42 +04:00
result , err := json . Marshal ( struct {
TxsThroughput float64 ` json:"txs_per_sec_avg" `
BlocksThroughput float64 ` json:"blocks_per_sec_avg" `
} { stats . TxsThroughput . Mean ( ) , stats . BlocksThroughput . Mean ( ) } )
if err != nil {
fmt . Fprintln ( os . Stderr , err )
os . Exit ( 1 )
}
2018-05-03 05:08:19 -04:00
fmt . Println ( string ( result ) )
} else {
w := tabwriter . NewWriter ( os . Stdout , 0 , 0 , 5 , ' ' , 0 )
2018-05-04 16:13:42 +04:00
fmt . Fprintln ( w , "Stats\tAvg\tStdDev\tMax\t" )
2018-05-03 05:08:19 -04:00
fmt . Fprintln ( w , fmt . Sprintf ( "Txs/sec\t%.0f\t%.0f\t%d\t" ,
2018-05-04 16:13:42 +04:00
stats . TxsThroughput . Mean ( ) ,
stats . TxsThroughput . StdDev ( ) ,
stats . TxsThroughput . Max ( ) ) )
fmt . Fprintln ( w , fmt . Sprintf ( "Blocks/sec\t%.3f\t%.3f\t%d\t" ,
stats . BlocksThroughput . Mean ( ) ,
stats . BlocksThroughput . StdDev ( ) ,
stats . BlocksThroughput . Max ( ) ) )
2018-05-03 05:08:19 -04:00
w . Flush ( )
}
2017-03-13 19:10:51 +04:00
}