Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
235 changes: 117 additions & 118 deletions chains/ethereum/metrics/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@
import (
"context"
"fmt"
"math"
"math/big"
"math/rand"
"sync"
"time"

"github.com/ethereum/go-ethereum/common"
Expand All @@ -20,127 +16,130 @@

// ProcessResults processes the results of the load test.
func ProcessResults(ctx context.Context, logger *zap.Logger, sentTxs []*types.SentTx, startBlock, endBlock uint64, clients []*ethclient.Client) (*loadtesttypes.LoadTestResult, error) {
wg := sync.WaitGroup{}
blockStats := make([]loadtesttypes.BlockStat, endBlock-startBlock+1)
receipts := make(map[uint64]gethtypes.Receipts)
logger.Info("collecting metrics", zap.Uint64("starting_block", startBlock), zap.Uint64("ending_block", endBlock))
logger.Info("sleeping for 15 mins to let test finish", zap.Uint64("starting_block", startBlock), zap.Uint64("ending_block", endBlock))
time.Sleep(15 * time.Minute)
logger.Info("done sleeping", zap.Uint64("starting_block", startBlock), zap.Uint64("ending_block", endBlock))
// wg := sync.WaitGroup{}
// blockStats := make([]loadtesttypes.BlockStat, endBlock-startBlock+1)
// receipts := make(map[uint64]gethtypes.Receipts)
// block stats. each go routine will query a block, get all receipts, and construct the block stats.
for blockNum := startBlock; blockNum <= endBlock; blockNum++ {
wg.Add(1)
go func() {
defer wg.Done()
client := clients[rand.Intn(len(clients))]
block, err := client.BlockByNumber(ctx, big.NewInt(int64(blockNum))) //nolint:gosec // G115: overflow unlikely in practice
if err != nil {
logger.Error("Error getting block by number", zap.Uint64("block_num", blockNum), zap.Error(err))
return
}
blockReceipts, err := getReceiptsForBlockTxs(ctx, block, client)
if err != nil {
logger.Error("Error getting receipts for block", zap.Uint64("block_num", blockNum), zap.Error(err))
return
}
if len(blockReceipts) > 0 {
receipts[blockReceipts[0].BlockNumber.Uint64()] = blockReceipts
}
blockStats[blockNum-startBlock] = buildBlockStats(block, blockReceipts)
}()
}
wg.Wait()
// for blockNum := startBlock; blockNum <= endBlock; blockNum++ {
// wg.Add(1)
// go func() {
// defer wg.Done()
// client := clients[rand.Intn(len(clients))]
// block, err := client.BlockByNumber(ctx, big.NewInt(int64(blockNum))) //nolint:gosec // G115: overflow unlikely in practice
// if err != nil {
// logger.Error("Error getting block by number", zap.Uint64("block_num", blockNum), zap.Error(err))
// return
// }
// blockReceipts, err := getReceiptsForBlockTxs(ctx, block, client)
// if err != nil {
// logger.Error("Error getting receipts for block", zap.Uint64("block_num", blockNum), zap.Error(err))
// return
// }
// if len(blockReceipts) > 0 {
// receipts[blockReceipts[0].BlockNumber.Uint64()] = blockReceipts
// }
// blockStats[blockNum-startBlock] = buildBlockStats(block, blockReceipts)
// }()
// }
// wg.Wait()

// remove any 0 tx blocks from the beginning and ends of block stats.
// this can happen if we started processing before txs landed on chain.
blockStats, err := trimBlocks(blockStats)
if err != nil {
return nil, fmt.Errorf("failed to trim blocks: %w", err)
}

logger.Info("analyzing blocks...", zap.Int("num_blocks", len(blockStats)))
msgStats := make(map[loadtesttypes.MsgType]loadtesttypes.MessageStats)
totalSentByType := calculateTotalSentByType(sentTxs)
// update each msgType's total sent transactions
for msgType, totalSent := range totalSentByType {
stat := msgStats[msgType]
stat.Transactions.TotalSent = int(totalSent) //nolint:gosec // G115: overflow unlikely in practice
stat.Gas.Min = math.MaxInt64 // sentinel values for next step
msgStats[msgType] = stat
}

// update msg stats and get global tally
totalIncluded, totalSuccess, totalFailed := 0, 0, 0
totalSent := len(sentTxs)
avgGasPerTx := 0.0
for _, blockReceipts := range receipts {
for _, receipt := range blockReceipts {
var msgType loadtesttypes.MsgType
if receipt.ContractAddress.Cmp(common.Address{}) == 0 {
msgType = types.ContractCall
} else {
msgType = types.ContractCreate
}
stat := msgStats[msgType]

// update gas values
stat.Gas.Max = max(stat.Gas.Max, int64(receipt.GasUsed)) //nolint:gosec // G115 likely not to happen
stat.Gas.Min = min(stat.Gas.Min, int64(receipt.GasUsed)) //nolint:gosec // G115 likely not to happen
stat.Gas.Total += int64(receipt.GasUsed) //nolint:gosec // G115 likely not to happen

// inclusion and statuses.
stat.Transactions.TotalIncluded++
totalIncluded++
if receipt.Status == gethtypes.ReceiptStatusSuccessful {
totalSuccess++
stat.Transactions.Successful++
} else {
totalFailed++
stat.Transactions.Failed++
}

// gas average
stat.Gas.Average = stat.Gas.Total / int64(stat.Transactions.TotalIncluded)
avgGasPerTx += (float64(receipt.GasUsed) - avgGasPerTx) / float64(totalIncluded)
msgStats[msgType] = stat
}
}

// calculate statistics for ALL txs by type. (totals)
// here we are using transactions from the blocks to update each msg type's statistics.
avgGasUtilization := 0.0
for i, blockStat := range blockStats {
// rolling average of gas utilization.
avgGasUtilization += (blockStat.GasUtilization - avgGasUtilization) / float64(i+1)
}

// timings / tps.
startTime := blockStats[0].Timestamp
endTime := blockStats[len(blockStats)-1].Timestamp
runtime := endTime.Sub(startTime)
tps := float64(totalIncluded) / runtime.Seconds()

// final results.
result := &loadtesttypes.LoadTestResult{
Overall: loadtesttypes.OverallStats{
TotalTransactions: totalSent,
TotalIncludedTransactions: totalIncluded,
SuccessfulTransactions: totalSuccess,
FailedTransactions: totalFailed,
AvgBlockGasUtilization: avgGasUtilization,
AvgGasPerTransaction: int64(avgGasPerTx),
Runtime: runtime,
StartTime: startTime,
EndTime: endTime,
BlocksProcessed: len(blockStats),
TPS: tps,
},
ByMessage: msgStats,
ByNode: nil, // TODO: we aren't differentiating on node at the moment. not supported.
ByBlock: blockStats,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Debugging code with 15-minute sleep returns empty results

The ProcessResults function now contains a hardcoded 15-minute time.Sleep and returns an empty LoadTestResult{} with all the actual metrics collection logic commented out. The callers in runner.go expect populated results including TPS, transaction counts, gas statistics, and block data, but they will receive all zero/nil values instead. This appears to be temporary debugging code that completely disables metrics collection functionality.

Fix in Cursor Fix in Web

return result, nil
// blockStats, err := trimBlocks(blockStats)
// if err != nil {
// return nil, fmt.Errorf("failed to trim blocks: %w", err)
// }
//
// logger.Info("analyzing blocks...", zap.Int("num_blocks", len(blockStats)))
// msgStats := make(map[loadtesttypes.MsgType]loadtesttypes.MessageStats)
// totalSentByType := calculateTotalSentByType(sentTxs)
// // update each msgType's total sent transactions
// for msgType, totalSent := range totalSentByType {
// stat := msgStats[msgType]
// stat.Transactions.TotalSent = int(totalSent) //nolint:gosec // G115: overflow unlikely in practice
// stat.Gas.Min = math.MaxInt64 // sentinel values for next step
// msgStats[msgType] = stat
// }
//
// // update msg stats and get global tally
// totalIncluded, totalSuccess, totalFailed := 0, 0, 0
// totalSent := len(sentTxs)
// avgGasPerTx := 0.0
// for _, blockReceipts := range receipts {
// for _, receipt := range blockReceipts {
// var msgType loadtesttypes.MsgType
// if receipt.ContractAddress.Cmp(common.Address{}) == 0 {
// msgType = types.ContractCall
// } else {
// msgType = types.ContractCreate
// }
// stat := msgStats[msgType]
//
// // update gas values
// stat.Gas.Max = max(stat.Gas.Max, int64(receipt.GasUsed)) //nolint:gosec // G115 likely not to happen
// stat.Gas.Min = min(stat.Gas.Min, int64(receipt.GasUsed)) //nolint:gosec // G115 likely not to happen
// stat.Gas.Total += int64(receipt.GasUsed) //nolint:gosec // G115 likely not to happen
//
// // inclusion and statuses.
// stat.Transactions.TotalIncluded++
// totalIncluded++
// if receipt.Status == gethtypes.ReceiptStatusSuccessful {
// totalSuccess++
// stat.Transactions.Successful++
// } else {
// totalFailed++
// stat.Transactions.Failed++
// }
//
// // gas average
// stat.Gas.Average = stat.Gas.Total / int64(stat.Transactions.TotalIncluded)
// avgGasPerTx += (float64(receipt.GasUsed) - avgGasPerTx) / float64(totalIncluded)
// msgStats[msgType] = stat
// }
// }
//
// // calculate statistics for ALL txs by type. (totals)
// // here we are using transactions from the blocks to update each msg type's statistics.
// avgGasUtilization := 0.0
// for i, blockStat := range blockStats {
// // rolling average of gas utilization.
// avgGasUtilization += (blockStat.GasUtilization - avgGasUtilization) / float64(i+1)
// }
//
// // timings / tps.
// startTime := blockStats[0].Timestamp
// endTime := blockStats[len(blockStats)-1].Timestamp
// runtime := endTime.Sub(startTime)
// tps := float64(totalIncluded) / runtime.Seconds()
//
// // final results.
// result := &loadtesttypes.LoadTestResult{
// Overall: loadtesttypes.OverallStats{
// TotalTransactions: totalSent,
// TotalIncludedTransactions: totalIncluded,
// SuccessfulTransactions: totalSuccess,
// FailedTransactions: totalFailed,
// AvgBlockGasUtilization: avgGasUtilization,
// AvgGasPerTransaction: int64(avgGasPerTx),
// Runtime: runtime,
// StartTime: startTime,
// EndTime: endTime,
// BlocksProcessed: len(blockStats),
// TPS: tps,
// },
// ByMessage: msgStats,
// ByNode: nil, // TODO: we aren't differentiating on node at the moment. not supported.
// ByBlock: blockStats,
// }
//
// return result, nil
return &loadtesttypes.LoadTestResult{}, nil
}

func buildBlockStats(block *gethtypes.Block, receipts gethtypes.Receipts) loadtesttypes.BlockStat {

Check failure on line 142 in chains/ethereum/metrics/collector.go

View workflow job for this annotation

GitHub Actions / lint

func buildBlockStats is unused (unused)
msgStats := make(map[loadtesttypes.MsgType]loadtesttypes.MessageBlockStats)
for _, r := range receipts {
// if the receipt didnt have a created contract address, its a contract call receipt.
Expand Down Expand Up @@ -170,7 +169,7 @@
return stats
}

func getReceiptsForBlockTxs(ctx context.Context, block *gethtypes.Block, client wallet.Client) ([]*gethtypes.Receipt, error) {

Check failure on line 172 in chains/ethereum/metrics/collector.go

View workflow job for this annotation

GitHub Actions / lint

func getReceiptsForBlockTxs is unused (unused)
txs := block.Transactions()
receipts := make([]*gethtypes.Receipt, 0, len(txs))
for _, tx := range txs {
Expand Down Expand Up @@ -216,7 +215,7 @@
}

// returns the total amount of transactions sent for each type.
func calculateTotalSentByType(sentTxs []*types.SentTx) map[loadtesttypes.MsgType]uint64 {

Check failure on line 218 in chains/ethereum/metrics/collector.go

View workflow job for this annotation

GitHub Actions / lint

func calculateTotalSentByType is unused (unused)
totalSentByType := make(map[loadtesttypes.MsgType]uint64)
for _, tx := range sentTxs {
if tx.Tx.To() == nil { // no To == contract creation
Expand Down
Loading