From 1564c7758ead50fbff942a438acfd63bc5c8e6ac Mon Sep 17 00:00:00 2001 From: Fabio Di Fabio Date: Thu, 31 Oct 2024 18:05:12 +0100 Subject: [PATCH] Generalize histogram metrics in a single class and check for category enabled Signed-off-by: Fabio Di Fabio --- .../plugin/acc/test/LineaPluginTestBase.java | 2 +- .../test/extradata/ExtraDataPricingTest.java | 15 +- gradle.properties | 4 +- ...stractLineaSharedPrivateOptionsPlugin.java | 16 +-- .../linea/extradata/LineaExtraDataPlugin.java | 14 +- .../linea/metrics/HistogramMetrics.java | 129 ++++++++++++++++++ .../linea/metrics/LineaMetricCategory.java | 14 +- .../LineaTransactionPoolValidatorPlugin.java | 77 ++++++----- .../TransactionPoolProfitabilityMetrics.java | 79 +++-------- .../LineaTransactionSelectorFactory.java | 15 +- .../LineaTransactionSelectorPlugin.java | 9 +- .../metrics/SelectorProfitabilityMetrics.java | 105 -------------- .../selectors/LineaTransactionSelector.java | 16 ++- .../ProfitableTransactionSelector.java | 91 +++++++++--- .../ProfitableTransactionSelectorTest.java | 14 +- 15 files changed, 330 insertions(+), 270 deletions(-) create mode 100644 sequencer/src/main/java/net/consensys/linea/metrics/HistogramMetrics.java delete mode 100644 sequencer/src/main/java/net/consensys/linea/sequencer/txselection/metrics/SelectorProfitabilityMetrics.java diff --git a/acceptance-tests/src/test/java/linea/plugin/acc/test/LineaPluginTestBase.java b/acceptance-tests/src/test/java/linea/plugin/acc/test/LineaPluginTestBase.java index ff777ceb..66f52131 100644 --- a/acceptance-tests/src/test/java/linea/plugin/acc/test/LineaPluginTestBase.java +++ b/acceptance-tests/src/test/java/linea/plugin/acc/test/LineaPluginTestBase.java @@ -135,7 +135,7 @@ private BesuNode createCliqueNodeWithExtraCliOptionsAndRpcApis( .metricsConfiguration( MetricsConfiguration.builder() .enabled(true) - .metricCategories(Set.of(LineaMetricCategory.PROFITABILITY)) + .metricCategories(Set.of(LineaMetricCategory.SEQUENCER_PROFITABILITY)) .build()) .requestedPlugins( List.of( diff --git a/acceptance-tests/src/test/java/linea/plugin/acc/test/extradata/ExtraDataPricingTest.java b/acceptance-tests/src/test/java/linea/plugin/acc/test/extradata/ExtraDataPricingTest.java index c6cab90a..e05a4a35 100644 --- a/acceptance-tests/src/test/java/linea/plugin/acc/test/extradata/ExtraDataPricingTest.java +++ b/acceptance-tests/src/test/java/linea/plugin/acc/test/extradata/ExtraDataPricingTest.java @@ -15,6 +15,7 @@ package linea.plugin.acc.test.extradata; import static java.util.Map.entry; +import static net.consensys.linea.metrics.LineaMetricCategory.PRICING_CONF; import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; @@ -23,7 +24,6 @@ import linea.plugin.acc.test.LineaPluginTestBase; import linea.plugin.acc.test.TestCommandLineOptionsBuilder; -import net.consensys.linea.metrics.LineaMetricCategory; import org.apache.tuweni.bytes.Bytes32; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.tests.acceptance.dsl.account.Account; @@ -125,24 +125,17 @@ public void updateProfitabilityParamsViaExtraData() throws IOException, Interrup assertThat(getTxPoolContent()).isEmpty(); final var fixedCostMetric = - getMetricValue( - LineaMetricCategory.PROFITABILITY, "conf", List.of(entry("field", "fixed_cost_wei"))); + getMetricValue(PRICING_CONF, "conf", List.of(entry("field", "fixed_cost_wei"))); assertThat(fixedCostMetric).isEqualTo(MIN_GAS_PRICE.multiply(2).getValue().doubleValue()); final var variableCostMetric = - getMetricValue( - LineaMetricCategory.PROFITABILITY, - "conf", - List.of(entry("field", "variable_cost_wei"))); + getMetricValue(PRICING_CONF, "conf", List.of(entry("field", "variable_cost_wei"))); assertThat(variableCostMetric).isEqualTo(MIN_GAS_PRICE.getValue().doubleValue()); final var ethGasPriceMetric = - getMetricValue( - LineaMetricCategory.PROFITABILITY, - "conf", - List.of(entry("field", "eth_gas_price_wei"))); + getMetricValue(PRICING_CONF, "conf", List.of(entry("field", "eth_gas_price_wei"))); assertThat(ethGasPriceMetric).isEqualTo(MIN_GAS_PRICE.getValue().doubleValue()); } diff --git a/gradle.properties b/gradle.properties index 9b4da4f6..aa3f2215 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ -releaseVersion=0.8.0-rc4.1-local -besuVersion=24.10-develop-829db23 +releaseVersion=0.8.0-rc4.1 +besuVersion=24.10-local arithmetizationVersion=0.8.0-rc4 besuArtifactGroup=io.consensys.linea-besu distributionIdentifier=linea-sequencer diff --git a/sequencer/src/main/java/net/consensys/linea/AbstractLineaSharedPrivateOptionsPlugin.java b/sequencer/src/main/java/net/consensys/linea/AbstractLineaSharedPrivateOptionsPlugin.java index 70b3cffa..6dd1a79f 100644 --- a/sequencer/src/main/java/net/consensys/linea/AbstractLineaSharedPrivateOptionsPlugin.java +++ b/sequencer/src/main/java/net/consensys/linea/AbstractLineaSharedPrivateOptionsPlugin.java @@ -33,7 +33,6 @@ import net.consensys.linea.config.LineaTransactionPoolValidatorConfiguration; import net.consensys.linea.config.LineaTransactionSelectorCliOptions; import net.consensys.linea.config.LineaTransactionSelectorConfiguration; -import net.consensys.linea.metrics.LineaMetricCategory; import net.consensys.linea.plugins.AbstractLineaSharedOptionsPlugin; import net.consensys.linea.plugins.LineaOptionsPluginConfiguration; import org.hyperledger.besu.plugin.BesuContext; @@ -59,6 +58,7 @@ public abstract class AbstractLineaSharedPrivateOptionsPlugin extends AbstractLineaSharedOptionsPlugin { protected static BlockchainService blockchainService; protected static MetricsSystem metricsSystem; + protected static MetricCategoryRegistry metricCategoryRegistry; private static final AtomicBoolean sharedRegisterTasksDone = new AtomicBoolean(false); private static final AtomicBoolean sharedStartTasksDone = new AtomicBoolean(false); @@ -143,13 +143,13 @@ protected static void performSharedRegisterTasksOnce(final BesuContext context) new RuntimeException( "Failed to obtain BlockchainService from the BesuContext.")); - context - .getService(MetricCategoryRegistry.class) - .orElseThrow( - () -> - new RuntimeException( - "Failed to obtain MetricCategoryRegistry from the BesuContext.")) - .addMetricCategory(LineaMetricCategory.PROFITABILITY); + metricCategoryRegistry = + context + .getService(MetricCategoryRegistry.class) + .orElseThrow( + () -> + new RuntimeException( + "Failed to obtain MetricCategoryRegistry from the BesuContext.")); } @Override diff --git a/sequencer/src/main/java/net/consensys/linea/extradata/LineaExtraDataPlugin.java b/sequencer/src/main/java/net/consensys/linea/extradata/LineaExtraDataPlugin.java index bc7c69ed..505f2193 100644 --- a/sequencer/src/main/java/net/consensys/linea/extradata/LineaExtraDataPlugin.java +++ b/sequencer/src/main/java/net/consensys/linea/extradata/LineaExtraDataPlugin.java @@ -15,13 +15,14 @@ package net.consensys.linea.extradata; +import static net.consensys.linea.metrics.LineaMetricCategory.PRICING_CONF; + import java.util.concurrent.atomic.AtomicBoolean; import com.google.auto.service.AutoService; import lombok.extern.slf4j.Slf4j; import net.consensys.linea.AbstractLineaRequiredPlugin; import net.consensys.linea.config.LineaProfitabilityConfiguration; -import org.hyperledger.besu.metrics.BesuMetricCategory; import org.hyperledger.besu.plugin.BesuContext; import org.hyperledger.besu.plugin.BesuPlugin; import org.hyperledger.besu.plugin.data.AddedBlockContext; @@ -46,6 +47,8 @@ public void doRegister(final BesuContext context) { () -> new RuntimeException( "Failed to obtain RpcEndpointService from the BesuContext.")); + + metricCategoryRegistry.addMetricCategory(PRICING_CONF); } /** @@ -94,16 +97,15 @@ public synchronized void onInitialSyncRestart() { }); } - initMetrics(profitabilityConfiguration()); + if (metricCategoryRegistry.isMetricCategoryEnabled(PRICING_CONF)) { + initMetrics(profitabilityConfiguration()); + } } private void initMetrics(final LineaProfitabilityConfiguration lineaProfitabilityConfiguration) { final var confLabelledGauge = metricsSystem.createLabelledGauge( - BesuMetricCategory.ETHEREUM, - "conf", - "Profitability configuration values at runtime", - "field"); + PRICING_CONF, "values", "Profitability configuration values at runtime", "field"); confLabelledGauge.labels(lineaProfitabilityConfiguration::fixedCostWei, "fixed_cost_wei"); confLabelledGauge.labels(lineaProfitabilityConfiguration::variableCostWei, "variable_cost_wei"); confLabelledGauge.labels(lineaProfitabilityConfiguration::ethGasPriceWei, "eth_gas_price_wei"); diff --git a/sequencer/src/main/java/net/consensys/linea/metrics/HistogramMetrics.java b/sequencer/src/main/java/net/consensys/linea/metrics/HistogramMetrics.java new file mode 100644 index 00000000..0bcd51f3 --- /dev/null +++ b/sequencer/src/main/java/net/consensys/linea/metrics/HistogramMetrics.java @@ -0,0 +1,129 @@ +/* + * Copyright Consensys Software Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.consensys.linea.metrics; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.metrics.Histogram; +import org.hyperledger.besu.plugin.services.metrics.LabelledGauge; +import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; + +@Slf4j +public class HistogramMetrics { + + public interface LabelValue { + String value(); + } + + private static final double[] DEFAULT_HISTOGRAM_BUCKETS = {0.9, 1.0, 1.2, 2, 5, 10, 100, 1000}; + private static final String LABEL_VALUES_SEPARATOR = "\u2060"; + private final LabelledMetric histogram; + private final Map mins; + private final Map maxs; + + @SafeVarargs + public HistogramMetrics( + final MetricsSystem metricsSystem, + final LineaMetricCategory category, + final String name, + final String help, + final Class... labels) { + + final var labelNames = getLabelNames(labels); + + final LabelledGauge minRatio = + metricsSystem.createLabelledGauge(category, name + "_min", "Lowest " + help, labelNames); + + final LabelledGauge maxRatio = + metricsSystem.createLabelledGauge(category, name + "_max", "Highest " + help, labelNames); + + final var combinations = getLabelValuesCombinations(labels); + mins = HashMap.newHashMap(combinations.size()); + maxs = HashMap.newHashMap(combinations.size()); + for (final var combination : combinations) { + final var key = String.join(LABEL_VALUES_SEPARATOR, combination); + mins.put(key, Double.POSITIVE_INFINITY); + minRatio.labels(() -> mins.get(key), combination); + maxs.put(key, 0.0); + maxRatio.labels(() -> maxs.get(key), combination); + } + + this.histogram = + metricsSystem.createLabelledHistogram( + category, + name, + StringUtils.capitalize(help) + " buckets", + DEFAULT_HISTOGRAM_BUCKETS, + labelNames); + } + + @SafeVarargs + private String[] getLabelNames(final Class... labels) { + return Arrays.stream(labels) + .map(Class::getSimpleName) + .map(sn -> sn.toLowerCase(Locale.ROOT)) + .toArray(String[]::new); + } + + @SafeVarargs + private List getLabelValuesCombinations(final Class... labels) { + if (labels.length == 0) { + return Collections.singletonList(new String[0]); + } + if (labels.length == 1) { + return Arrays.stream(labels[0].getEnumConstants()) + .map(lv -> new String[] {lv.value()}) + .toList(); + } + final var head = labels[0]; + final var tail = Arrays.copyOfRange(labels, 1, labels.length); + final var tailCombinations = getLabelValuesCombinations(tail); + final int newSize = tailCombinations.size() * head.getEnumConstants().length; + final List combinations = new ArrayList<>(newSize); + for (final var headValue : head.getEnumConstants()) { + for (final var tailValues : tailCombinations) { + final var combination = new String[tailValues.length + 1]; + combination[0] = headValue.value(); + System.arraycopy(tailValues, 0, combination, 1, tailValues.length); + combinations.add(combination); + } + } + return combinations; + } + + public void track(final double value, final String... labelValues) { + + final var key = String.join(LABEL_VALUES_SEPARATOR, labelValues); + + // Update lowest seen + mins.compute(key, (unused, currMin) -> Math.min(currMin, value)); + + // Update highest seen + maxs.compute(key, (unused, currMax) -> Math.max(currMax, value)); + + // Record the observation + histogram.labels(labelValues).observe(value); + } +} diff --git a/sequencer/src/main/java/net/consensys/linea/metrics/LineaMetricCategory.java b/sequencer/src/main/java/net/consensys/linea/metrics/LineaMetricCategory.java index 31ade658..849e7ddb 100644 --- a/sequencer/src/main/java/net/consensys/linea/metrics/LineaMetricCategory.java +++ b/sequencer/src/main/java/net/consensys/linea/metrics/LineaMetricCategory.java @@ -14,19 +14,25 @@ */ package net.consensys.linea.metrics; +import java.util.Locale; import java.util.Optional; import org.hyperledger.besu.plugin.services.metrics.MetricCategory; public enum LineaMetricCategory implements MetricCategory { - /** Profitability metric category */ - PROFITABILITY("profitability"); + /** Sequencer profitability metric category */ + SEQUENCER_PROFITABILITY, + /** Tx pool profitability metric category */ + TX_POOL_PROFITABILITY, + /** Runtime pricing configuration */ + PRICING_CONF; private static final Optional APPLICATION_PREFIX = Optional.of("linea_"); + private final String name; - LineaMetricCategory(final String name) { - this.name = name; + LineaMetricCategory() { + this.name = name().toLowerCase(Locale.ROOT); } @Override diff --git a/sequencer/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/LineaTransactionPoolValidatorPlugin.java b/sequencer/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/LineaTransactionPoolValidatorPlugin.java index f31328e9..82392cdc 100644 --- a/sequencer/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/LineaTransactionPoolValidatorPlugin.java +++ b/sequencer/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/LineaTransactionPoolValidatorPlugin.java @@ -15,6 +15,7 @@ package net.consensys.linea.sequencer.txpoolvalidation; +import static net.consensys.linea.metrics.LineaMetricCategory.TX_POOL_PROFITABILITY; import static net.consensys.linea.sequencer.modulelimit.ModuleLineCountValidator.createLimitModules; import java.io.File; @@ -49,7 +50,6 @@ @Slf4j @AutoService(BesuPlugin.class) public class LineaTransactionPoolValidatorPlugin extends AbstractLineaRequiredPlugin { - public static final String NAME = "linea"; private BesuContext besuContext; private BesuConfiguration besuConfiguration; private TransactionPoolValidatorService transactionPoolValidatorService; @@ -82,28 +82,14 @@ public void doRegister(final BesuContext context) { () -> new RuntimeException( "Failed to obtain TransactionSimulatorService from the BesuContext.")); + + metricCategoryRegistry.addMetricCategory(TX_POOL_PROFITABILITY); } @Override public void start() { super.start(); - final var transactionPoolService = - besuContext - .getService(TransactionPoolService.class) - .orElseThrow( - () -> - new RuntimeException( - "Failed to obtain TransactionPoolService from the BesuContext.")); - - final var transactionPoolProfitabilityMetrics = - new TransactionPoolProfitabilityMetrics( - besuConfiguration, - metricsSystem, - profitabilityConfiguration(), - transactionPoolService, - blockchainService); - try (Stream lines = Files.lines( Path.of(new File(transactionPoolValidatorConfiguration().denyListPath()).toURI()))) { @@ -135,25 +121,44 @@ public void start() { l1L2BridgeSharedConfiguration(), rejectedTxJsonRpcManager)); - final var besuEventsService = - besuContext - .getService(BesuEvents.class) - .orElseThrow( - () -> new RuntimeException("Failed to obtain BesuEvents from the BesuContext.")); - - besuEventsService.addBlockAddedListener( - addedBlockContext -> { - try { - // on new block let's calculate profitability for every txs in the pool - transactionPoolProfitabilityMetrics.recalculate(); - } catch (final Exception e) { - log.warn( - "Error calculating transaction profitability for block {}({})", - addedBlockContext.getBlockHeader().getNumber(), - addedBlockContext.getBlockHeader().getBlockHash(), - e); - } - }); + if (metricCategoryRegistry.isMetricCategoryEnabled(TX_POOL_PROFITABILITY)) { + final var besuEventsService = + besuContext + .getService(BesuEvents.class) + .orElseThrow( + () -> + new RuntimeException("Failed to obtain BesuEvents from the BesuContext.")); + + final var transactionPoolService = + besuContext + .getService(TransactionPoolService.class) + .orElseThrow( + () -> + new RuntimeException( + "Failed to obtain TransactionPoolService from the BesuContext.")); + + final var transactionPoolProfitabilityMetrics = + new TransactionPoolProfitabilityMetrics( + besuConfiguration, + metricsSystem, + profitabilityConfiguration(), + transactionPoolService, + blockchainService); + + besuEventsService.addBlockAddedListener( + addedBlockContext -> { + try { + // on new block let's calculate profitability for every txs in the pool + transactionPoolProfitabilityMetrics.update(); + } catch (final Exception e) { + log.warn( + "Error calculating transaction profitability for block {}({})", + addedBlockContext.getBlockHeader().getNumber(), + addedBlockContext.getBlockHeader().getBlockHash(), + e); + } + }); + } } catch (Exception e) { throw new RuntimeException(e); diff --git a/sequencer/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/metrics/TransactionPoolProfitabilityMetrics.java b/sequencer/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/metrics/TransactionPoolProfitabilityMetrics.java index b92da985..fb4d1122 100644 --- a/sequencer/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/metrics/TransactionPoolProfitabilityMetrics.java +++ b/sequencer/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/metrics/TransactionPoolProfitabilityMetrics.java @@ -14,22 +14,19 @@ */ package net.consensys.linea.sequencer.txpoolvalidation.metrics; -import java.util.concurrent.atomic.AtomicReference; +import static net.consensys.linea.metrics.LineaMetricCategory.TX_POOL_PROFITABILITY; import lombok.extern.slf4j.Slf4j; import net.consensys.linea.bl.TransactionProfitabilityCalculator; import net.consensys.linea.config.LineaProfitabilityConfiguration; +import net.consensys.linea.metrics.HistogramMetrics; import org.apache.tuweni.units.bigints.UInt256s; import org.hyperledger.besu.datatypes.PendingTransaction; import org.hyperledger.besu.datatypes.Transaction; import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.metrics.BesuMetricCategory; import org.hyperledger.besu.plugin.services.BesuConfiguration; import org.hyperledger.besu.plugin.services.BlockchainService; import org.hyperledger.besu.plugin.services.MetricsSystem; -import org.hyperledger.besu.plugin.services.metrics.Histogram; -import org.hyperledger.besu.plugin.services.metrics.LabelledGauge; -import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; import org.hyperledger.besu.plugin.services.transactionpool.TransactionPoolService; /** @@ -42,19 +39,12 @@ */ @Slf4j public class TransactionPoolProfitabilityMetrics { - private static final double[] HISTOGRAM_BUCKETS = {0.1, 0.5, 0.8, 1.0, 1.2, 1.5, 2.0, 5.0, 10.0}; - private final TransactionProfitabilityCalculator profitabilityCalculator; private final LineaProfitabilityConfiguration profitabilityConf; private final BesuConfiguration besuConfiguration; private final TransactionPoolService transactionPoolService; private final BlockchainService blockchainService; - - private final LabelledMetric profitabilityHistogram; - - // Thread-safe references for gauge values - private final AtomicReference currentLowest = new AtomicReference<>(0.0); - private final AtomicReference currentHighest = new AtomicReference<>(0.0); + private final HistogramMetrics histogramMetrics; public TransactionPoolProfitabilityMetrics( final BesuConfiguration besuConfiguration, @@ -68,33 +58,25 @@ public TransactionPoolProfitabilityMetrics( this.profitabilityCalculator = new TransactionProfitabilityCalculator(profitabilityConf); this.transactionPoolService = transactionPoolService; this.blockchainService = blockchainService; + this.histogramMetrics = + new HistogramMetrics( + metricsSystem, TX_POOL_PROFITABILITY, "ratio", "transaction pool profitability ratio"); + } - // Min/Max/Avg gauges with DoubleSupplier - LabelledGauge lowestProfitabilityRatio = - metricsSystem.createLabelledGauge( - BesuMetricCategory.ETHEREUM, - "txpool_profitability_ratio_min", - "Lowest profitability ratio seen"); - lowestProfitabilityRatio.labels(currentLowest::get); - - LabelledGauge highestProfitabilityRatio = - metricsSystem.createLabelledGauge( - BesuMetricCategory.ETHEREUM, - "txpool_profitability_ratio_max", - "Highest profitability ratio seen"); - highestProfitabilityRatio.labels(currentHighest::get); - - // Running statistics - this.profitabilityHistogram = - metricsSystem.createLabelledHistogram( - BesuMetricCategory.ETHEREUM, - "txpool_profitability_ratio", - "Histogram statistics of profitability ratios", - HISTOGRAM_BUCKETS, - "type"); + public void update() { + final long startTime = System.currentTimeMillis(); + final var txPoolContent = transactionPoolService.getPendingTransactions(); + txPoolContent.parallelStream() + .map(PendingTransaction::getTransaction) + .forEach(this::handleTransaction); + log.atDebug() + .setMessage("Transaction pool profitability metrics processed {}txs in {}ms") + .addArgument(txPoolContent::size) + .addArgument(() -> System.currentTimeMillis() - startTime) + .log(); } - public void handleTransaction(final Transaction transaction) { + private void handleTransaction(final Transaction transaction) { final Wei actualPriorityFeePerGas; if (transaction.getMaxPriorityFeePerGas().isEmpty()) { actualPriorityFeePerGas = @@ -109,36 +91,19 @@ public void handleTransaction(final Transaction transaction) { Wei.fromQuantity(transaction.getMaxFeePerGas().orElseThrow())); } - Wei profitablePriorityFeePerGas = + final Wei profitablePriorityFeePerGas = profitabilityCalculator.profitablePriorityFeePerGas( transaction, profitabilityConf.txPoolMinMargin(), transaction.getGasLimit(), besuConfiguration.getMinGasPrice()); - double ratio = + final double ratio = actualPriorityFeePerGas.toBigInteger().doubleValue() / profitablePriorityFeePerGas.toBigInteger().doubleValue(); - updateRunningStats(ratio); + histogramMetrics.track(ratio); log.trace("Recorded profitability ratio {} for tx {}", ratio, transaction.getHash()); } - - private void updateRunningStats(double ratio) { - // Update lowest seen - currentLowest.updateAndGet(current -> Math.min(current, ratio)); - - // Update highest seen - currentHighest.updateAndGet(current -> Math.max(current, ratio)); - - // Record the observation in summary - profitabilityHistogram.labels("profitability").observe(ratio); - } - - public void recalculate() { - transactionPoolService.getPendingTransactions().stream() - .map(PendingTransaction::getTransaction) - .forEach(this::handleTransaction); - } } diff --git a/sequencer/src/main/java/net/consensys/linea/sequencer/txselection/LineaTransactionSelectorFactory.java b/sequencer/src/main/java/net/consensys/linea/sequencer/txselection/LineaTransactionSelectorFactory.java index d6efe4e4..5f7da97c 100644 --- a/sequencer/src/main/java/net/consensys/linea/sequencer/txselection/LineaTransactionSelectorFactory.java +++ b/sequencer/src/main/java/net/consensys/linea/sequencer/txselection/LineaTransactionSelectorFactory.java @@ -23,9 +23,10 @@ import net.consensys.linea.config.LineaTransactionSelectorConfiguration; import net.consensys.linea.jsonrpc.JsonRpcManager; import net.consensys.linea.plugins.config.LineaL1L2BridgeSharedConfiguration; -import net.consensys.linea.sequencer.txselection.metrics.SelectorProfitabilityMetrics; import net.consensys.linea.sequencer.txselection.selectors.LineaTransactionSelector; import org.hyperledger.besu.plugin.services.BlockchainService; +import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.metrics.MetricCategoryRegistry; import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelector; import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelectorFactory; @@ -40,7 +41,8 @@ public class LineaTransactionSelectorFactory implements PluginTransactionSelecto private final LineaL1L2BridgeSharedConfiguration l1L2BridgeConfiguration; private final LineaProfitabilityConfiguration profitabilityConfiguration; private final LineaTracerConfiguration tracerConfiguration; - private final SelectorProfitabilityMetrics selectorProfitabilityMetrics; + private final MetricsSystem metricsSystem; + private final MetricCategoryRegistry metricCategoryRegistry; private final Map limitsMap; @@ -52,7 +54,8 @@ public LineaTransactionSelectorFactory( final LineaTracerConfiguration tracerConfiguration, final Map limitsMap, final Optional rejectedTxJsonRpcManager, - final SelectorProfitabilityMetrics selectorProfitabilityMetrics) { + final MetricsSystem metricsSystem, + final MetricCategoryRegistry metricCategoryRegistry) { this.blockchainService = blockchainService; this.txSelectorConfiguration = txSelectorConfiguration; this.l1L2BridgeConfiguration = l1L2BridgeConfiguration; @@ -60,7 +63,8 @@ public LineaTransactionSelectorFactory( this.tracerConfiguration = tracerConfiguration; this.limitsMap = limitsMap; this.rejectedTxJsonRpcManager = rejectedTxJsonRpcManager; - this.selectorProfitabilityMetrics = selectorProfitabilityMetrics; + this.metricsSystem = metricsSystem; + this.metricCategoryRegistry = metricCategoryRegistry; } @Override @@ -73,6 +77,7 @@ public PluginTransactionSelector create() { tracerConfiguration, limitsMap, rejectedTxJsonRpcManager, - selectorProfitabilityMetrics); + metricsSystem, + metricCategoryRegistry); } } diff --git a/sequencer/src/main/java/net/consensys/linea/sequencer/txselection/LineaTransactionSelectorPlugin.java b/sequencer/src/main/java/net/consensys/linea/sequencer/txselection/LineaTransactionSelectorPlugin.java index 408ea849..76bb677d 100644 --- a/sequencer/src/main/java/net/consensys/linea/sequencer/txselection/LineaTransactionSelectorPlugin.java +++ b/sequencer/src/main/java/net/consensys/linea/sequencer/txselection/LineaTransactionSelectorPlugin.java @@ -15,6 +15,7 @@ package net.consensys.linea.sequencer.txselection; +import static net.consensys.linea.metrics.LineaMetricCategory.SEQUENCER_PROFITABILITY; import static net.consensys.linea.sequencer.modulelimit.ModuleLineCountValidator.createLimitModules; import java.util.Optional; @@ -25,7 +26,6 @@ import net.consensys.linea.config.LineaRejectedTxReportingConfiguration; import net.consensys.linea.config.LineaTransactionSelectorConfiguration; import net.consensys.linea.jsonrpc.JsonRpcManager; -import net.consensys.linea.sequencer.txselection.metrics.SelectorProfitabilityMetrics; import org.hyperledger.besu.plugin.BesuContext; import org.hyperledger.besu.plugin.BesuPlugin; import org.hyperledger.besu.plugin.services.BesuConfiguration; @@ -63,6 +63,8 @@ public void doRegister(final BesuContext context) { () -> new RuntimeException( "Failed to obtain BesuConfiguration from the BesuContext.")); + + metricCategoryRegistry.addMetricCategory(SEQUENCER_PROFITABILITY); } @Override @@ -83,8 +85,6 @@ public void start() { lineaRejectedTxReportingConfiguration) .start()); - final var selectorProfitabilityMetrics = new SelectorProfitabilityMetrics(metricsSystem); - transactionSelectionService.registerPluginTransactionSelectorFactory( new LineaTransactionSelectorFactory( blockchainService, @@ -94,7 +94,8 @@ public void start() { tracerConfiguration(), createLimitModules(tracerConfiguration()), rejectedTxJsonRpcManager, - selectorProfitabilityMetrics)); + metricsSystem, + metricCategoryRegistry)); } @Override diff --git a/sequencer/src/main/java/net/consensys/linea/sequencer/txselection/metrics/SelectorProfitabilityMetrics.java b/sequencer/src/main/java/net/consensys/linea/sequencer/txselection/metrics/SelectorProfitabilityMetrics.java deleted file mode 100644 index 40a14b96..00000000 --- a/sequencer/src/main/java/net/consensys/linea/sequencer/txselection/metrics/SelectorProfitabilityMetrics.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright Consensys Software Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package net.consensys.linea.sequencer.txselection.metrics; - -import java.util.concurrent.atomic.AtomicReference; - -import lombok.extern.slf4j.Slf4j; -import org.hyperledger.besu.datatypes.Transaction; -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.LabelledGauge; -import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; - -@Slf4j -public class SelectorProfitabilityMetrics { - private final LabelledMetric histogram; - - // Thread-safe references for gauge values - private final AtomicReference currentLowest = new AtomicReference<>(0.0); - private final AtomicReference currentHighest = new AtomicReference<>(0.0); - - public SelectorProfitabilityMetrics(final MetricsSystem metricsSystem) { - // Min/Max/Avg gauges with DoubleSupplier - LabelledGauge lowestProfitabilityRatio = - metricsSystem.createLabelledGauge( - BesuMetricCategory.ETHEREUM, - "selection_profitability_ratio_min", - "Lowest profitability ratio seen"); - lowestProfitabilityRatio.labels(currentLowest::get); - - LabelledGauge highestProfitabilityRatio = - metricsSystem.createLabelledGauge( - BesuMetricCategory.ETHEREUM, - "selection_profitability_ratio_max", - "Highest profitability ratio seen"); - highestProfitabilityRatio.labels(currentHighest::get); - - this.histogram = - metricsSystem.createLabelledHistogram( - BesuMetricCategory.ETHEREUM, - "selection_profitability_ratio", - "The profitability of transaction evaluated during block creation", - new double[] {0.9, 1.0, 1.2, 2, 5, 10, 100, 1000}, - "phase"); - } - - public enum Phase { - PRE_PROCESSING, - POST_PROCESSING - } - - public void track( - final Phase phase, - final long blockNumber, - final Transaction tx, - final Wei baseFee, - final Wei effectiveGasPrice, - final Wei profitablePriorityFee) { - final var effectivePriorityFee = effectiveGasPrice.subtract(baseFee); - final var ratio = - effectivePriorityFee.getValue().doubleValue() - / profitablePriorityFee.getValue().doubleValue(); - - updateRunningStats(phase, ratio); - - log.atTrace() - .setMessage( - "{}: block[{}] tx {} , baseFee {}, effectiveGasPrice {}, ratio (effectivePayingPriorityFee {} / calculatedProfitablePriorityFee {}) {}") - .addArgument(phase) - .addArgument(blockNumber) - .addArgument(tx.getHash()) - .addArgument(baseFee::toHumanReadableString) - .addArgument(effectiveGasPrice::toHumanReadableString) - .addArgument(effectivePriorityFee::toHumanReadableString) - .addArgument(profitablePriorityFee::toHumanReadableString) - .addArgument(ratio) - .log(); - } - - private void updateRunningStats(final Phase phase, double ratio) { - // Update lowest seen - currentLowest.updateAndGet(current -> Math.min(current, ratio)); - - // Update highest seen - currentHighest.updateAndGet(current -> Math.max(current, ratio)); - - // Record the observation in summary - histogram.labels(phase.name()).observe(ratio); - } -} diff --git a/sequencer/src/main/java/net/consensys/linea/sequencer/txselection/selectors/LineaTransactionSelector.java b/sequencer/src/main/java/net/consensys/linea/sequencer/txselection/selectors/LineaTransactionSelector.java index 2705238f..78e1d3c3 100644 --- a/sequencer/src/main/java/net/consensys/linea/sequencer/txselection/selectors/LineaTransactionSelector.java +++ b/sequencer/src/main/java/net/consensys/linea/sequencer/txselection/selectors/LineaTransactionSelector.java @@ -26,11 +26,12 @@ import net.consensys.linea.jsonrpc.JsonRpcManager; import net.consensys.linea.jsonrpc.JsonRpcRequestBuilder; import net.consensys.linea.plugins.config.LineaL1L2BridgeSharedConfiguration; -import net.consensys.linea.sequencer.txselection.metrics.SelectorProfitabilityMetrics; import org.hyperledger.besu.datatypes.PendingTransaction; 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.metrics.MetricCategoryRegistry; import org.hyperledger.besu.plugin.services.tracer.BlockAwareOperationTracer; import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelector; import org.hyperledger.besu.plugin.services.txselection.TransactionEvaluationContext; @@ -51,7 +52,8 @@ public LineaTransactionSelector( final LineaTracerConfiguration tracerConfiguration, final Map limitsMap, final Optional rejectedTxJsonRpcManager, - final SelectorProfitabilityMetrics selectorProfitabilityMetrics) { + final MetricsSystem metricsSystem, + final MetricCategoryRegistry metricCategoryRegistry) { this.rejectedTxJsonRpcManager = rejectedTxJsonRpcManager; selectors = createTransactionSelectors( @@ -61,7 +63,8 @@ public LineaTransactionSelector( profitabilityConfiguration, tracerConfiguration, limitsMap, - selectorProfitabilityMetrics); + metricsSystem, + metricCategoryRegistry); } /** @@ -71,6 +74,7 @@ public LineaTransactionSelector( * @param txSelectorConfiguration The configuration to use. * @param profitabilityConfiguration The profitability configuration. * @param limitsMap The limits map. + * @param metricCategoryRegistry * @return A list of selectors. */ private List createTransactionSelectors( @@ -80,7 +84,8 @@ private List createTransactionSelectors( final LineaProfitabilityConfiguration profitabilityConfiguration, final LineaTracerConfiguration tracerConfiguration, final Map limitsMap, - final SelectorProfitabilityMetrics selectorProfitabilityMetrics) { + final MetricsSystem metricsSystem, + final MetricCategoryRegistry metricCategoryRegistry) { traceLineLimitTransactionSelector = new TraceLineLimitTransactionSelector( @@ -97,7 +102,8 @@ private List createTransactionSelectors( blockchainService, txSelectorConfiguration, profitabilityConfiguration, - selectorProfitabilityMetrics), + metricsSystem, + metricCategoryRegistry), traceLineLimitTransactionSelector); } diff --git a/sequencer/src/main/java/net/consensys/linea/sequencer/txselection/selectors/ProfitableTransactionSelector.java b/sequencer/src/main/java/net/consensys/linea/sequencer/txselection/selectors/ProfitableTransactionSelector.java index c483402b..08ba5714 100644 --- a/sequencer/src/main/java/net/consensys/linea/sequencer/txselection/selectors/ProfitableTransactionSelector.java +++ b/sequencer/src/main/java/net/consensys/linea/sequencer/txselection/selectors/ProfitableTransactionSelector.java @@ -14,14 +14,15 @@ */ package net.consensys.linea.sequencer.txselection.selectors; +import static net.consensys.linea.metrics.LineaMetricCategory.SEQUENCER_PROFITABILITY; import static net.consensys.linea.sequencer.txselection.LineaTransactionSelectionResult.TX_UNPROFITABLE; import static net.consensys.linea.sequencer.txselection.LineaTransactionSelectionResult.TX_UNPROFITABLE_RETRY_LIMIT; import static net.consensys.linea.sequencer.txselection.LineaTransactionSelectionResult.TX_UNPROFITABLE_UPFRONT; -import static net.consensys.linea.sequencer.txselection.metrics.SelectorProfitabilityMetrics.Phase.POST_PROCESSING; -import static net.consensys.linea.sequencer.txselection.metrics.SelectorProfitabilityMetrics.Phase.PRE_PROCESSING; import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.SELECTED; import java.util.LinkedHashSet; +import java.util.Locale; +import java.util.Optional; import java.util.Set; import com.google.common.annotations.VisibleForTesting; @@ -29,7 +30,8 @@ import net.consensys.linea.bl.TransactionProfitabilityCalculator; import net.consensys.linea.config.LineaProfitabilityConfiguration; import net.consensys.linea.config.LineaTransactionSelectorConfiguration; -import net.consensys.linea.sequencer.txselection.metrics.SelectorProfitabilityMetrics; +import net.consensys.linea.metrics.HistogramMetrics; +import net.consensys.linea.metrics.HistogramMetrics.LabelValue; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.PendingTransaction; import org.hyperledger.besu.datatypes.Transaction; @@ -37,6 +39,8 @@ 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.metrics.MetricCategoryRegistry; import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelector; import org.hyperledger.besu.plugin.services.txselection.TransactionEvaluationContext; @@ -56,7 +60,7 @@ public class ProfitableTransactionSelector implements PluginTransactionSelector private final LineaTransactionSelectorConfiguration txSelectorConf; private final LineaProfitabilityConfiguration profitabilityConf; private final TransactionProfitabilityCalculator transactionProfitabilityCalculator; - private final SelectorProfitabilityMetrics selectorProfitabilityMetrics; + private final Optional maybeProfitabilityMetrics; private final Wei baseFee; private int unprofitableRetries; @@ -65,12 +69,23 @@ public ProfitableTransactionSelector( final BlockchainService blockchainService, final LineaTransactionSelectorConfiguration txSelectorConf, final LineaProfitabilityConfiguration profitabilityConf, - final SelectorProfitabilityMetrics selectorProfitabilityMetrics) { + final MetricsSystem metricsSystem, + final MetricCategoryRegistry metricCategoryRegistry) { this.txSelectorConf = txSelectorConf; this.profitabilityConf = profitabilityConf; this.transactionProfitabilityCalculator = new TransactionProfitabilityCalculator(profitabilityConf); - this.selectorProfitabilityMetrics = selectorProfitabilityMetrics; + this.maybeProfitabilityMetrics = + metricCategoryRegistry.isMetricCategoryEnabled(SEQUENCER_PROFITABILITY) + ? Optional.of( + new HistogramMetrics( + metricsSystem, + SEQUENCER_PROFITABILITY, + "ratio", + "sequencer profitability ratio", + Phase.class)) + : Optional.empty(); + this.baseFee = blockchainService .getNextBlockBaseFee() @@ -104,13 +119,8 @@ public TransactionSelectionResult evaluateTransactionPreProcessing( transactionProfitabilityCalculator.profitablePriorityFeePerGas( transaction, profitabilityConf.minMargin(), gasLimit, minGasPrice); - selectorProfitabilityMetrics.track( - PRE_PROCESSING, - evaluationContext.getPendingBlockHeader().getNumber(), - transaction, - baseFee, - evaluationContext.getTransactionGasPrice(), - profitablePriorityFeePerGas); + updateMetric( + Phase.PRE_PROCESSING, evaluationContext, transaction, profitablePriorityFeePerGas); // check the upfront profitability using the gas limit of the tx if (!transactionProfitabilityCalculator.isProfitable( @@ -171,13 +181,8 @@ public TransactionSelectionResult evaluateTransactionPostProcessing( gasUsed, evaluationContext.getMinGasPrice()); - selectorProfitabilityMetrics.track( - POST_PROCESSING, - evaluationContext.getPendingBlockHeader().getNumber(), - transaction, - baseFee, - evaluationContext.getTransactionGasPrice(), - profitablePriorityFeePerGas); + updateMetric( + Phase.POST_PROCESSING, evaluationContext, transaction, profitablePriorityFeePerGas); if (!transactionProfitabilityCalculator.isProfitable( "PostProcessing", @@ -237,4 +242,50 @@ private void rememberUnprofitable(final Transaction transaction) { unprofitableCache.add(transaction.getHash()); log.atTrace().setMessage("unprofitableCache={}").addArgument(unprofitableCache::size).log(); } + + private void updateMetric( + final Phase label, + final TransactionEvaluationContext evaluationContext, + final Transaction tx, + final Wei profitablePriorityFeePerGas) { + + maybeProfitabilityMetrics.ifPresent( + histogramMetrics -> { + final var effectivePriorityFee = + evaluationContext.getTransactionGasPrice().subtract(baseFee); + final var ratio = + effectivePriorityFee.getValue().doubleValue() + / profitablePriorityFeePerGas.getValue().doubleValue(); + + histogramMetrics.track(ratio, label.value()); + + log.atTrace() + .setMessage( + "POST_PROCESSING: block[{}] tx {} , baseFee {}, effectiveGasPrice {}, ratio (effectivePayingPriorityFee {} / calculatedProfitablePriorityFee {}) {}") + .addArgument(evaluationContext.getPendingBlockHeader().getNumber()) + .addArgument(tx.getHash()) + .addArgument(baseFee::toHumanReadableString) + .addArgument(evaluationContext.getTransactionGasPrice()::toHumanReadableString) + .addArgument(effectivePriorityFee::toHumanReadableString) + .addArgument(profitablePriorityFeePerGas::toHumanReadableString) + .addArgument(ratio) + .log(); + }); + } + + enum Phase implements LabelValue { + PRE_PROCESSING, + POST_PROCESSING; + + final String value; + + Phase() { + this.value = name().toLowerCase(Locale.ROOT); + } + + @Override + public String value() { + return value; + } + } } diff --git a/sequencer/src/test/java/net/consensys/linea/sequencer/txselection/selectors/ProfitableTransactionSelectorTest.java b/sequencer/src/test/java/net/consensys/linea/sequencer/txselection/selectors/ProfitableTransactionSelectorTest.java index 1c9f1d90..31ab5c33 100644 --- a/sequencer/src/test/java/net/consensys/linea/sequencer/txselection/selectors/ProfitableTransactionSelectorTest.java +++ b/sequencer/src/test/java/net/consensys/linea/sequencer/txselection/selectors/ProfitableTransactionSelectorTest.java @@ -28,7 +28,6 @@ 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; @@ -66,11 +65,9 @@ public class ProfitableTransactionSelectorTest { .build(); private TestableProfitableTransactionSelector transactionSelector; private MetricsSystem metricsSystem = new NoOpMetricsSystem(); - private SelectorProfitabilityMetrics selectorProfitabilityMetrics; @BeforeEach public void initialize() { - selectorProfitabilityMetrics = new SelectorProfitabilityMetrics(metricsSystem); transactionSelector = newSelectorForNewBlock(); transactionSelector.reset(); } @@ -79,7 +76,7 @@ private TestableProfitableTransactionSelector newSelectorForNewBlock() { final var blockchainService = mock(BlockchainService.class); when(blockchainService.getNextBlockBaseFee()).thenReturn(Optional.of(BASE_FEE)); return new TestableProfitableTransactionSelector( - blockchainService, txSelectorConf, profitabilityConf, selectorProfitabilityMetrics); + blockchainService, txSelectorConf, profitabilityConf, metricsSystem); } @Test @@ -417,8 +414,13 @@ private static class TestableProfitableTransactionSelector extends ProfitableTra final BlockchainService blockchainService, final LineaTransactionSelectorConfiguration txSelectorConf, final LineaProfitabilityConfiguration profitabilityConf, - final SelectorProfitabilityMetrics selectorProfitabilityMetrics) { - super(blockchainService, txSelectorConf, profitabilityConf, selectorProfitabilityMetrics); + final MetricsSystem metricsSystem) { + super( + blockchainService, + txSelectorConf, + profitabilityConf, + metricsSystem, + metricCategoryRegistry); } boolean isUnprofitableTxCached(final Hash txHash) {