github.com/evdatsion/aphelion-dpos-bft@v0.32.1/tools/tm-bench/statistics.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"math"
     7  	"os"
     8  	"text/tabwriter"
     9  	"time"
    10  
    11  	metrics "github.com/rcrowley/go-metrics"
    12  	tmrpc "github.com/evdatsion/aphelion-dpos-bft/rpc/client"
    13  	"github.com/evdatsion/aphelion-dpos-bft/types"
    14  )
    15  
    16  type statistics struct {
    17  	TxsThroughput    metrics.Histogram `json:"txs_per_sec"`
    18  	BlocksThroughput metrics.Histogram `json:"blocks_per_sec"`
    19  }
    20  
    21  // calculateStatistics calculates the tx / second, and blocks / second based
    22  // off of the number the transactions and number of blocks that occurred from
    23  // the start block, and the end time.
    24  func calculateStatistics(
    25  	client tmrpc.Client,
    26  	minHeight int64,
    27  	timeStart time.Time,
    28  	duration int,
    29  ) (*statistics, error) {
    30  	timeEnd := timeStart.Add(time.Duration(duration) * time.Second)
    31  
    32  	stats := &statistics{
    33  		BlocksThroughput: metrics.NewHistogram(metrics.NewUniformSample(1000)),
    34  		TxsThroughput:    metrics.NewHistogram(metrics.NewUniformSample(1000)),
    35  	}
    36  
    37  	var (
    38  		numBlocksPerSec = make(map[int64]int64)
    39  		numTxsPerSec    = make(map[int64]int64)
    40  	)
    41  
    42  	// because during some seconds blocks won't be created...
    43  	for i := int64(0); i < int64(duration); i++ {
    44  		numBlocksPerSec[i] = 0
    45  		numTxsPerSec[i] = 0
    46  	}
    47  
    48  	blockMetas, err := getBlockMetas(client, minHeight, timeStart, timeEnd)
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  
    53  	// iterates from max height to min height
    54  	for _, blockMeta := range blockMetas {
    55  		// check if block was created after timeStart
    56  		if blockMeta.Header.Time.Before(timeStart) {
    57  			break
    58  		}
    59  
    60  		// check if block was created before timeEnd
    61  		if blockMeta.Header.Time.After(timeEnd) {
    62  			continue
    63  		}
    64  		sec := secondsSinceTimeStart(timeStart, blockMeta.Header.Time)
    65  
    66  		// increase number of blocks for that second
    67  		numBlocksPerSec[sec]++
    68  
    69  		// increase number of txs for that second
    70  		numTxsPerSec[sec] += blockMeta.Header.NumTxs
    71  		logger.Debug(fmt.Sprintf("%d txs at block height %d", blockMeta.Header.NumTxs, blockMeta.Header.Height))
    72  	}
    73  
    74  	for i := int64(0); i < int64(duration); i++ {
    75  		stats.BlocksThroughput.Update(numBlocksPerSec[i])
    76  		stats.TxsThroughput.Update(numTxsPerSec[i])
    77  	}
    78  
    79  	return stats, nil
    80  }
    81  
    82  func getBlockMetas(client tmrpc.Client, minHeight int64, timeStart, timeEnd time.Time) ([]*types.BlockMeta, error) {
    83  	// get blocks between minHeight and last height
    84  	// This returns max(minHeight,(last_height - 20)) to last_height
    85  	info, err := client.BlockchainInfo(minHeight, 0)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  
    90  	var (
    91  		blockMetas = info.BlockMetas
    92  		lastHeight = info.LastHeight
    93  		diff       = lastHeight - minHeight
    94  		offset     = len(blockMetas)
    95  	)
    96  
    97  	for offset < int(diff) {
    98  		// get blocks between minHeight and last height
    99  		info, err := client.BlockchainInfo(minHeight, lastHeight-int64(offset))
   100  		if err != nil {
   101  			return nil, err
   102  		}
   103  		blockMetas = append(blockMetas, info.BlockMetas...)
   104  		offset = len(blockMetas)
   105  	}
   106  
   107  	return blockMetas, nil
   108  }
   109  
   110  func secondsSinceTimeStart(timeStart, timePassed time.Time) int64 {
   111  	return int64(math.Round(timePassed.Sub(timeStart).Seconds()))
   112  }
   113  
   114  func printStatistics(stats *statistics, outputFormat string) {
   115  	if outputFormat == "json" {
   116  		result, err := json.Marshal(struct {
   117  			TxsThroughput    float64 `json:"txs_per_sec_avg"`
   118  			BlocksThroughput float64 `json:"blocks_per_sec_avg"`
   119  		}{stats.TxsThroughput.Mean(), stats.BlocksThroughput.Mean()})
   120  
   121  		if err != nil {
   122  			fmt.Fprintln(os.Stderr, err)
   123  			os.Exit(1)
   124  		}
   125  		fmt.Println(string(result))
   126  	} else {
   127  		w := tabwriter.NewWriter(os.Stdout, 0, 0, 5, ' ', 0)
   128  		fmt.Fprintln(w, "Stats\tAvg\tStdDev\tMax\tTotal\t")
   129  		fmt.Fprintln(
   130  			w,
   131  			fmt.Sprintf(
   132  				"Txs/sec\t%.0f\t%.0f\t%d\t%d\t",
   133  				stats.TxsThroughput.Mean(),
   134  				stats.TxsThroughput.StdDev(),
   135  				stats.TxsThroughput.Max(),
   136  				stats.TxsThroughput.Sum(),
   137  			),
   138  		)
   139  		fmt.Fprintln(
   140  			w,
   141  			fmt.Sprintf("Blocks/sec\t%.3f\t%.3f\t%d\t%d\t",
   142  				stats.BlocksThroughput.Mean(),
   143  				stats.BlocksThroughput.StdDev(),
   144  				stats.BlocksThroughput.Max(),
   145  				stats.BlocksThroughput.Sum(),
   146  			),
   147  		)
   148  		w.Flush()
   149  	}
   150  }