package main import ( "encoding/json" "fmt" "math" "os" "text/tabwriter" "time" metrics "github.com/rcrowley/go-metrics" tmrpc "github.com/tendermint/tendermint/rpc/client" "github.com/tendermint/tendermint/types" ) type statistics struct { TxsThroughput metrics.Histogram `json:"txs_per_sec"` BlocksThroughput metrics.Histogram `json:"blocks_per_sec"` } // 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 time.Time, duration int, ) (*statistics, error) { timeEnd := timeStart.Add(time.Duration(duration) * time.Second) stats := &statistics{ BlocksThroughput: metrics.NewHistogram(metrics.NewUniformSample(1000)), TxsThroughput: metrics.NewHistogram(metrics.NewUniformSample(1000)), } var ( numBlocksPerSec = make(map[int64]int64) numTxsPerSec = make(map[int64]int64) ) // because during some seconds blocks won't be created... for i := int64(0); i < int64(duration); i++ { numBlocksPerSec[i] = 0 numTxsPerSec[i] = 0 } blockMetas, err := getBlockMetas(client, minHeight, timeStart, timeEnd) if err != nil { return nil, err } // iterates from max height to min height for _, blockMeta := range blockMetas { // check if block was created after timeStart if blockMeta.Header.Time.Before(timeStart) { break } // check if block was created before timeEnd if blockMeta.Header.Time.After(timeEnd) { continue } sec := secondsSinceTimeStart(timeStart, blockMeta.Header.Time) // increase number of blocks for that second numBlocksPerSec[sec]++ // increase number of txs for that second numTxsPerSec[sec] += blockMeta.Header.NumTxs logger.Debug(fmt.Sprintf("%d txs at block height %d", blockMeta.Header.NumTxs, blockMeta.Header.Height)) } for i := int64(0); i < int64(duration); i++ { stats.BlocksThroughput.Update(numBlocksPerSec[i]) stats.TxsThroughput.Update(numTxsPerSec[i]) } return stats, nil } func getBlockMetas(client tmrpc.Client, minHeight int64, timeStart, timeEnd time.Time) ([]*types.BlockMeta, error) { // get blocks between minHeight and last height // This returns max(minHeight,(last_height - 20)) to last_height info, err := client.BlockchainInfo(minHeight, 0) if err != nil { return nil, err } var ( blockMetas = info.BlockMetas lastHeight = info.LastHeight diff = lastHeight - minHeight offset = len(blockMetas) ) for offset < int(diff) { // get blocks between minHeight and last height info, err := client.BlockchainInfo(minHeight, lastHeight-int64(offset)) if err != nil { return nil, err } blockMetas = append(blockMetas, info.BlockMetas...) offset = len(blockMetas) } return blockMetas, nil } func secondsSinceTimeStart(timeStart, timePassed time.Time) int64 { return int64(math.Round(timePassed.Sub(timeStart).Seconds())) } func printStatistics(stats *statistics, outputFormat string) { if outputFormat == "json" { 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) } fmt.Println(string(result)) } else { w := tabwriter.NewWriter(os.Stdout, 0, 0, 5, ' ', 0) fmt.Fprintln(w, "Stats\tAvg\tStdDev\tMax\tTotal\t") fmt.Fprintln( w, fmt.Sprintf( "Txs/sec\t%.0f\t%.0f\t%d\t%d\t", stats.TxsThroughput.Mean(), stats.TxsThroughput.StdDev(), stats.TxsThroughput.Max(), stats.TxsThroughput.Sum(), ), ) fmt.Fprintln( w, fmt.Sprintf("Blocks/sec\t%.3f\t%.3f\t%d\t%d\t", stats.BlocksThroughput.Mean(), stats.BlocksThroughput.StdDev(), stats.BlocksThroughput.Max(), stats.BlocksThroughput.Sum(), ), ) w.Flush() } }