From aa8feb0a877b952ef4adc0558bb8e2e9463fecb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Wed, 20 Apr 2022 14:38:43 +0200 Subject: [PATCH] Fix loadbot gas metric collection (#497) * Rework how gas metrics are gathered * Resolve receiving address issue * Fix typo * Change up the ordering in var init * Simplify contract metric init logic * Simplify the metric fetching code blocks --- command/loadbot/deploy_contract.go | 18 ++-- command/loadbot/execution.go | 78 ++++++++++++---- command/loadbot/helper.go | 140 ++++++++++++++++++----------- command/loadbot/params.go | 14 ++- command/loadbot/result.go | 2 +- 5 files changed, 176 insertions(+), 76 deletions(-) diff --git a/command/loadbot/deploy_contract.go b/command/loadbot/deploy_contract.go index 81fba72277..9852710fc1 100644 --- a/command/loadbot/deploy_contract.go +++ b/command/loadbot/deploy_contract.go @@ -63,8 +63,18 @@ func (l *Loadbot) deployContract( } end := time.Now() - // initialize gas metrics map with block nuber as index - l.metrics.ContractMetrics.ContractGasMetrics.Blocks[receipt.BlockNumber] = GasMetrics{} + + // initialize gas metrics map with block number as index + l.metrics.ContractMetrics.ContractGasMetrics, err = getBlockGasMetrics( + jsonClient, + map[uint64]struct{}{ + receipt.BlockNumber: {}, + }, + ) + if err != nil { + return fmt.Errorf("unable to fetch gas metrics, %w", err) + } + // fetch contract address l.metrics.ContractMetrics.ContractAddress = receipt.ContractAddress // set contract address in order to get new example txn and gas estimate @@ -88,10 +98,6 @@ func (l *Loadbot) deployContract( blockNumber: receipt.BlockNumber, }, ) - // calculate contract deployment metrics - if err := l.calculateGasMetrics(jsonClient, l.metrics.ContractMetrics.ContractGasMetrics); err != nil { - return fmt.Errorf("unable to calculate contract block gas metrics: %w", err) - } l.metrics.ContractMetrics.ContractDeploymentDuration.calcTurnAroundMetrics() l.metrics.ContractMetrics.ContractDeploymentDuration.TotalExecTime = end.Sub(start) diff --git a/command/loadbot/execution.go b/command/loadbot/execution.go index fce4af4906..d77a4ff7f8 100644 --- a/command/loadbot/execution.go +++ b/command/loadbot/execution.go @@ -76,8 +76,17 @@ type GasMetrics struct { } type BlockGasMetrics struct { - Blocks map[uint64]GasMetrics - BlockGasMutex *sync.Mutex + sync.Mutex + + Blocks map[uint64]GasMetrics +} + +// AddBlockMetric adds a block gas metric for the specified block number [Thread safe] +func (b *BlockGasMetrics) AddBlockMetric(blockNum uint64, gasMetric GasMetrics) { + b.Lock() + defer b.Unlock() + + b.Blocks[blockNum] = gasMetric } type ContractMetricsData struct { @@ -91,7 +100,7 @@ type Metrics struct { TotalTransactionsSentCount uint64 FailedTransactionsCount uint64 TransactionDuration ExecDuration - ContractMetrics ContractMetricsData + ContractMetrics *ContractMetricsData GasMetrics *BlockGasMetrics } @@ -102,7 +111,7 @@ type Loadbot struct { } func NewLoadbot(cfg *Configuration) *Loadbot { - return &Loadbot{ + loadbot := &Loadbot{ cfg: cfg, metrics: &Metrics{ TotalTransactionsSentCount: 0, @@ -110,21 +119,39 @@ func NewLoadbot(cfg *Configuration) *Loadbot { TransactionDuration: ExecDuration{ blockTransactions: make(map[uint64]uint64), }, - ContractMetrics: ContractMetricsData{ - ContractDeploymentDuration: ExecDuration{ - blockTransactions: make(map[uint64]uint64), - }, - ContractGasMetrics: &BlockGasMetrics{ - Blocks: make(map[uint64]GasMetrics), - BlockGasMutex: &sync.Mutex{}, - }, - }, GasMetrics: &BlockGasMetrics{ - Blocks: make(map[uint64]GasMetrics), - BlockGasMutex: &sync.Mutex{}, + Blocks: make(map[uint64]GasMetrics), }, }, } + + // Attempt to initialize contract metrics if needed + loadbot.initContractMetricsIfNeeded() + + return loadbot +} + +// initContractMetrics initializes contract metrics for +// the loadbot instance +func (l *Loadbot) initContractMetricsIfNeeded() { + if !l.needsContractMetrics() { + return + } + + l.metrics.ContractMetrics = &ContractMetricsData{ + ContractDeploymentDuration: ExecDuration{ + blockTransactions: make(map[uint64]uint64), + }, + ContractGasMetrics: &BlockGasMetrics{ + Blocks: make(map[uint64]GasMetrics), + }, + } +} + +func (l *Loadbot) needsContractMetrics() bool { + return l.cfg.GeneratorMode == deploy || + l.cfg.GeneratorMode == erc20 || + l.cfg.GeneratorMode == erc721 } func (l *Loadbot) GetMetrics() *Metrics { @@ -235,7 +262,18 @@ func (l *Loadbot) Run() error { } } - var wg sync.WaitGroup + var ( + seenBlockNums = make(map[uint64]struct{}) + seenBlockNumsLock sync.Mutex + wg sync.WaitGroup + ) + + markSeenBlock := func(blockNum uint64) { + seenBlockNumsLock.Lock() + defer seenBlockNumsLock.Unlock() + + seenBlockNums[blockNum] = struct{}{} + } for i := uint64(0); i < l.cfg.Count; i++ { <-ticker.C @@ -284,7 +322,9 @@ func (l *Loadbot) Run() error { return } - l.initGasMetricsBlocksMap(receipt.BlockNumber) + // Mark the block as seen so data on it + // is gathered later + markSeenBlock(receipt.BlockNumber) // Stop the performance timer end := time.Now() @@ -303,7 +343,9 @@ func (l *Loadbot) Run() error { endTime := time.Now() - if err := l.calculateGasMetrics(jsonClient, l.metrics.GasMetrics); err != nil { + // Fetch the block gas metrics for seen blocks + l.metrics.GasMetrics, err = getBlockGasMetrics(jsonClient, seenBlockNums) + if err != nil { return fmt.Errorf("unable to calculate block gas metrics: %w", err) } diff --git a/command/loadbot/helper.go b/command/loadbot/helper.go index c9b6687f57..970d8183b1 100644 --- a/command/loadbot/helper.go +++ b/command/loadbot/helper.go @@ -1,13 +1,11 @@ package loadbot import ( - "context" "fmt" "math/big" + "sync" "time" - "golang.org/x/sync/errgroup" - "github.com/0xPolygon/polygon-edge/types" "github.com/umbracle/go-web3" "github.com/umbracle/go-web3/jsonrpc" @@ -57,12 +55,12 @@ func estimateGas(client *jsonrpc.Client, txn *types.Transaction) (uint64, error) return gasEstimate, nil } -// calculate block utilization in percents -func calculateBlockUtilization(blockInfo GasMetrics) float64 { - return float64(blockInfo.GasUsed) / float64(blockInfo.GasLimit) * 100 +// calculateBlockUtilization calculates block utilization in percents +func calculateBlockUtilization(gasUsed, gasLimit uint64) float64 { + return float64(gasUsed) / float64(gasLimit) * 100 } -// calculate average block utilization across all blocks +// calculateAvgBlockUtil calculates average block utilization across all blocks func calculateAvgBlockUtil(gasData map[uint64]GasMetrics) float64 { sum := float64(0) for _, i := range gasData { @@ -72,58 +70,107 @@ func calculateAvgBlockUtil(gasData map[uint64]GasMetrics) float64 { return sum / float64(len(gasData)) } -// fetch block gas usage and gas limit and calculate block utilization -func (l *Loadbot) calculateGasMetrics(jsonClient *jsonrpc.Client, gasMetrics *BlockGasMetrics) error { - errGr, _ := errgroup.WithContext(context.Background()) +// getBlockGasMetrics fetches block gas metrics from the JSON-RPC client +// for the specified blocks +func getBlockGasMetrics( + jsonClient *jsonrpc.Client, + blockNums map[uint64]struct{}, +) (*BlockGasMetrics, error) { + var ( + errors = make([]error, 0) + errorsLock sync.Mutex + blockGasMetrics = &BlockGasMetrics{ + Blocks: make(map[uint64]GasMetrics), + } + wg sync.WaitGroup + ) + + // addError is a helper for accumulating errors + // in go routines + addError := func(fetchErr error) { + errorsLock.Lock() + defer errorsLock.Unlock() + + errors = append(errors, fetchErr) + } + + queryBlockInfo := func( + blockNum uint64, + ) { + // Query node for block + blockInfo, err := jsonClient.Eth().GetBlockByNumber( + web3.BlockNumber(blockNum), + false, + ) + if err != nil { + addError( + fmt.Errorf("could not fetch block %d by number, %w", blockNum, err), + ) + + return + } - for num, data := range gasMetrics.Blocks { - blockNum := num - blockData := data + // Update the block gas metrics + blockGasMetrics.AddBlockMetric( + blockNum, + GasMetrics{ + GasUsed: blockInfo.GasUsed, + GasLimit: blockInfo.GasLimit, + Utilization: calculateBlockUtilization(blockInfo.GasUsed, blockInfo.GasLimit), + }, + ) + } - errGr.Go(func() error { - blockInfom, err := jsonClient.Eth().GetBlockByNumber(web3.BlockNumber(blockNum), false) - if err != nil { - return fmt.Errorf("could not fetch block %d by number, %w", blockNum, err) - } + // For each block number, fetch the corresponding + // block info data + for blockNum := range blockNums { + wg.Add(1) - blockData.GasLimit = blockInfom.GasLimit - blockData.GasUsed = blockInfom.GasUsed - blockData.Utilization = calculateBlockUtilization(blockData) - gasMetrics.Blocks[blockNum] = blockData + go func(blockNum uint64) { + defer wg.Done() - return nil - }) + queryBlockInfo(blockNum) + }(blockNum) } - if err := errGr.Wait(); err != nil { - return err + wg.Wait() + + if len(errors) > 1 { + return nil, fmt.Errorf( + "unable to successfully fetch gas metrics, %v", + errors, + ) } - return nil + return blockGasMetrics, nil } +// updateGasEstimate updates the loadbot generator gas estimate func (l *Loadbot) updateGasEstimate(jsonClient *jsonrpc.Client) error { - //nolint: ifshort gasLimit := l.cfg.GasLimit - if gasLimit == nil { - // Get the gas estimate - exampleTxn, err := l.generator.GetExampleTransaction() - if err != nil { - return fmt.Errorf("unable to get example transaction, %w", err) - } - - // No gas limit specified, query the network for an estimation - gasEstimate, estimateErr := estimateGas(jsonClient, exampleTxn) - if estimateErr != nil { - return fmt.Errorf("unable to get gas estimate, %w", err) - } + if gasLimit != nil { + // User specified a gas limit to use, + // no need to calculate one + return nil + } - gasLimit = new(big.Int).SetUint64(gasEstimate) + // User didn't specify a gas limit to use, calculate it + exampleTxn, err := l.generator.GetExampleTransaction() + if err != nil { + return fmt.Errorf("unable to get example transaction, %w", err) + } - l.generator.SetGasEstimate(gasLimit.Uint64()) + // No gas limit specified, query the network for an estimation + gasEstimate, estimateErr := estimateGas(jsonClient, exampleTxn) + if estimateErr != nil { + return fmt.Errorf("unable to get gas estimate, %w", err) } + gasLimit = new(big.Int).SetUint64(gasEstimate) + + l.generator.SetGasEstimate(gasLimit.Uint64()) + return nil } @@ -143,7 +190,7 @@ func calcMaxTimeout(count, tps uint64) time.Duration { return waitTime + waitFactor } -// returns true if this is erc20 or erc721 mode +// isTokenTransferMode checks if the mode is erc20 or erc721 func (l *Loadbot) isTokenTransferMode() bool { switch l.cfg.GeneratorMode { case erc20, erc721: @@ -152,10 +199,3 @@ func (l *Loadbot) isTokenTransferMode() bool { return false } } - -//initialze gas metrics blocks map with block number as key -func (l *Loadbot) initGasMetricsBlocksMap(blockNum uint64) { - l.metrics.GasMetrics.BlockGasMutex.Lock() - l.metrics.GasMetrics.Blocks[blockNum] = GasMetrics{} - l.metrics.GasMetrics.BlockGasMutex.Unlock() -} diff --git a/command/loadbot/params.go b/command/loadbot/params.go index d8772849eb..b52d5ca41c 100644 --- a/command/loadbot/params.go +++ b/command/loadbot/params.go @@ -140,13 +140,25 @@ func (p *loadbotParams) initAddressValues() error { return fmt.Errorf("failed to decode sender address: %w", err) } - if err := p.receiver.UnmarshalText([]byte(p.receiverRaw)); err != nil { + if err := p.initReceiverAddress(); err != nil { return fmt.Errorf("failed to decode receiver address: %w", err) } return nil } +func (p *loadbotParams) initReceiverAddress() error { + if p.receiverRaw == "" { + // No receiving address specified, + // use the sender address as the receiving address + p.receiver = p.sender + + return nil + } + + return p.receiver.UnmarshalText([]byte(p.receiverRaw)) +} + func (p *loadbotParams) initTxnValue() error { value, err := types.ParseUint256orHex(&p.valueRaw) if err != nil { diff --git a/command/loadbot/result.go b/command/loadbot/result.go index 1a8f131a9d..4feeba4efd 100644 --- a/command/loadbot/result.go +++ b/command/loadbot/result.go @@ -244,7 +244,7 @@ func (lr *LoadbotResult) writeLoadbotResults(buffer *bytes.Buffer) { func (lr *LoadbotResult) writeAverageBlockUtilization(buffer *bytes.Buffer) { buffer.WriteString("\n\n[AVERAGE BLOCK UTILIZATION]\n") buffer.WriteString(helper.FormatKV([]string{ - fmt.Sprintf("Average utilization acorss all blocks|%.2f%%", calculateAvgBlockUtil(lr.BlockData.GasData)), + fmt.Sprintf("Average utilization across all blocks|%.2f%%", calculateAvgBlockUtil(lr.BlockData.GasData)), })) }