bitbucket.org/number571/tendermint@v0.8.14/test/e2e/runner/load.go (about) 1 package main 2 3 import ( 4 "context" 5 "crypto/rand" 6 "errors" 7 "fmt" 8 "math" 9 "time" 10 11 rpchttp "bitbucket.org/number571/tendermint/rpc/client/http" 12 e2e "bitbucket.org/number571/tendermint/test/e2e/pkg" 13 "bitbucket.org/number571/tendermint/types" 14 ) 15 16 // Load generates transactions against the network until the given context is 17 // canceled. A multiplier of greater than one can be supplied if load needs to 18 // be generated beyond a minimum amount. 19 func Load(ctx context.Context, testnet *e2e.Testnet, multiplier int) error { 20 // Since transactions are executed across all nodes in the network, we need 21 // to reduce transaction load for larger networks to avoid using too much 22 // CPU. This gives high-throughput small networks and low-throughput large ones. 23 // This also limits the number of TCP connections, since each worker has 24 // a connection to all nodes. 25 concurrency := 64 / len(testnet.Nodes) 26 if concurrency == 0 { 27 concurrency = 1 28 } 29 initialTimeout := 1 * time.Minute 30 stallTimeout := 30 * time.Second 31 32 chTx := make(chan types.Tx) 33 chSuccess := make(chan types.Tx) 34 ctx, cancel := context.WithCancel(ctx) 35 defer cancel() 36 37 // Spawn job generator and processors. 38 logger.Info(fmt.Sprintf("Starting transaction load (%v workers)...", concurrency)) 39 started := time.Now() 40 41 go loadGenerate(ctx, chTx, multiplier, testnet.TxSize) 42 43 for w := 0; w < concurrency; w++ { 44 go loadProcess(ctx, testnet, chTx, chSuccess) 45 } 46 47 // Monitor successful transactions, and abort on stalls. 48 success := 0 49 timeout := initialTimeout 50 for { 51 select { 52 case <-chSuccess: 53 success++ 54 timeout = stallTimeout 55 case <-time.After(timeout): 56 return fmt.Errorf("unable to submit transactions for %v", timeout) 57 case <-ctx.Done(): 58 if success == 0 { 59 return errors.New("failed to submit any transactions") 60 } 61 logger.Info(fmt.Sprintf("Ending transaction load after %v txs (%.1f tx/s)...", 62 success, float64(success)/time.Since(started).Seconds())) 63 return nil 64 } 65 } 66 } 67 68 // loadGenerate generates jobs until the context is canceled 69 func loadGenerate(ctx context.Context, chTx chan<- types.Tx, multiplier int, size int64) { 70 for i := 0; i < math.MaxInt64; i++ { 71 // We keep generating the same 100 keys over and over, with different values. 72 // This gives a reasonable load without putting too much data in the app. 73 id := i % 100 74 75 bz := make([]byte, size) 76 _, err := rand.Read(bz) 77 if err != nil { 78 panic(fmt.Sprintf("Failed to read random bytes: %v", err)) 79 } 80 tx := types.Tx(fmt.Sprintf("load-%X=%x", id, bz)) 81 82 select { 83 case chTx <- tx: 84 sqrtSize := int(math.Sqrt(float64(size))) 85 time.Sleep(10 * time.Millisecond * time.Duration(sqrtSize/multiplier)) 86 87 case <-ctx.Done(): 88 close(chTx) 89 return 90 } 91 } 92 } 93 94 // loadProcess processes transactions 95 func loadProcess(ctx context.Context, testnet *e2e.Testnet, chTx <-chan types.Tx, chSuccess chan<- types.Tx) { 96 // Each worker gets its own client to each node, which allows for some 97 // concurrency while still bounding it. 98 clients := map[string]*rpchttp.HTTP{} 99 100 var err error 101 for tx := range chTx { 102 node := testnet.RandomNode() 103 104 client, ok := clients[node.Name] 105 if !ok { 106 client, err = node.Client() 107 if err != nil { 108 continue 109 } 110 111 // check that the node is up 112 _, err = client.Health(ctx) 113 if err != nil { 114 continue 115 } 116 117 clients[node.Name] = client 118 } 119 120 if _, err = client.BroadcastTxSync(ctx, tx); err != nil { 121 continue 122 } 123 124 chSuccess <- tx 125 } 126 }