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.

200 lines
5.2 KiB

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