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.

201 lines
5.2 KiB

  1. package main
  2. import (
  3. "container/ring"
  4. "context"
  5. "fmt"
  6. "math/rand"
  7. "time"
  8. "github.com/tendermint/tendermint/libs/log"
  9. tmrand "github.com/tendermint/tendermint/libs/rand"
  10. rpchttp "github.com/tendermint/tendermint/rpc/client/http"
  11. e2e "github.com/tendermint/tendermint/test/e2e/pkg"
  12. "github.com/tendermint/tendermint/types"
  13. )
  14. // Load generates transactions against the network until the given context is
  15. // canceled.
  16. func Load(ctx context.Context, logger log.Logger, r *rand.Rand, testnet *e2e.Testnet) error {
  17. // Since transactions are executed across all nodes in the network, we need
  18. // to reduce transaction load for larger networks to avoid using too much
  19. // CPU. This gives high-throughput small networks and low-throughput large ones.
  20. // This also limits the number of TCP connections, since each worker has
  21. // a connection to all nodes.
  22. concurrency := len(testnet.Nodes) * 2
  23. if concurrency > 32 {
  24. concurrency = 32
  25. }
  26. chTx := make(chan types.Tx)
  27. chSuccess := make(chan int) // success counts per iteration
  28. ctx, cancel := context.WithCancel(ctx)
  29. defer cancel()
  30. // Spawn job generator and processors.
  31. logger.Info("starting transaction load",
  32. "workers", concurrency,
  33. "nodes", len(testnet.Nodes),
  34. "tx", testnet.TxSize)
  35. started := time.Now()
  36. go loadGenerate(ctx, r, chTx, testnet.TxSize, len(testnet.Nodes))
  37. for w := 0; w < concurrency; w++ {
  38. go loadProcess(ctx, testnet, chTx, chSuccess)
  39. }
  40. // Montior transaction to ensure load propagates to the network
  41. //
  42. // This loop doesn't check or time out for stalls, since a stall here just
  43. // aborts the load generator sooner and could obscure backpressure
  44. // from the test harness, and there are other checks for
  45. // stalls in the framework. Ideally we should monitor latency as a guide
  46. // for when to give up, but we don't have a good way to track that yet.
  47. success := 0
  48. for {
  49. select {
  50. case numSeen := <-chSuccess:
  51. success += numSeen
  52. case <-ctx.Done():
  53. if success == 0 {
  54. return fmt.Errorf("failed to submit transactions in %s by %d workers",
  55. time.Since(started), concurrency)
  56. }
  57. // TODO perhaps allow test networks to
  58. // declare required transaction rates, which
  59. // might allow us to avoid the special case
  60. // around 0 txs above.
  61. rate := float64(success) / time.Since(started).Seconds()
  62. logger.Info("ending transaction load",
  63. "dur_secs", time.Since(started).Seconds(),
  64. "txns", success,
  65. "workers", concurrency,
  66. "rate", rate)
  67. return nil
  68. }
  69. }
  70. }
  71. // loadGenerate generates jobs until the context is canceled.
  72. //
  73. // The chTx has multiple consumers, thus the rate limiting of the load
  74. // generation is primarily the result of backpressure from the
  75. // broadcast transaction, though there is still some timer-based
  76. // limiting.
  77. func loadGenerate(ctx context.Context, r *rand.Rand, chTx chan<- types.Tx, txSize int, networkSize int) {
  78. timer := time.NewTimer(0)
  79. defer timer.Stop()
  80. defer close(chTx)
  81. for {
  82. select {
  83. case <-ctx.Done():
  84. return
  85. case <-timer.C:
  86. }
  87. // Constrain the key space to avoid using too much
  88. // space, while reduce the size of the data in the app.
  89. id := r.Int63n(100)
  90. tx := types.Tx(fmt.Sprintf("load-%X=%s", id, tmrand.StrFromSource(r, txSize)))
  91. select {
  92. case <-ctx.Done():
  93. return
  94. case chTx <- tx:
  95. // sleep for a bit before sending the
  96. // next transaction.
  97. timer.Reset(loadGenerateWaitTime(r, networkSize))
  98. }
  99. }
  100. }
  101. func loadGenerateWaitTime(r *rand.Rand, size int) time.Duration {
  102. const (
  103. min = int64(250 * time.Millisecond)
  104. max = int64(time.Second)
  105. )
  106. var (
  107. baseJitter = r.Int63n(max-min+1) + min
  108. sizeFactor = int64(size) * min
  109. sizeJitter = r.Int63n(sizeFactor-min+1) + min
  110. )
  111. return time.Duration(baseJitter + sizeJitter)
  112. }
  113. // loadProcess processes transactions
  114. func loadProcess(ctx context.Context, testnet *e2e.Testnet, chTx <-chan types.Tx, chSuccess chan<- int) {
  115. // Each worker gets its own client to each usable node, which
  116. // allows for some concurrency while still bounding it.
  117. clients := make([]*rpchttp.HTTP, 0, len(testnet.Nodes))
  118. for idx := range testnet.Nodes {
  119. // Construct a list of usable nodes for the creating
  120. // load. Don't send load through seed nodes because
  121. // they do not provide the RPC endpoints required to
  122. // broadcast transaction.
  123. if testnet.Nodes[idx].Mode == e2e.ModeSeed {
  124. continue
  125. }
  126. client, err := testnet.Nodes[idx].Client()
  127. if err != nil {
  128. continue
  129. }
  130. clients = append(clients, client)
  131. }
  132. if len(clients) == 0 {
  133. panic("no clients to process load")
  134. }
  135. // Put the clients in a ring so they can be used in a
  136. // round-robin fashion.
  137. clientRing := ring.New(len(clients))
  138. for idx := range clients {
  139. clientRing.Value = clients[idx]
  140. clientRing = clientRing.Next()
  141. }
  142. successes := 0
  143. for {
  144. select {
  145. case <-ctx.Done():
  146. return
  147. case tx := <-chTx:
  148. clientRing = clientRing.Next()
  149. client := clientRing.Value.(*rpchttp.HTTP)
  150. if status, err := client.Status(ctx); err != nil {
  151. continue
  152. } else if status.SyncInfo.CatchingUp {
  153. continue
  154. }
  155. if _, err := client.BroadcastTxSync(ctx, tx); err != nil {
  156. continue
  157. }
  158. successes++
  159. select {
  160. case chSuccess <- successes:
  161. successes = 0 // reset counter for the next iteration
  162. continue
  163. case <-ctx.Done():
  164. return
  165. default:
  166. }
  167. }
  168. }
  169. }