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