Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose metrics for profitability configuration at runtime #104

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@

import java.io.IOException;
import java.math.BigInteger;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
Expand All @@ -34,6 +38,8 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import linea.plugin.acc.test.tests.web3j.generated.SimpleStorage;
import lombok.extern.slf4j.Slf4j;
import net.consensys.linea.metrics.LineaMetricCategory;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
Expand All @@ -42,6 +48,8 @@
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration;
import org.hyperledger.besu.plugin.services.metrics.MetricCategory;
import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase;
import org.hyperledger.besu.tests.acceptance.dsl.account.Accounts;
import org.hyperledger.besu.tests.acceptance.dsl.condition.txpool.TxPoolConditions;
Expand All @@ -66,13 +74,15 @@
import org.web3j.tx.response.TransactionReceiptProcessor;

/** Base class for plugin tests. */
@Slf4j
public class LineaPluginTestBase extends AcceptanceTestBase {
public static final int MAX_CALLDATA_SIZE = 1188; // contract has a call data size of 1160
public static final int MAX_TX_GAS_LIMIT = DefaultGasProvider.GAS_LIMIT.intValue();
public static final long CHAIN_ID = 1337L;
public static final CliqueOptions LINEA_CLIQUE_OPTIONS =
new CliqueOptions(
CliqueOptions.DEFAULT.blockPeriodSeconds(), CliqueOptions.DEFAULT.epochLength(), false);
protected static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();
protected BesuNode minerNode;

@BeforeEach
Expand Down Expand Up @@ -122,6 +132,11 @@ private BesuNode createCliqueNodeWithExtraCliOptionsAndRpcApis(
.genesisConfigProvider(
validators -> Optional.of(provideGenesisConfig(validators, cliqueOptions)))
.extraCLIOptions(extraCliOptions)
.metricsConfiguration(
MetricsConfiguration.builder()
.enabled(true)
.metricCategories(Set.of(LineaMetricCategory.PROFITABILITY))
.build())
.requestedPlugins(
List.of(
"LineaExtraDataPlugin",
Expand Down Expand Up @@ -306,4 +321,33 @@ protected Bytes32 createExtraDataPricingField(
return Bytes32.rightPad(
Bytes.concatenate(Bytes.of((byte) 1), fixed.toBytes(), variable.toBytes(), min.toBytes()));
}

protected double getMetricValue(
final MetricCategory category,
final String metricName,
final List<Map.Entry<String, String>> labelValues)
throws IOException, InterruptedException {

final var metricsReq =
HttpRequest.newBuilder().GET().uri(URI.create("http://127.0.0.1:9545/metrics")).build();

final var respLines = HTTP_CLIENT.send(metricsReq, HttpResponse.BodyHandlers.ofLines());

final var searchString =
category.getApplicationPrefix().orElse("")
+ category.getName()
+ "_"
+ metricName
+ labelValues.stream()
.map(lv -> lv.getKey() + "=\"" + lv.getValue() + "\"")
.collect(Collectors.joining(",", "{", ",}"));

final var foundMetric =
respLines.body().filter(line -> line.startsWith(searchString)).findFirst();

return foundMetric
.map(line -> line.substring(searchString.length()).trim())
.map(Double::valueOf)
.orElse(Double.NaN);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*/
package linea.plugin.acc.test.extradata;

import static java.util.Map.entry;
import static org.assertj.core.api.Assertions.assertThat;

import java.io.IOException;
Expand All @@ -22,6 +23,7 @@

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;
Expand Down Expand Up @@ -78,7 +80,7 @@ public void updateMinGasPriceViaExtraData() {
}

@Test
public void updateProfitabilityParamsViaExtraData() throws IOException {
public void updateProfitabilityParamsViaExtraData() throws IOException, InterruptedException {
final Web3j web3j = minerNode.nodeRequests().eth();
final Account sender = accounts.getSecondaryBenefactor();
final Account recipient = accounts.createAccount("recipient");
Expand Down Expand Up @@ -121,6 +123,28 @@ public void updateProfitabilityParamsViaExtraData() throws IOException {
assertThat(signedUnprofitableTxResp.getError().getMessage()).isEqualTo("Gas price too low");

assertThat(getTxPoolContent()).isEmpty();

final var fixedCostMetric =
getMetricValue(
LineaMetricCategory.PROFITABILITY, "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")));

assertThat(variableCostMetric).isEqualTo(MIN_GAS_PRICE.getValue().doubleValue());

final var ethGasPriceMetric =
getMetricValue(
LineaMetricCategory.PROFITABILITY,
"conf",
List.of(entry("field", "eth_gas_price_wei")));

assertThat(ethGasPriceMetric).isEqualTo(MIN_GAS_PRICE.getValue().doubleValue());
}

static class MinerSetExtraDataRequest implements Transaction<Boolean> {
Expand Down
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
releaseVersion=0.8.0-rc4.1
besuVersion=24.10-delivery34
releaseVersion=0.8.0-rc4.1-local
besuVersion=24.10-develop-829db23
arithmetizationVersion=0.8.0-rc4
besuArtifactGroup=io.consensys.linea-besu
distributionIdentifier=linea-sequencer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,9 @@
import lombok.extern.slf4j.Slf4j;
import org.hyperledger.besu.plugin.BesuContext;
import org.hyperledger.besu.plugin.BesuPlugin;
import org.hyperledger.besu.plugin.services.BlockchainService;

@Slf4j
public abstract class AbstractLineaRequiredPlugin extends AbstractLineaPrivateOptionsPlugin {
protected BlockchainService blockchainService;
public abstract class AbstractLineaRequiredPlugin extends AbstractLineaSharedPrivateOptionsPlugin {

/**
* Linea plugins extending this class will halt startup of Besu in case of exception during
Expand All @@ -38,14 +36,6 @@ public void register(final BesuContext context) {
try {
log.info("Registering Linea plugin {}", this.getClass().getName());

blockchainService =
context
.getService(BlockchainService.class)
.orElseThrow(
() ->
new RuntimeException(
"Failed to obtain BlockchainService from the BesuContext."));

doRegister(context);

} catch (Exception e) {
Expand All @@ -62,21 +52,4 @@ public void register(final BesuContext context) {
* @param context the BesuContext to be used.
*/
public abstract void doRegister(final BesuContext context);

@Override
public void start() {
super.start();

blockchainService
.getChainId()
.ifPresentOrElse(
chainId -> {
if (chainId.signum() <= 0) {
throw new IllegalArgumentException("Chain id must be greater than zero.");
}
},
() -> {
throw new IllegalArgumentException("Chain id required");
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import lombok.extern.slf4j.Slf4j;
import net.consensys.linea.compress.LibCompress;
Expand All @@ -32,21 +33,35 @@
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;
import org.hyperledger.besu.plugin.services.BlockchainService;
import org.hyperledger.besu.plugin.services.MetricsSystem;
import org.hyperledger.besu.plugin.services.metrics.MetricCategoryRegistry;

/**
* This abstract class is used as superclass for all the plugins that share one or more
* configuration options.
* configuration options, services and common initializations.
*
* <p>Configuration options that are exclusive of a single plugin, are not required to be added
* here, but they could stay in the class that implement a plugin, but in case that configuration
* becomes to be used by multiple plugins, then to avoid code duplications and possible different
* management of the options, it is better to move the configuration here so all plugins will
* automatically see it.
*
* <p>Same for services and other initialization tasks, that are shared by more than one plugin,
* like registration of metrics categories or check to perform once at startup
*/
@Slf4j
public abstract class AbstractLineaPrivateOptionsPlugin extends AbstractLineaSharedOptionsPlugin {
public abstract class AbstractLineaSharedPrivateOptionsPlugin
extends AbstractLineaSharedOptionsPlugin {
protected static BlockchainService blockchainService;
protected static MetricsSystem metricsSystem;

private static AtomicBoolean sharedRegisterTasksDone = new AtomicBoolean(false);
private static AtomicBoolean sharedStartTasksDone = new AtomicBoolean(false);

static {
// force the initialization of the gnark compress native library to fail fast in case of issues
Expand Down Expand Up @@ -106,8 +121,68 @@ public LineaRejectedTxReportingConfiguration rejectedTxReportingConfiguration()
getConfigurationByKey(LineaRejectedTxReportingCliOptions.CONFIG_KEY).optionsConfig();
}

@Override
public synchronized void register(final BesuContext context) {
super.register(context);

if (sharedRegisterTasksDone.compareAndSet(false, true)) {
performSharedRegisterTasksOnce(context);
}
}

protected void performSharedRegisterTasksOnce(final BesuContext context) {
blockchainService =
context
.getService(BlockchainService.class)
.orElseThrow(
() ->
new RuntimeException(
"Failed to obtain BlockchainService from the BesuContext."));

metricsSystem =
context
.getService(MetricsSystem.class)
.orElseThrow(
() -> new RuntimeException("Failed to obtain MetricSystem from the BesuContext."));

context
.getService(MetricCategoryRegistry.class)
.orElseThrow(
() ->
new RuntimeException(
"Failed to obtain MetricCategoryRegistry from the BesuContext."))
.addMetricCategory(LineaMetricCategory.PROFITABILITY);
}

@Override
public void start() {
super.start();

if (sharedStartTasksDone.compareAndSet(false, true)) {
performSharedStartTasksOnce();
}
}

private static void performSharedStartTasksOnce() {
blockchainService
.getChainId()
.ifPresentOrElse(
chainId -> {
if (chainId.signum() <= 0) {
throw new IllegalArgumentException("Chain id must be greater than zero.");
}
},
() -> {
throw new IllegalArgumentException("Chain id required");
});
}

@Override
public void stop() {
super.stop();
sharedRegisterTasksDone.set(false);
sharedStartTasksDone.set(false);
blockchainService = null;
metricsSystem = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import com.google.auto.service.AutoService;
import lombok.extern.slf4j.Slf4j;
import net.consensys.linea.AbstractLineaRequiredPlugin;
import net.consensys.linea.config.LineaProfitabilityConfiguration;
import net.consensys.linea.metrics.LineaMetricCategory;
import org.hyperledger.besu.plugin.BesuContext;
import org.hyperledger.besu.plugin.BesuPlugin;
import org.hyperledger.besu.plugin.services.BesuEvents;
Expand Down Expand Up @@ -97,5 +99,19 @@ public void start() {
}
});
}

initMetrics(profitabilityConfiguration());
}

private void initMetrics(final LineaProfitabilityConfiguration lineaProfitabilityConfiguration) {
final var confLabelledGauge =
metricsSystem.createLabelledGauge(
LineaMetricCategory.PROFITABILITY,
"conf",
"Profitability configuration values at runtime",
"field");
confLabelledGauge.labels(lineaProfitabilityConfiguration::fixedCostWei, "fixed_cost_wei");
confLabelledGauge.labels(lineaProfitabilityConfiguration::variableCostWei, "variable_cost_wei");
fab-10 marked this conversation as resolved.
Show resolved Hide resolved
confLabelledGauge.labels(lineaProfitabilityConfiguration::ethGasPriceWei, "eth_gas_price_wei");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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.Optional;

import org.hyperledger.besu.plugin.services.metrics.MetricCategory;

public enum LineaMetricCategory implements MetricCategory {
/** Profitability metric category */
PROFITABILITY("profitability");

private static final Optional<String> APPLICATION_PREFIX = Optional.of("linea_");
private final String name;

LineaMetricCategory(final String name) {
this.name = name;
}

@Override
public String getName() {
return name;
}

@Override
public Optional<String> getApplicationPrefix() {
return APPLICATION_PREFIX;
}
}
Loading