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.

114 lines
3.0 KiB

  1. package main
  2. import (
  3. "context"
  4. "crypto/rand"
  5. "errors"
  6. "fmt"
  7. "math"
  8. "time"
  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
  14. // context is cancelled.
  15. func Load(ctx context.Context, 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 := 64 / len(testnet.Nodes)
  22. if concurrency == 0 {
  23. concurrency = 1
  24. }
  25. initialTimeout := 1 * time.Minute
  26. stallTimeout := 30 * time.Second
  27. chTx := make(chan types.Tx)
  28. chSuccess := make(chan types.Tx)
  29. ctx, cancel := context.WithCancel(ctx)
  30. defer cancel()
  31. // Spawn job generator and processors.
  32. logger.Info(fmt.Sprintf("Starting transaction load (%v workers)...", concurrency))
  33. started := time.Now()
  34. go loadGenerate(ctx, chTx)
  35. for w := 0; w < concurrency; w++ {
  36. go loadProcess(ctx, testnet, chTx, chSuccess)
  37. }
  38. // Monitor successful transactions, and abort on stalls.
  39. success := 0
  40. timeout := initialTimeout
  41. for {
  42. select {
  43. case <-chSuccess:
  44. success++
  45. timeout = stallTimeout
  46. case <-time.After(timeout):
  47. return fmt.Errorf("unable to submit transactions for %v", timeout)
  48. case <-ctx.Done():
  49. if success == 0 {
  50. return errors.New("failed to submit any transactions")
  51. }
  52. logger.Info(fmt.Sprintf("Ending transaction load after %v txs (%.1f tx/s)...",
  53. success, float64(success)/time.Since(started).Seconds()))
  54. return nil
  55. }
  56. }
  57. }
  58. // loadGenerate generates jobs until the context is cancelled
  59. func loadGenerate(ctx context.Context, chTx chan<- types.Tx) {
  60. for i := 0; i < math.MaxInt64; i++ {
  61. // We keep generating the same 1000 keys over and over, with different values.
  62. // This gives a reasonable load without putting too much data in the app.
  63. id := i % 1000
  64. bz := make([]byte, 2048) // 4kb hex-encoded
  65. _, err := rand.Read(bz)
  66. if err != nil {
  67. panic(fmt.Sprintf("Failed to read random bytes: %v", err))
  68. }
  69. tx := types.Tx(fmt.Sprintf("load-%X=%x", id, bz))
  70. select {
  71. case chTx <- tx:
  72. time.Sleep(10 * time.Millisecond)
  73. case <-ctx.Done():
  74. close(chTx)
  75. return
  76. }
  77. }
  78. }
  79. // loadProcess processes transactions
  80. func loadProcess(ctx context.Context, testnet *e2e.Testnet, chTx <-chan types.Tx, chSuccess chan<- types.Tx) {
  81. // Each worker gets its own client to each node, which allows for some
  82. // concurrency while still bounding it.
  83. clients := map[string]*rpchttp.HTTP{}
  84. var err error
  85. for tx := range chTx {
  86. node := testnet.RandomNode()
  87. client, ok := clients[node.Name]
  88. if !ok {
  89. client, err = node.Client()
  90. if err != nil {
  91. continue
  92. }
  93. clients[node.Name] = client
  94. }
  95. _, err = client.BroadcastTxCommit(ctx, tx)
  96. if err != nil {
  97. continue
  98. }
  99. chSuccess <- tx
  100. }
  101. }