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.

192 lines
4.5 KiB

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