Skip to content

Commit

Permalink
Use histogram from Besu plugin API
Browse files Browse the repository at this point in the history
Signed-off-by: Fabio Di Fabio <[email protected]>
  • Loading branch information
fab-10 committed Oct 28, 2024
1 parent f84cf0b commit 160032e
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 120 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ public void start() {
besuConfiguration, metricsSystem, profitabilityConfiguration());

final var transactionPoolProfitabilityMetrics =
new TransactionPoolProfitabilityMetrics(besuConfiguration, metricsSystem, profitabilityConfiguration());
new TransactionPoolProfitabilityMetrics(
besuConfiguration, metricsSystem, profitabilityConfiguration());

try (Stream<String> lines =
Files.lines(
Expand Down Expand Up @@ -145,15 +146,16 @@ public void start() {
});

besuEventsService.addTransactionAddedListener(
transaction -> {
try {
transactionPoolProfitabilityMetrics.handleTransactionAdded(transaction);
} catch (Exception e) {
log.warn("Error recording transaction profitability metrics for {}: {}",
transaction.getHash(), e.getMessage());
}
}
);
transaction -> {
try {
transactionPoolProfitabilityMetrics.handleTransactionAdded(transaction);
} catch (Exception e) {
log.warn(
"Error recording transaction profitability metrics for {}: {}",
transaction.getHash(),
e.getMessage());
}
});

} catch (Exception e) {
throw new RuntimeException(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package net.consensys.linea.sequencer.txpoolvalidation.metrics;

import java.util.concurrent.atomic.AtomicReference;

import lombok.extern.slf4j.Slf4j;
import net.consensys.linea.bl.TransactionProfitabilityCalculator;
import net.consensys.linea.config.LineaProfitabilityConfiguration;
Expand All @@ -24,20 +25,17 @@
import org.hyperledger.besu.plugin.services.BesuConfiguration;
import org.hyperledger.besu.plugin.services.MetricsSystem;
import org.hyperledger.besu.plugin.services.metrics.Counter;
import org.hyperledger.besu.plugin.services.metrics.LabelledMetric;
import org.hyperledger.besu.plugin.services.metrics.Histogram;
import org.hyperledger.besu.plugin.services.metrics.LabelledGauge;
import org.hyperledger.besu.plugin.services.metrics.Summary;
import org.hyperledger.besu.plugin.services.metrics.LabelledMetric;

/**
* Tracks profitability metrics for transactions in the transaction pool.
* Specifically monitors the ratio of profitable priority fee to actual priority fee:
* Tracks profitability metrics for transactions in the transaction pool. Specifically monitors the
* ratio of profitable priority fee to actual priority fee:
* profitablePriorityFeePerGas/transaction.priorityFeePerGas
* <p>
* Provides:
* - Lowest ratio seen (minimum profitability)
* - Highest ratio seen (maximum profitability)
* - Running average ratio (average profitability)
* - Distribution histogram of ratios
*
* <p>Provides: - Lowest ratio seen (minimum profitability) - Highest ratio seen (maximum
* profitability) - Distribution histogram of ratios
*/
@Slf4j
public class TransactionPoolProfitabilityMetrics {
Expand All @@ -47,68 +45,51 @@ public class TransactionPoolProfitabilityMetrics {
private final LineaProfitabilityConfiguration profitabilityConf;
private final BesuConfiguration besuConfiguration;

private final LabelledMetric<Counter> profitabilityHistogram;

private final LabelledMetric<Summary> profitabilityRatioSummary;
private final LabelledMetric<Histogram> profitabilityHistogram;
private final Counter invalidTransactionCount;

// Thread-safe references for gauge values
private final AtomicReference<Double> currentLowest = new AtomicReference<>(Double.MAX_VALUE);
private final AtomicReference<Double> currentHighest = new AtomicReference<>(0.0);

public TransactionPoolProfitabilityMetrics(
final BesuConfiguration besuConfiguration,
final MetricsSystem metricsSystem,
final LineaProfitabilityConfiguration profitabilityConf) {
final BesuConfiguration besuConfiguration,
final MetricsSystem metricsSystem,
final LineaProfitabilityConfiguration profitabilityConf) {

this.besuConfiguration = besuConfiguration;
this.profitabilityConf = profitabilityConf;
this.profitabilityCalculator = new TransactionProfitabilityCalculator(profitabilityConf);

// Distribution histogram
this.profitabilityHistogram = metricsSystem.createLabelledCounter(
LineaMetricCategory.PROFITABILITY,
"txpool_profitability_ratio",
"Distribution of transaction profitability ratios in TxPool",
"bucket"
);

// Min/Max/Avg gauges with DoubleSupplier
LabelledGauge lowestProfitabilityRatio = metricsSystem.createLabelledGauge(
LineaMetricCategory.PROFITABILITY,
"txpool_profitability_ratio_min",
"Lowest profitability ratio seen"
);
// Min/Max gauges with DoubleSupplier
LabelledGauge lowestProfitabilityRatio =
metricsSystem.createLabelledGauge(
LineaMetricCategory.PROFITABILITY,
"txpool_profitability_ratio_min",
"Lowest profitability ratio seen");
lowestProfitabilityRatio.labels(currentLowest::get);

LabelledGauge highestProfitabilityRatio = metricsSystem.createLabelledGauge(
LineaMetricCategory.PROFITABILITY,
"txpool_profitability_ratio_max",
"Highest profitability ratio seen"
);
LabelledGauge highestProfitabilityRatio =
metricsSystem.createLabelledGauge(
LineaMetricCategory.PROFITABILITY,
"txpool_profitability_ratio_max",
"Highest profitability ratio seen");
highestProfitabilityRatio.labels(currentHighest::get);

LabelledGauge averageProfitabilityRatio = metricsSystem.createLabelledGauge(
LineaMetricCategory.PROFITABILITY,
"txpool_profitability_ratio_avg",
"Average profitability ratio"
);
AtomicReference<Double> currentAverage = new AtomicReference<>(0.0);
averageProfitabilityRatio.labels(currentAverage::get);

// Running statistics
this.profitabilityRatioSummary = metricsSystem.createLabelledSummary(
LineaMetricCategory.PROFITABILITY,
"txpool_profitability_ratio_summary",
"Summary statistics of profitability ratios",
"type"
);

this.invalidTransactionCount = metricsSystem.createCounter(
LineaMetricCategory.PROFITABILITY,
"txpool_invalid_transaction_count",
"Number of transactions that couldn't be processed for profitability"
);
this.profitabilityHistogram =
metricsSystem.createLabelledHistogram(
LineaMetricCategory.PROFITABILITY,
"txpool_profitability_ratio_summary",
"Summary statistics of profitability ratios",
HISTOGRAM_BUCKETS,
"type");

this.invalidTransactionCount =
metricsSystem.createCounter(
LineaMetricCategory.PROFITABILITY,
"txpool_invalid_transaction_count",
"Number of transactions that couldn't be processed for profitability");

// Pre-create histogram buckets
for (double bucket : HISTOGRAM_BUCKETS) {
Expand All @@ -124,38 +105,33 @@ public void handleTransactionAdded(Transaction transaction) {
return;
}

Wei profitablePriorityFeePerGas = profitabilityCalculator.profitablePriorityFeePerGas(
transaction,
profitabilityConf.txPoolMinMargin(),
transaction.getGasLimit(),
besuConfiguration.getMinGasPrice()
);
Wei profitablePriorityFeePerGas =
profitabilityCalculator.profitablePriorityFeePerGas(
transaction,
profitabilityConf.txPoolMinMargin(),
transaction.getGasLimit(),
besuConfiguration.getMinGasPrice());

Wei actualPriorityFeePerGas = Wei.fromQuantity(transaction.getMaxPriorityFeePerGas().get());

if (actualPriorityFeePerGas.toLong() > 0) {
double ratio = profitablePriorityFeePerGas.toBigInteger().doubleValue() /
actualPriorityFeePerGas.toBigInteger().doubleValue();
double ratio =
profitablePriorityFeePerGas.toBigInteger().doubleValue()
/ actualPriorityFeePerGas.toBigInteger().doubleValue();

updateRunningStats(ratio);
updateHistogramBuckets(ratio);

log.trace(
"Recorded profitability ratio {} for tx {}",
ratio,
transaction.getHash()
);
log.trace("Recorded profitability ratio {} for tx {}", ratio, transaction.getHash());
} else {
invalidTransactionCount.inc();
log.trace("Skipping transaction {} - zero priority fee", transaction.getHash());
}
} catch (Exception e) {
invalidTransactionCount.inc();
log.warn(
"Failed to record profitability metrics for tx {}: {}",
transaction.getHash(),
e.getMessage()
);
"Failed to record profitability metrics for tx {}: {}",
transaction.getHash(),
e.getMessage());
}
}

Expand All @@ -167,14 +143,6 @@ private void updateRunningStats(double ratio) {
currentHighest.updateAndGet(current -> Math.max(current, ratio));

// Record the observation in summary
profitabilityRatioSummary.labels("profitability").observe(ratio);
}

private void updateHistogramBuckets(double ratio) {
for (double bucket : HISTOGRAM_BUCKETS) {
if (ratio <= bucket) {
profitabilityHistogram.labels(String.format("le_%.1f", bucket)).inc();
}
}
profitabilityHistogram.labels("profitability").observe(ratio);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,20 @@
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.metrics.BesuMetricCategory;
import org.hyperledger.besu.plugin.services.MetricsSystem;
import org.hyperledger.besu.plugin.services.metrics.Histogram;
import org.hyperledger.besu.plugin.services.metrics.LabelledMetric;
import org.hyperledger.besu.plugin.services.metrics.Summary;

@Slf4j
public class SelectorProfitabilityMetrics {
private final LabelledMetric<Summary> summaries;
private final LabelledMetric<Histogram> summaries;

public SelectorProfitabilityMetrics(final MetricsSystem metricsSystem) {
this.summaries =
metricsSystem.createLabelledSummary(
metricsSystem.createLabelledHistogram(
BesuMetricCategory.ETHEREUM,
"selection_priority_fee_ratio",
"selection_profitability_ratio",
"The ratio between the effective priority fee and the calculated one",
new double[] {0.9, 1.0, 1.2, 2, 5, 10, 100, 1000},
"phase");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,26 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import net.consensys.linea.config.LineaProfitabilityCliOptions;
import net.consensys.linea.config.LineaProfitabilityConfiguration;
import net.consensys.linea.config.LineaTransactionSelectorCliOptions;
import net.consensys.linea.config.LineaTransactionSelectorConfiguration;
import net.consensys.linea.sequencer.txselection.metrics.SelectorProfitabilityMetrics;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.bouncycastle.crypto.digests.KeccakDigest;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.PendingTransaction;
import org.hyperledger.besu.datatypes.Transaction;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import org.hyperledger.besu.plugin.data.ProcessableBlockHeader;
import org.hyperledger.besu.plugin.data.TransactionProcessingResult;
import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
import org.hyperledger.besu.plugin.services.BlockchainService;
import org.hyperledger.besu.plugin.services.MetricsSystem;
import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelector;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -64,11 +65,12 @@ public class ProfitableTransactionSelectorTest {
.variableCostWei(VARIABLE_GAS_COST_WEI)
.build();
private TestableProfitableTransactionSelector transactionSelector;
private Map<Hash, Wei> profitablePriorityFeeCache;
private MetricsSystem metricsSystem = new NoOpMetricsSystem();
private SelectorProfitabilityMetrics selectorProfitabilityMetrics;

@BeforeEach
public void initialize() {
profitablePriorityFeeCache = new HashMap<>();
selectorProfitabilityMetrics = new SelectorProfitabilityMetrics(metricsSystem);
transactionSelector = newSelectorForNewBlock();
transactionSelector.reset();
}
Expand All @@ -77,7 +79,7 @@ private TestableProfitableTransactionSelector newSelectorForNewBlock() {
final var blockchainService = mock(BlockchainService.class);
when(blockchainService.getNextBlockBaseFee()).thenReturn(Optional.of(BASE_FEE));
return new TestableProfitableTransactionSelector(
blockchainService, txSelectorConf, profitabilityConf, profitablePriorityFeeCache);
blockchainService, txSelectorConf, profitabilityConf, selectorProfitabilityMetrics);
}

@Test
Expand Down Expand Up @@ -335,22 +337,6 @@ public void profitableAndUnprofitableTxsMix() {
SELECTED);
}

@Test
public void shouldAddToProfitablePriorityFeeWhenSelect() {
final var mockTransactionProcessingResult = mockTransactionProcessingResult(21000);
final var mockEvaluationContext =
mockEvaluationContext(false, 100, Wei.of(1_100_000_000), Wei.of(1_000_000_000), 21000);
verifyTransactionSelection(
transactionSelector,
mockEvaluationContext,
mockTransactionProcessingResult,
SELECTED,
SELECTED);

assertThat(profitablePriorityFeeCache)
.containsOnlyKeys(mockEvaluationContext.getPendingTransaction().getTransaction().getHash());
}

private void verifyTransactionSelection(
final ProfitableTransactionSelector selector,
final TestTransactionEvaluationContext evaluationContext,
Expand Down Expand Up @@ -431,8 +417,8 @@ private static class TestableProfitableTransactionSelector extends ProfitableTra
final BlockchainService blockchainService,
final LineaTransactionSelectorConfiguration txSelectorConf,
final LineaProfitabilityConfiguration profitabilityConf,
final Map<Hash, Wei> profitablePriorityFeeCache) {
super(blockchainService, txSelectorConf, profitabilityConf, profitablePriorityFeeCache);
final SelectorProfitabilityMetrics selectorProfitabilityMetrics) {
super(blockchainService, txSelectorConf, profitabilityConf, selectorProfitabilityMetrics);
}

boolean isUnprofitableTxCached(final Hash txHash) {
Expand Down

0 comments on commit 160032e

Please sign in to comment.