Skip to content

Commit

Permalink
Add gas per second estimation in metrics (#3401)
Browse files Browse the repository at this point in the history
* add gas estimation in metrics

* fix linter
  • Loading branch information
agnusmor authored Feb 29, 2024
1 parent f4f44b5 commit f4dd705
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 32 deletions.
27 changes: 19 additions & 8 deletions sequencer/finalizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -483,12 +483,17 @@ func (f *finalizer) processTransaction(ctx context.Context, tx *TxTracker, first

// handleProcessTransactionResponse handles the response of transaction processing.
func (f *finalizer) handleProcessTransactionResponse(ctx context.Context, tx *TxTracker, result *state.ProcessBatchResponse, oldStateRoot common.Hash) (errWg *sync.WaitGroup, err error) {
txResponse := result.BlockResponses[0].TransactionResponses[0]

// Update metrics
f.wipL2Block.metrics.processedTxsCount++

// Handle Transaction Error
errorCode := executor.RomErrorCode(result.BlockResponses[0].TransactionResponses[0].RomError)
errorCode := executor.RomErrorCode(txResponse.RomError)
if !state.IsStateRootChanged(errorCode) {
// If intrinsic error or OOC error, we skip adding the transaction to the batch
errWg = f.handleProcessTransactionError(ctx, result, tx)
return errWg, result.BlockResponses[0].TransactionResponses[0].RomError
return errWg, txResponse.RomError
}

egpEnabled := f.effectiveGasPrice.IsEnabled()
Expand All @@ -499,7 +504,7 @@ func (f *finalizer) handleProcessTransactionResponse(ctx context.Context, tx *Tx
// Get the tx gas price we will use in the egp calculation. If egp is disabled we will use a "simulated" tx gas price
txGasPrice, txL2GasPrice := f.effectiveGasPrice.GetTxAndL2GasPrice(tx.GasPrice, tx.L1GasPrice, tx.L2GasPrice)

newEffectiveGasPrice, err := f.effectiveGasPrice.CalculateEffectiveGasPrice(tx.RawTx, txGasPrice, result.BlockResponses[0].TransactionResponses[0].GasUsed, tx.L1GasPrice, txL2GasPrice)
newEffectiveGasPrice, err := f.effectiveGasPrice.CalculateEffectiveGasPrice(tx.RawTx, txGasPrice, txResponse.GasUsed, tx.L1GasPrice, txL2GasPrice)
if err != nil {
if egpEnabled {
log.Errorf("failed to calculate effective gas price with new gasUsed for tx %s, error: %v", tx.HashStr, err.Error())
Expand All @@ -511,9 +516,9 @@ func (f *finalizer) handleProcessTransactionResponse(ctx context.Context, tx *Tx
} else {
// Save new (second) gas used and second effective gas price calculation for later logging
tx.EGPLog.ValueSecond.Set(newEffectiveGasPrice)
tx.EGPLog.GasUsedSecond = result.BlockResponses[0].TransactionResponses[0].GasUsed
tx.EGPLog.GasUsedSecond = txResponse.GasUsed

errCompare := f.compareTxEffectiveGasPrice(ctx, tx, newEffectiveGasPrice, result.BlockResponses[0].TransactionResponses[0].HasGaspriceOpcode, result.BlockResponses[0].TransactionResponses[0].HasBalanceOpcode)
errCompare := f.compareTxEffectiveGasPrice(ctx, tx, newEffectiveGasPrice, txResponse.HasGaspriceOpcode, txResponse.HasBalanceOpcode)

// If EffectiveGasPrice is disabled we will calculate the percentage and save it for later logging
if !egpEnabled {
Expand Down Expand Up @@ -572,14 +577,14 @@ func (f *finalizer) handleProcessTransactionResponse(ctx context.Context, tx *Tx
// If reserved tx resources don't fit in the remaining batch resources (or we got an overflow when trying to subtract the used resources)
// we update the ZKCounters of the tx and returns ErrBatchResourceOverFlow error
if !fits || subOverflow {
f.workerIntf.UpdateTxZKCounters(result.BlockResponses[0].TransactionResponses[0].TxHash, tx.From, result.UsedZkCounters, result.ReservedZkCounters)
f.workerIntf.UpdateTxZKCounters(txResponse.TxHash, tx.From, result.UsedZkCounters, result.ReservedZkCounters)
return nil, ErrBatchResourceOverFlow
}

// Save Enabled, GasPriceOC, BalanceOC and final effective gas price for later logging
tx.EGPLog.Enabled = egpEnabled
tx.EGPLog.GasPriceOC = result.BlockResponses[0].TransactionResponses[0].HasGaspriceOpcode
tx.EGPLog.BalanceOC = result.BlockResponses[0].TransactionResponses[0].HasBalanceOpcode
tx.EGPLog.GasPriceOC = txResponse.HasGaspriceOpcode
tx.EGPLog.BalanceOC = txResponse.HasBalanceOpcode
tx.EGPLog.ValueFinal.Set(tx.EffectiveGasPrice)

// Log here the results of EGP calculation
Expand All @@ -593,6 +598,9 @@ func (f *finalizer) handleProcessTransactionResponse(ctx context.Context, tx *Tx

f.updateWorkerAfterSuccessfulProcessing(ctx, tx.Hash, tx.From, false, result)

// Update metrics
f.wipL2Block.metrics.gas += txResponse.GasUsed

return nil, nil
}

Expand Down Expand Up @@ -720,6 +728,9 @@ func (f *finalizer) handleProcessTransactionError(ctx context.Context, result *s
}()
}

// Update metrics
f.wipL2Block.metrics.gas += txResponse.GasUsed

return wg
}

Expand Down
66 changes: 42 additions & 24 deletions sequencer/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package sequencer

import (
"fmt"
"math"
"time"
)

Expand Down Expand Up @@ -34,28 +35,35 @@ func (p *processTimes) sumUp(ptSumUp processTimes) {

type metrics struct {
closedAt time.Time
txsCount int64
processedTxsCount int64
l2BlockTxsCount int64
idleTime time.Duration
newL2BlockTimes processTimes
transactionsTimes processTimes
l2BlockTimes processTimes
gas uint64
estimatedTxsPerSec float64
estimatedGasPerSec uint64
}

func (m *metrics) sub(mSub metrics) {
m.txsCount -= mSub.txsCount
m.processedTxsCount -= mSub.processedTxsCount
m.l2BlockTxsCount -= mSub.l2BlockTxsCount
m.idleTime -= mSub.idleTime
m.newL2BlockTimes.sub(mSub.newL2BlockTimes)
m.transactionsTimes.sub(mSub.transactionsTimes)
m.l2BlockTimes.sub(mSub.l2BlockTimes)
m.gas -= mSub.gas
}

func (m *metrics) sumUp(mSumUp metrics) {
m.txsCount += mSumUp.txsCount
m.processedTxsCount += mSumUp.processedTxsCount
m.l2BlockTxsCount += mSumUp.l2BlockTxsCount
m.idleTime += mSumUp.idleTime
m.newL2BlockTimes.sumUp(mSumUp.newL2BlockTimes)
m.transactionsTimes.sumUp(mSumUp.transactionsTimes)
m.l2BlockTimes.sumUp(mSumUp.l2BlockTimes)
m.gas += mSumUp.gas
}

func (m *metrics) executorTime() time.Duration {
Expand All @@ -70,27 +78,32 @@ func (m *metrics) totalTime() time.Duration {
return m.newL2BlockTimes.total() + m.transactionsTimes.total() + m.l2BlockTimes.total() + m.idleTime
}

func (m *metrics) close(createdAt time.Time, txsCount int64) {
func (m *metrics) close(createdAt time.Time, l2BlockTxsCount int64) {
// Compute pending fields
m.closedAt = time.Now()
totalTime := time.Since(createdAt)
m.txsCount = txsCount
m.l2BlockTxsCount = l2BlockTxsCount
m.transactionsTimes.sequencer = totalTime - m.idleTime - m.newL2BlockTimes.total() - m.transactionsTimes.executor - m.l2BlockTimes.total()

// Compute performance
if m.txsCount > 0 {
if m.processedTxsCount > 0 {
// timePerTxuS is the average time spent per tx. This includes the l2Block time since the processing time of this section is proportional to the number of txs
timePerTxuS := (m.transactionsTimes.total() + m.l2BlockTimes.total()).Microseconds() / m.txsCount
timePerTxuS := (m.transactionsTimes.total() + m.l2BlockTimes.total()).Microseconds() / m.processedTxsCount
// estimatedTxs is the number of transactions that we estimate could have been processed in the block
estimatedTxs := float64(totalTime.Microseconds()-m.newL2BlockTimes.total().Microseconds()) / float64(timePerTxuS)
// estimatedTxxPerSec is the estimated transactions per second
m.estimatedTxsPerSec = estimatedTxs / totalTime.Seconds()
// estimatedTxxPerSec is the estimated transactions per second (rounded to 2 decimal digits)
m.estimatedTxsPerSec = math.Ceil(estimatedTxs/totalTime.Seconds()*100) / 100 //nolint:gomnd

// gasPerTx is the average gas used per tx
gasPerTx := m.gas / uint64(m.processedTxsCount)
// estimatedGasPerSec is the estimated gas per second
m.estimatedGasPerSec = uint64(m.estimatedTxsPerSec * float64(gasPerTx))
}
}

func (m *metrics) log() string {
return fmt.Sprintf("txs: %d, estimated txs/s: %.1f, time: {total: %d, idle: %d, sequencer: {total: %d, newL2Block: %d, txs: %d, l2Block: %d}, executor: {total: %d, newL2Block: %d, txs: %d, l2Block: %d}",
m.txsCount, m.estimatedTxsPerSec, m.totalTime().Microseconds(), m.idleTime.Microseconds(),
return fmt.Sprintf("blockTxs: %d, txs: %d, gas: %d, txsSec: %.2f, gasSec: %d, time: {total: %d, idle: %d, sequencer: {total: %d, newL2Block: %d, txs: %d, l2Block: %d}, executor: {total: %d, newL2Block: %d, txs: %d, l2Block: %d}",
m.l2BlockTxsCount, m.processedTxsCount, m.gas, m.estimatedTxsPerSec, m.estimatedGasPerSec, m.totalTime().Microseconds(), m.idleTime.Microseconds(),
m.sequencerTime().Microseconds(), m.newL2BlockTimes.sequencer.Microseconds(), m.transactionsTimes.sequencer.Microseconds(), m.l2BlockTimes.sequencer.Microseconds(),
m.executorTime().Microseconds(), m.newL2BlockTimes.executor.Microseconds(), m.transactionsTimes.executor.Microseconds(), m.l2BlockTimes.executor.Microseconds())
}
Expand All @@ -99,8 +112,9 @@ type intervalMetrics struct {
l2Blocks []*metrics
maxInterval time.Duration
metrics
estimatedTxsPerSecAcc float64
estimatedTxsPerSecCount int64
estimatedTxsPerSecAcc float64
estimatedGasPerSecAcc uint64
l2BlockCountAcc int64
}

func newIntervalMetrics(maxInterval time.Duration) *intervalMetrics {
Expand All @@ -122,9 +136,10 @@ func (i *intervalMetrics) cleanUp() {
if l2Block.closedAt.Add(i.maxInterval).Before(now) {
// Subtract l2Block metrics from accumulated values
i.sub(*l2Block)
if l2Block.txsCount > 0 {
i.estimatedTxsPerSecAcc -= i.estimatedTxsPerSec
i.estimatedTxsPerSecCount--
if l2Block.processedTxsCount > 0 {
i.estimatedTxsPerSecAcc -= l2Block.estimatedTxsPerSec
i.estimatedGasPerSecAcc -= l2Block.estimatedGasPerSec
i.l2BlockCountAcc--
}
// Remove from l2Blocks
i.l2Blocks = i.l2Blocks[1:]
Expand All @@ -136,28 +151,31 @@ func (i *intervalMetrics) cleanUp() {

if ct > 0 {
// Compute performance
i.computeEstimatedTxsPerSec()
i.computePerformance()
}
}

func (i *intervalMetrics) addL2BlockMetrics(l2Block metrics) {
i.cleanUp()

i.sumUp(l2Block)
if l2Block.txsCount > 0 {
if l2Block.processedTxsCount > 0 {
i.estimatedTxsPerSecAcc += l2Block.estimatedTxsPerSec
i.estimatedTxsPerSecCount++
i.computeEstimatedTxsPerSec()
i.estimatedGasPerSecAcc += l2Block.estimatedGasPerSec
i.l2BlockCountAcc++
i.computePerformance()
}

i.l2Blocks = append(i.l2Blocks, &l2Block)
}

func (i *intervalMetrics) computeEstimatedTxsPerSec() {
if i.estimatedTxsPerSecCount > 0 {
i.estimatedTxsPerSec = i.estimatedTxsPerSecAcc / float64(i.estimatedTxsPerSecCount)
func (i *intervalMetrics) computePerformance() {
if i.l2BlockCountAcc > 0 {
i.estimatedTxsPerSec = math.Ceil(i.estimatedTxsPerSecAcc/float64(i.l2BlockCountAcc)*100) / 100 //nolint:gomnd
i.estimatedGasPerSec = i.estimatedGasPerSecAcc / uint64(i.l2BlockCountAcc)
} else {
i.estimatedTxsPerSecCount = 0
i.estimatedTxsPerSec = 0
i.estimatedGasPerSec = 0
}
}

Expand Down

0 comments on commit f4dd705

Please sign in to comment.