github.com/vipernet-xyz/tm@v0.34.24/test/e2e/runner/benchmark.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math"
     7  	"time"
     8  
     9  	e2e "github.com/vipernet-xyz/tm/test/e2e/pkg"
    10  	"github.com/vipernet-xyz/tm/types"
    11  )
    12  
    13  // Benchmark is a simple function for fetching, calculating and printing
    14  // the following metrics:
    15  // 1. Average block production time
    16  // 2. Block interval standard deviation
    17  // 3. Max block interval (slowest block)
    18  // 4. Min block interval (fastest block)
    19  //
    20  // Metrics are based of the `benchmarkLength`, the amount of consecutive blocks
    21  // sampled from in the testnet
    22  func Benchmark(testnet *e2e.Testnet, benchmarkLength int64) error {
    23  	block, _, err := waitForHeight(testnet, 0)
    24  	if err != nil {
    25  		return err
    26  	}
    27  
    28  	logger.Info("Beginning benchmark period...", "height", block.Height)
    29  
    30  	// wait for the length of the benchmark period in blocks to pass. We allow 5 seconds for each block
    31  	// which should be sufficient.
    32  	waitingTime := time.Duration(benchmarkLength*5) * time.Second
    33  	endHeight, err := waitForAllNodes(testnet, block.Height+benchmarkLength, waitingTime)
    34  	if err != nil {
    35  		return err
    36  	}
    37  
    38  	logger.Info("Ending benchmark period", "height", endHeight)
    39  
    40  	// fetch a sample of blocks
    41  	blocks, err := fetchBlockChainSample(testnet, benchmarkLength)
    42  	if err != nil {
    43  		return err
    44  	}
    45  
    46  	// slice into time intervals and collate data
    47  	timeIntervals := splitIntoBlockIntervals(blocks)
    48  	testnetStats := extractTestnetStats(timeIntervals)
    49  	testnetStats.startHeight = blocks[0].Header.Height
    50  	testnetStats.endHeight = blocks[len(blocks)-1].Header.Height
    51  
    52  	// print and return
    53  	logger.Info(testnetStats.String())
    54  	return nil
    55  }
    56  
    57  type testnetStats struct {
    58  	startHeight int64
    59  	endHeight   int64
    60  
    61  	// average time to produce a block
    62  	mean time.Duration
    63  	// standard deviation of block production
    64  	std float64
    65  	// longest time to produce a block
    66  	max time.Duration
    67  	// shortest time to produce a block
    68  	min time.Duration
    69  }
    70  
    71  func (t *testnetStats) String() string {
    72  	return fmt.Sprintf(`Benchmarked from height %v to %v
    73  	Mean Block Interval: %v
    74  	Standard Deviation: %f
    75  	Max Block Interval: %v
    76  	Min Block Interval: %v
    77  	`,
    78  		t.startHeight,
    79  		t.endHeight,
    80  		t.mean,
    81  		t.std,
    82  		t.max,
    83  		t.min,
    84  	)
    85  }
    86  
    87  // fetchBlockChainSample waits for `benchmarkLength` amount of blocks to pass, fetching
    88  // all of the headers for these blocks from an archive node and returning it.
    89  func fetchBlockChainSample(testnet *e2e.Testnet, benchmarkLength int64) ([]*types.BlockMeta, error) {
    90  	var blocks []*types.BlockMeta
    91  
    92  	// Find the first archive node
    93  	archiveNode := testnet.ArchiveNodes()[0]
    94  	c, err := archiveNode.Client()
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  
    99  	// find the latest height
   100  	ctx := context.Background()
   101  	s, err := c.Status(ctx)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  
   106  	to := s.SyncInfo.LatestBlockHeight
   107  	from := to - benchmarkLength + 1
   108  	if from <= testnet.InitialHeight {
   109  		return nil, fmt.Errorf("tesnet was unable to reach required height for benchmarking (latest height %d)", to)
   110  	}
   111  
   112  	// Fetch blocks
   113  	for from < to {
   114  		// fetch the blockchain metas. Currently we can only fetch 20 at a time
   115  		resp, err := c.BlockchainInfo(ctx, from, min(from+19, to))
   116  		if err != nil {
   117  			return nil, err
   118  		}
   119  
   120  		blockMetas := resp.BlockMetas
   121  		// we receive blocks in descending order so we have to add them in reverse
   122  		for i := len(blockMetas) - 1; i >= 0; i-- {
   123  			if blockMetas[i].Header.Height != from {
   124  				return nil, fmt.Errorf("node gave us another header. Wanted %d, got %d",
   125  					from,
   126  					blockMetas[i].Header.Height,
   127  				)
   128  			}
   129  			from++
   130  			blocks = append(blocks, blockMetas[i])
   131  		}
   132  	}
   133  
   134  	return blocks, nil
   135  }
   136  
   137  func splitIntoBlockIntervals(blocks []*types.BlockMeta) []time.Duration {
   138  	intervals := make([]time.Duration, len(blocks)-1)
   139  	lastTime := blocks[0].Header.Time
   140  	for i, block := range blocks {
   141  		// skip the first block
   142  		if i == 0 {
   143  			continue
   144  		}
   145  
   146  		intervals[i-1] = block.Header.Time.Sub(lastTime)
   147  		lastTime = block.Header.Time
   148  	}
   149  	return intervals
   150  }
   151  
   152  func extractTestnetStats(intervals []time.Duration) testnetStats {
   153  	var (
   154  		sum, mean time.Duration
   155  		std       float64
   156  		max       = intervals[0]
   157  		min       = intervals[0]
   158  	)
   159  
   160  	for _, interval := range intervals {
   161  		sum += interval
   162  
   163  		if interval > max {
   164  			max = interval
   165  		}
   166  
   167  		if interval < min {
   168  			min = interval
   169  		}
   170  	}
   171  	mean = sum / time.Duration(len(intervals))
   172  
   173  	for _, interval := range intervals {
   174  		diff := (interval - mean).Seconds()
   175  		std += math.Pow(diff, 2)
   176  	}
   177  	std = math.Sqrt(std / float64(len(intervals)))
   178  
   179  	return testnetStats{
   180  		mean: mean,
   181  		std:  std,
   182  		max:  max,
   183  		min:  min,
   184  	}
   185  }
   186  
   187  func min(a, b int64) int64 {
   188  	if a > b {
   189  		return b
   190  	}
   191  	return a
   192  }