Fix registering new metric categories from plugins (#7825)

Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>
pull/7832/head
Fabio Di Fabio 3 weeks ago committed by GitHub
parent f82bb7d6b7
commit 22a570eda4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 76
      acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/TestMetricsPlugin.java
  3. 73
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/MetricsPluginTest.java
  4. 18
      besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
  5. 73
      besu/src/main/java/org/hyperledger/besu/cli/converter/MetricCategoryConverter.java
  6. 47
      besu/src/main/java/org/hyperledger/besu/cli/options/stable/MetricsOptions.java
  7. 41
      besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java
  8. 63
      besu/src/test/java/org/hyperledger/besu/cli/converter/MetricCategoryConverterTest.java
  9. 16
      besu/src/test/java/org/hyperledger/besu/cli/options/MetricsOptionsTest.java
  10. 47
      metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricCategoryRegistryImpl.java

@ -14,6 +14,7 @@
- Add a method to get all the transaction in the pool, to the `TransactionPoolService`, to easily access the transaction pool content from plugins [#7813](https://github.com/hyperledger/besu/pull/7813) - Add a method to get all the transaction in the pool, to the `TransactionPoolService`, to easily access the transaction pool content from plugins [#7813](https://github.com/hyperledger/besu/pull/7813)
### Bug fixes ### Bug fixes
- Fix registering new metric categories from plugins [#7825](https://github.com/hyperledger/besu/pull/7825)
## 24.10.0 ## 24.10.0

@ -0,0 +1,76 @@
/*
* Copyright contributors to Besu.
*
* 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 org.hyperledger.besu.tests.acceptance.plugins;
import org.hyperledger.besu.plugin.BesuContext;
import org.hyperledger.besu.plugin.BesuPlugin;
import org.hyperledger.besu.plugin.services.MetricsSystem;
import org.hyperledger.besu.plugin.services.metrics.MetricCategory;
import org.hyperledger.besu.plugin.services.metrics.MetricCategoryRegistry;
import java.util.Locale;
import java.util.Optional;
import com.google.auto.service.AutoService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@AutoService(BesuPlugin.class)
public class TestMetricsPlugin implements BesuPlugin {
private static final Logger LOG = LoggerFactory.getLogger(TestMetricsPlugin.class);
private BesuContext besuContext;
@Override
public void register(final BesuContext context) {
LOG.info("Registering TestMetricsPlugin");
besuContext = context;
context
.getService(MetricCategoryRegistry.class)
.orElseThrow()
.addMetricCategory(TestMetricCategory.TEST_METRIC_CATEGORY);
}
@Override
public void start() {
LOG.info("Starting TestMetricsPlugin");
besuContext
.getService(MetricsSystem.class)
.orElseThrow()
.createGauge(
TestMetricCategory.TEST_METRIC_CATEGORY,
"test_metric",
"Returns 1 on succes",
() -> 1.0);
}
@Override
public void stop() {
LOG.info("Stopping TestMetricsPlugin");
}
public enum TestMetricCategory implements MetricCategory {
TEST_METRIC_CATEGORY;
@Override
public String getName() {
return name().toLowerCase(Locale.ROOT);
}
@Override
public Optional<String> getApplicationPrefix() {
return Optional.of("plugin_test_");
}
}
}

@ -0,0 +1,73 @@
/*
* Copyright contributors to Besu.
*
* 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 org.hyperledger.besu.tests.acceptance.plugins;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.tests.acceptance.plugins.TestMetricsPlugin.TestMetricCategory.TEST_METRIC_CATEGORY;
import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration;
import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase;
import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode;
import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.BesuNodeConfigurationBuilder;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class MetricsPluginTest extends AcceptanceTestBase {
private BesuNode node;
private MetricsConfiguration metricsConfiguration;
@BeforeEach
public void setUp() throws Exception {
metricsConfiguration =
MetricsConfiguration.builder()
.enabled(true)
.port(0)
.metricCategories(Set.of(TEST_METRIC_CATEGORY))
.build();
node =
besu.create(
new BesuNodeConfigurationBuilder()
.name("node1")
.plugins(List.of("testPlugins"))
.metricsConfiguration(metricsConfiguration)
.build());
cluster.start(node);
}
@Test
public void metricCategoryAdded() throws IOException, InterruptedException {
final var httpClient = HttpClient.newHttpClient();
final var req = HttpRequest.newBuilder(URI.create(node.metricsHttpUrl().get())).build();
final var resp = httpClient.send(req, HttpResponse.BodyHandlers.ofLines());
assertThat(resp.statusCode()).isEqualTo(200);
final var foundMetric =
resp.body()
.filter(
line -> line.startsWith(TEST_METRIC_CATEGORY.getApplicationPrefix().orElseThrow()))
.findFirst()
.orElseThrow();
assertThat(foundMetric).endsWith("1.0");
}
}

@ -37,7 +37,6 @@ import org.hyperledger.besu.chainimport.RlpBlockImporter;
import org.hyperledger.besu.cli.config.EthNetworkConfig; import org.hyperledger.besu.cli.config.EthNetworkConfig;
import org.hyperledger.besu.cli.config.NetworkName; import org.hyperledger.besu.cli.config.NetworkName;
import org.hyperledger.besu.cli.config.ProfilesCompletionCandidates; import org.hyperledger.besu.cli.config.ProfilesCompletionCandidates;
import org.hyperledger.besu.cli.converter.MetricCategoryConverter;
import org.hyperledger.besu.cli.custom.JsonRPCAllowlistHostsProperty; import org.hyperledger.besu.cli.custom.JsonRPCAllowlistHostsProperty;
import org.hyperledger.besu.cli.error.BesuExecutionExceptionHandler; import org.hyperledger.besu.cli.error.BesuExecutionExceptionHandler;
import org.hyperledger.besu.cli.error.BesuParameterExceptionHandler; import org.hyperledger.besu.cli.error.BesuParameterExceptionHandler;
@ -170,7 +169,6 @@ import org.hyperledger.besu.plugin.services.TransactionPoolValidatorService;
import org.hyperledger.besu.plugin.services.TransactionSelectionService; import org.hyperledger.besu.plugin.services.TransactionSelectionService;
import org.hyperledger.besu.plugin.services.TransactionSimulationService; import org.hyperledger.besu.plugin.services.TransactionSimulationService;
import org.hyperledger.besu.plugin.services.exception.StorageException; import org.hyperledger.besu.plugin.services.exception.StorageException;
import org.hyperledger.besu.plugin.services.metrics.MetricCategory;
import org.hyperledger.besu.plugin.services.metrics.MetricCategoryRegistry; import org.hyperledger.besu.plugin.services.metrics.MetricCategoryRegistry;
import org.hyperledger.besu.plugin.services.p2p.P2PService; import org.hyperledger.besu.plugin.services.p2p.P2PService;
import org.hyperledger.besu.plugin.services.rlp.RlpConverterService; import org.hyperledger.besu.plugin.services.rlp.RlpConverterService;
@ -332,7 +330,6 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
private final Map<String, String> environment; private final Map<String, String> environment;
private final MetricCategoryRegistryImpl metricCategoryRegistry = private final MetricCategoryRegistryImpl metricCategoryRegistry =
new MetricCategoryRegistryImpl(); new MetricCategoryRegistryImpl();
private final MetricCategoryConverter metricCategoryConverter = new MetricCategoryConverter();
private final PreSynchronizationTaskRunner preSynchronizationTaskRunner = private final PreSynchronizationTaskRunner preSynchronizationTaskRunner =
new PreSynchronizationTaskRunner(); new PreSynchronizationTaskRunner();
@ -1136,10 +1133,6 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
commandLine.registerConverter(Hash.class, Hash::fromHexString); commandLine.registerConverter(Hash.class, Hash::fromHexString);
commandLine.registerConverter(Optional.class, Optional::of); commandLine.registerConverter(Optional.class, Optional::of);
commandLine.registerConverter(Double.class, Double::parseDouble); commandLine.registerConverter(Double.class, Double::parseDouble);
metricCategoryConverter.addCategories(BesuMetricCategory.class);
metricCategoryConverter.addCategories(StandardMetricCategory.class);
commandLine.registerConverter(MetricCategory.class, metricCategoryConverter);
} }
private void handleStableOptions() { private void handleStableOptions() {
@ -1174,6 +1167,9 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
besuPluginContext.addService(PicoCLIOptions.class, new PicoCLIOptionsImpl(commandLine)); besuPluginContext.addService(PicoCLIOptions.class, new PicoCLIOptionsImpl(commandLine));
besuPluginContext.addService(SecurityModuleService.class, securityModuleService); besuPluginContext.addService(SecurityModuleService.class, securityModuleService);
besuPluginContext.addService(StorageService.class, storageService); besuPluginContext.addService(StorageService.class, storageService);
metricCategoryRegistry.addCategories(BesuMetricCategory.class);
metricCategoryRegistry.addCategories(StandardMetricCategory.class);
besuPluginContext.addService(MetricCategoryRegistry.class, metricCategoryRegistry); besuPluginContext.addService(MetricCategoryRegistry.class, metricCategoryRegistry);
besuPluginContext.addService(PermissioningService.class, permissioningService); besuPluginContext.addService(PermissioningService.class, permissioningService);
besuPluginContext.addService(PrivacyPluginService.class, privacyPluginService); besuPluginContext.addService(PrivacyPluginService.class, privacyPluginService);
@ -1191,10 +1187,6 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
rocksDBPlugin.register(besuPluginContext); rocksDBPlugin.register(besuPluginContext);
new InMemoryStoragePlugin().register(besuPluginContext); new InMemoryStoragePlugin().register(besuPluginContext);
metricCategoryRegistry
.getMetricCategories()
.forEach(metricCategoryConverter::addRegistryCategory);
// register default security module // register default security module
securityModuleService.register( securityModuleService.register(
DEFAULT_SECURITY_MODULE, Suppliers.memoize(this::defaultSecurityModule)); DEFAULT_SECURITY_MODULE, Suppliers.memoize(this::defaultSecurityModule));
@ -1891,6 +1883,10 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
"--metrics-push-interval", "--metrics-push-interval",
"--metrics-push-prometheus-job")); "--metrics-push-prometheus-job"));
metricsOptions.setMetricCategoryRegistry(metricCategoryRegistry);
metricsOptions.validate(commandLine);
final MetricsConfiguration.Builder metricsConfigurationBuilder = final MetricsConfiguration.Builder metricsConfigurationBuilder =
metricsOptions.toDomainObject(); metricsOptions.toDomainObject();
metricsConfigurationBuilder metricsConfigurationBuilder

@ -1,73 +0,0 @@
/*
* Copyright ConsenSys AG.
*
* 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 org.hyperledger.besu.cli.converter;
import org.hyperledger.besu.plugin.services.metrics.MetricCategory;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import com.google.common.annotations.VisibleForTesting;
import picocli.CommandLine;
/** The Metric category converter for CLI options. */
public class MetricCategoryConverter implements CommandLine.ITypeConverter<MetricCategory> {
private final Map<String, MetricCategory> metricCategories = new HashMap<>();
/** Default Constructor. */
public MetricCategoryConverter() {}
@Override
public MetricCategory convert(final String value) {
final MetricCategory category = metricCategories.get(value);
if (category == null) {
throw new IllegalArgumentException("Unknown category: " + value);
}
return category;
}
/**
* Add Metrics categories.
*
* @param <T> the type parameter
* @param categoryEnum the category enum
*/
public <T extends Enum<T> & MetricCategory> void addCategories(final Class<T> categoryEnum) {
EnumSet.allOf(categoryEnum)
.forEach(category -> metricCategories.put(category.name(), category));
}
/**
* Add registry category.
*
* @param metricCategory the metric category
*/
public void addRegistryCategory(final MetricCategory metricCategory) {
metricCategories.put(metricCategory.getName().toUpperCase(Locale.ROOT), metricCategory);
}
/**
* Gets metric categories.
*
* @return the metric categories
*/
@VisibleForTesting
Map<String, MetricCategory> getMetricCategories() {
return metricCategories;
}
}

@ -14,6 +14,8 @@
*/ */
package org.hyperledger.besu.cli.options.stable; package org.hyperledger.besu.cli.options.stable;
import static com.google.common.base.Preconditions.checkState;
import static java.util.stream.Collectors.toUnmodifiableSet;
import static org.hyperledger.besu.cli.DefaultCommandValues.MANDATORY_HOST_FORMAT_HELP; import static org.hyperledger.besu.cli.DefaultCommandValues.MANDATORY_HOST_FORMAT_HELP;
import static org.hyperledger.besu.cli.DefaultCommandValues.MANDATORY_INTEGER_FORMAT_HELP; import static org.hyperledger.besu.cli.DefaultCommandValues.MANDATORY_INTEGER_FORMAT_HELP;
import static org.hyperledger.besu.cli.DefaultCommandValues.MANDATORY_PORT_FORMAT_HELP; import static org.hyperledger.besu.cli.DefaultCommandValues.MANDATORY_PORT_FORMAT_HELP;
@ -24,6 +26,7 @@ import static org.hyperledger.besu.metrics.prometheus.MetricsConfiguration.DEFAU
import org.hyperledger.besu.cli.options.CLIOptions; import org.hyperledger.besu.cli.options.CLIOptions;
import org.hyperledger.besu.cli.util.CommandLineUtils; import org.hyperledger.besu.cli.util.CommandLineUtils;
import org.hyperledger.besu.metrics.MetricCategoryRegistryImpl;
import org.hyperledger.besu.metrics.MetricsProtocol; import org.hyperledger.besu.metrics.MetricsProtocol;
import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration;
import org.hyperledger.besu.plugin.services.metrics.MetricCategory; import org.hyperledger.besu.plugin.services.metrics.MetricCategory;
@ -36,6 +39,7 @@ import picocli.CommandLine;
/** Command line options for configuring metrics. */ /** Command line options for configuring metrics. */
// TODO: implement CLIOption<MetricsConfiguration> // TODO: implement CLIOption<MetricsConfiguration>
public class MetricsOptions implements CLIOptions<MetricsConfiguration.Builder> { public class MetricsOptions implements CLIOptions<MetricsConfiguration.Builder> {
private MetricCategoryRegistryImpl metricCategoryRegistry;
/** /**
* Returns a MetricsConfiguration.Builder because fields are often overridden from other domains, * Returns a MetricsConfiguration.Builder because fields are often overridden from other domains,
@ -77,7 +81,10 @@ public class MetricsOptions implements CLIOptions<MetricsConfiguration.Builder>
metricsOptions.metricsHost = config.getHost(); metricsOptions.metricsHost = config.getHost();
metricsOptions.metricsPort = config.getPort(); metricsOptions.metricsPort = config.getPort();
metricsOptions.metricsProtocol = config.getProtocol(); metricsOptions.metricsProtocol = config.getProtocol();
metricsOptions.metricCategories = config.getMetricCategories(); metricsOptions.metricCategories =
config.getMetricCategories().stream()
.map(MetricCategory::getName)
.collect(toUnmodifiableSet());
metricsOptions.metricsPrometheusJob = config.getPrometheusJob(); metricsOptions.metricsPrometheusJob = config.getPrometheusJob();
metricsOptions.isMetricsPushEnabled = config.isPushEnabled(); metricsOptions.isMetricsPushEnabled = config.isPushEnabled();
metricsOptions.metricsPushHost = config.getPushHost(); metricsOptions.metricsPushHost = config.getPushHost();
@ -126,7 +133,8 @@ public class MetricsOptions implements CLIOptions<MetricsConfiguration.Builder>
arity = "1..*", arity = "1..*",
description = description =
"Comma separated list of categories to track metrics for (default: ${DEFAULT-VALUE})") "Comma separated list of categories to track metrics for (default: ${DEFAULT-VALUE})")
private Set<MetricCategory> metricCategories = DEFAULT_METRIC_CATEGORIES; private Set<String> metricCategories =
DEFAULT_METRIC_CATEGORIES.stream().map(MetricCategory::getName).collect(toUnmodifiableSet());
@CommandLine.Option( @CommandLine.Option(
names = {"--metrics-push-enabled"}, names = {"--metrics-push-enabled"},
@ -216,7 +224,13 @@ public class MetricsOptions implements CLIOptions<MetricsConfiguration.Builder>
* @return the metric categories * @return the metric categories
*/ */
public Set<MetricCategory> getMetricCategories() { public Set<MetricCategory> getMetricCategories() {
return metricCategories; checkState(
metricCategoryRegistry != null, "Set metricCategoryRegistry before calling this method");
final var list =
metricCategories.stream().map(metricCategoryRegistry::getMetricCategory).toList();
return Set.copyOf(list);
} }
/** /**
@ -264,6 +278,33 @@ public class MetricsOptions implements CLIOptions<MetricsConfiguration.Builder>
return metricsPrometheusJob; return metricsPrometheusJob;
} }
/**
* Perform final validation after all the options, and the metric category registry, have been set
*
* @param commandLine the command line
*/
public void validate(final CommandLine commandLine) {
checkState(
metricCategoryRegistry != null, "Set metricCategoryRegistry before calling this method");
final var unknownCategories =
metricCategories.stream()
.filter(category -> !metricCategoryRegistry.containsMetricCategory(category))
.toList();
if (!unknownCategories.isEmpty()) {
throw new CommandLine.ParameterException(
commandLine, "--metrics-categories contains unknown categories: " + unknownCategories);
}
}
/**
* Set the metric category registry in order to support verification and conversion
*
* @param metricCategoryRegistry the metric category registry
*/
public void setMetricCategoryRegistry(final MetricCategoryRegistryImpl metricCategoryRegistry) {
this.metricCategoryRegistry = metricCategoryRegistry;
}
@CommandLine.ArgGroup(validate = false) @CommandLine.ArgGroup(validate = false)
private final MetricsOptions.Unstable unstableOptions = new MetricsOptions.Unstable(); private final MetricsOptions.Unstable unstableOptions = new MetricsOptions.Unstable();

@ -40,6 +40,7 @@ import static org.mockito.ArgumentMatchers.isNotNull;
import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import org.hyperledger.besu.BesuInfo; import org.hyperledger.besu.BesuInfo;
import org.hyperledger.besu.cli.config.EthNetworkConfig; import org.hyperledger.besu.cli.config.EthNetworkConfig;
@ -99,7 +100,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import picocli.CommandLine; import picocli.CommandLine;
@ -566,7 +566,7 @@ public class BesuCommandTest extends CommandTestAbstract {
parseCommand("--genesis-file", genesisFile.toString(), "--network", "mainnet"); parseCommand("--genesis-file", genesisFile.toString(), "--network", "mainnet");
Mockito.verifyNoInteractions(mockRunnerBuilder); verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)) assertThat(commandErrorOutput.toString(UTF_8))
@ -578,7 +578,7 @@ public class BesuCommandTest extends CommandTestAbstract {
final String nonExistentGenesis = "non-existent-genesis.json"; final String nonExistentGenesis = "non-existent-genesis.json";
parseCommand("--genesis-file", nonExistentGenesis); parseCommand("--genesis-file", nonExistentGenesis);
Mockito.verifyNoInteractions(mockRunnerBuilder); verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).startsWith("Unable to load genesis file"); assertThat(commandErrorOutput.toString(UTF_8)).startsWith("Unable to load genesis file");
@ -1177,7 +1177,7 @@ public class BesuCommandTest extends CommandTestAbstract {
parseCommand( parseCommand(
"--remote-connections-limit-enabled", "--remote-connections-max-percentage", "invalid"); "--remote-connections-limit-enabled", "--remote-connections-max-percentage", "invalid");
Mockito.verifyNoInteractions(mockRunnerBuilder); verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)) assertThat(commandErrorOutput.toString(UTF_8))
.contains( .contains(
@ -1190,7 +1190,7 @@ public class BesuCommandTest extends CommandTestAbstract {
parseCommand( parseCommand(
"--remote-connections-limit-enabled", "--remote-connections-max-percentage", "150"); "--remote-connections-limit-enabled", "--remote-connections-max-percentage", "150");
Mockito.verifyNoInteractions(mockRunnerBuilder); verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)) assertThat(commandErrorOutput.toString(UTF_8))
.contains( .contains(
@ -1225,7 +1225,7 @@ public class BesuCommandTest extends CommandTestAbstract {
@Test @Test
public void syncMode_invalid() { public void syncMode_invalid() {
parseCommand("--sync-mode", "bogus"); parseCommand("--sync-mode", "bogus");
Mockito.verifyNoInteractions(mockRunnerBuilder); verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)) assertThat(commandErrorOutput.toString(UTF_8))
@ -1275,7 +1275,7 @@ public class BesuCommandTest extends CommandTestAbstract {
public void helpShouldDisplayFastSyncOptions() { public void helpShouldDisplayFastSyncOptions() {
parseCommand("--help"); parseCommand("--help");
Mockito.verifyNoInteractions(mockRunnerBuilder); verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).contains("--fast-sync-min-peers"); assertThat(commandOutput.toString(UTF_8)).contains("--fast-sync-min-peers");
// whitelist is now a hidden option // whitelist is now a hidden option
@ -1335,7 +1335,7 @@ public class BesuCommandTest extends CommandTestAbstract {
public void parsesInvalidFastSyncMinPeersOptionWrongFormatShouldFail() { public void parsesInvalidFastSyncMinPeersOptionWrongFormatShouldFail() {
parseCommand("--sync-mode", "FAST", "--fast-sync-min-peers", "ten"); parseCommand("--sync-mode", "FAST", "--fast-sync-min-peers", "ten");
Mockito.verifyNoInteractions(mockRunnerBuilder); verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)) assertThat(commandErrorOutput.toString(UTF_8))
.contains("Invalid value for option '--fast-sync-min-peers': 'ten' is not an int"); .contains("Invalid value for option '--fast-sync-min-peers': 'ten' is not an int");
@ -1358,7 +1358,7 @@ public class BesuCommandTest extends CommandTestAbstract {
public void netRestrictInvalidShouldFail() { public void netRestrictInvalidShouldFail() {
final String subnet = "127.0.0.1/abc"; final String subnet = "127.0.0.1/abc";
parseCommand("--net-restrict", subnet); parseCommand("--net-restrict", subnet);
Mockito.verifyNoInteractions(mockRunnerBuilder); verifyNoInteractions(mockRunnerBuilder);
assertThat(commandErrorOutput.toString(UTF_8)) assertThat(commandErrorOutput.toString(UTF_8))
.contains("Invalid value for option '--net-restrict'"); .contains("Invalid value for option '--net-restrict'");
} }
@ -1382,7 +1382,7 @@ public class BesuCommandTest extends CommandTestAbstract {
@Test @Test
public void ethStatsContactOptionCannotBeUsedWithoutEthStatsServerProvided() { public void ethStatsContactOptionCannotBeUsedWithoutEthStatsServerProvided() {
parseCommand("--ethstats-contact", "besu-updated"); parseCommand("--ethstats-contact", "besu-updated");
Mockito.verifyNoInteractions(mockRunnerBuilder); verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)) assertThat(commandErrorOutput.toString(UTF_8))
.contains( .contains(
@ -1435,7 +1435,7 @@ public class BesuCommandTest extends CommandTestAbstract {
public void parsesInvalidWhenFullSyncAndBonsaiLimitTrieLogsExplicitlyTrue() { public void parsesInvalidWhenFullSyncAndBonsaiLimitTrieLogsExplicitlyTrue() {
parseCommand("--sync-mode=FULL", "--bonsai-limit-trie-logs-enabled=true"); parseCommand("--sync-mode=FULL", "--bonsai-limit-trie-logs-enabled=true");
Mockito.verifyNoInteractions(mockRunnerBuilder); verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)) assertThat(commandErrorOutput.toString(UTF_8))
.contains( .contains(
@ -1467,7 +1467,7 @@ public class BesuCommandTest extends CommandTestAbstract {
parseCommand("--data-storage-format", "BONSAI", "--bonsai-maximum-back-layers-to-load", "ten"); parseCommand("--data-storage-format", "BONSAI", "--bonsai-maximum-back-layers-to-load", "ten");
Mockito.verifyNoInteractions(mockRunnerBuilder); verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)) assertThat(commandErrorOutput.toString(UTF_8))
.contains( .contains(
@ -1501,7 +1501,7 @@ public class BesuCommandTest extends CommandTestAbstract {
@Test @Test
public void dnsUpdateEnabledOptionCannotBeUsedWithoutDnsEnabled() { public void dnsUpdateEnabledOptionCannotBeUsedWithoutDnsEnabled() {
parseCommand("--Xdns-update-enabled", "true"); parseCommand("--Xdns-update-enabled", "true");
Mockito.verifyNoInteractions(mockRunnerBuilder); verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)) assertThat(commandErrorOutput.toString(UTF_8))
.contains( .contains(
@ -1720,6 +1720,17 @@ public class BesuCommandTest extends CommandTestAbstract {
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
} }
@Test
public void metricsUnknownCategoryRaiseError() {
parseCommand("--metrics-enabled", "--metrics-category", "UNKNOWN_CATEGORY");
verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8))
.startsWith("--metrics-categories contains unknown categories: [UNKNOWN_CATEGORY]");
}
@Test @Test
public void metricsPushEnabledPropertyMustBeUsed() { public void metricsPushEnabledPropertyMustBeUsed() {
parseCommand("--metrics-push-enabled"); parseCommand("--metrics-push-enabled");
@ -1799,7 +1810,7 @@ public class BesuCommandTest extends CommandTestAbstract {
public void metricsAndMetricsPushMustNotBeUsedTogether() { public void metricsAndMetricsPushMustNotBeUsedTogether() {
parseCommand("--metrics-enabled", "--metrics-push-enabled"); parseCommand("--metrics-enabled", "--metrics-push-enabled");
Mockito.verifyNoInteractions(mockRunnerBuilder); verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)) assertThat(commandErrorOutput.toString(UTF_8))
@ -2023,7 +2034,7 @@ public class BesuCommandTest extends CommandTestAbstract {
public void fullCLIOptionsShown() { public void fullCLIOptionsShown() {
parseCommand("--help"); parseCommand("--help");
Mockito.verifyNoInteractions(mockRunnerBuilder); verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).contains("--config-file"); assertThat(commandOutput.toString(UTF_8)).contains("--config-file");
assertThat(commandOutput.toString(UTF_8)).contains("--data-path"); assertThat(commandOutput.toString(UTF_8)).contains("--data-path");

@ -1,63 +0,0 @@
/*
* Copyright ConsenSys AG.
*
* 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 org.hyperledger.besu.cli.converter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.plugin.services.metrics.MetricCategory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class MetricCategoryConverterTest {
private MetricCategoryConverter metricCategoryConverter;
@Mock MetricCategory metricCategory;
@BeforeEach
public void setUp() {
metricCategoryConverter = new MetricCategoryConverter();
}
@Test
public void convertShouldFailIfValueNotRegistered() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> metricCategoryConverter.convert("notRegistered"));
}
@Test
public void addRegistryCategoryShouldUppercaseInputValues() {
when(metricCategory.getName()).thenReturn("testcat");
metricCategoryConverter.addRegistryCategory(metricCategory);
when(metricCategory.getName()).thenReturn("tesTCat2");
metricCategoryConverter.addRegistryCategory(metricCategory);
final boolean containsLowercase =
metricCategoryConverter.getMetricCategories().keySet().stream()
.anyMatch(testString -> testString.chars().anyMatch(Character::isLowerCase));
assertThat(containsLowercase).isFalse();
assertThat(metricCategoryConverter.getMetricCategories().size()).isEqualTo(2);
assertThat(metricCategoryConverter.getMetricCategories().keySet())
.containsExactlyInAnyOrder("TESTCAT", "TESTCAT2");
}
}

@ -15,14 +15,26 @@
package org.hyperledger.besu.cli.options; package org.hyperledger.besu.cli.options;
import org.hyperledger.besu.cli.options.stable.MetricsOptions; import org.hyperledger.besu.cli.options.stable.MetricsOptions;
import org.hyperledger.besu.metrics.BesuMetricCategory;
import org.hyperledger.besu.metrics.MetricCategoryRegistryImpl;
import org.hyperledger.besu.metrics.StandardMetricCategory;
import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
public class MetricsOptionsTest public class MetricsOptionsTest
extends AbstractCLIOptionsTest<MetricsConfiguration.Builder, MetricsOptions> { extends AbstractCLIOptionsTest<MetricsConfiguration.Builder, MetricsOptions> {
private MetricCategoryRegistryImpl categoryRegistry;
@BeforeEach
public void setUp() {
categoryRegistry = new MetricCategoryRegistryImpl();
categoryRegistry.addCategories(BesuMetricCategory.class);
categoryRegistry.addCategories(StandardMetricCategory.class);
}
@Override @Override
protected MetricsConfiguration.Builder createDefaultDomainObject() { protected MetricsConfiguration.Builder createDefaultDomainObject() {
@ -39,7 +51,9 @@ public class MetricsOptionsTest
@Override @Override
protected MetricsOptions optionsFromDomainObject( protected MetricsOptions optionsFromDomainObject(
final MetricsConfiguration.Builder domainObject) { final MetricsConfiguration.Builder domainObject) {
return MetricsOptions.fromConfiguration(domainObject.build()); final var options = MetricsOptions.fromConfiguration(domainObject.build());
options.setMetricCategoryRegistry(categoryRegistry);
return options;
} }
@Override @Override

@ -17,28 +17,55 @@ package org.hyperledger.besu.metrics;
import org.hyperledger.besu.plugin.services.metrics.MetricCategory; import org.hyperledger.besu.plugin.services.metrics.MetricCategory;
import org.hyperledger.besu.plugin.services.metrics.MetricCategoryRegistry; import org.hyperledger.besu.plugin.services.metrics.MetricCategoryRegistry;
import java.util.ArrayList; import java.util.EnumSet;
import java.util.List; import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/** The Metric category registry implementation. */ /** The Metric category registry implementation. */
public class MetricCategoryRegistryImpl implements MetricCategoryRegistry { public class MetricCategoryRegistryImpl implements MetricCategoryRegistry {
private final Map<String, MetricCategory> metricCategories = new HashMap<>();
private final List<MetricCategory> metricCategories = new ArrayList<>();
/** Default constructor */ /** Default constructor */
public MetricCategoryRegistryImpl() {} public MetricCategoryRegistryImpl() {}
/** /**
* Gets metric categories. * Add Metrics categories.
* *
* @return the metric categories * @param <T> the type parameter
* @param categoryEnum the category enum
*/ */
public List<MetricCategory> getMetricCategories() { public <T extends Enum<T> & MetricCategory> void addCategories(final Class<T> categoryEnum) {
return metricCategories; EnumSet.allOf(categoryEnum).forEach(this::addMetricCategory);
} }
/**
* Add registry category.
*
* @param metricCategory the metric category
*/
@Override @Override
public void addMetricCategory(final MetricCategory newMetricCategory) { public void addMetricCategory(final MetricCategory metricCategory) {
metricCategories.add(newMetricCategory); metricCategories.put(metricCategory.getName().toUpperCase(Locale.ROOT), metricCategory);
}
/**
* Return true if a category with that name is already registered
*
* @param name the category name
* @return true if a category with that name is already registered
*/
public boolean containsMetricCategory(final String name) {
return metricCategories.containsKey(name.toUpperCase(Locale.ROOT));
}
/**
* Return a metric category by name
*
* @param name the category name
* @return the metric category or null if not registered
*/
public MetricCategory getMetricCategory(final String name) {
return metricCategories.get(name.toUpperCase(Locale.ROOT));
} }
} }

Loading…
Cancel
Save