From 2b42d315b50b582cbc6a2231601e40c36fe331ad Mon Sep 17 00:00:00 2001 From: Fabio Di Fabio Date: Tue, 29 Oct 2024 15:53:19 +0100 Subject: [PATCH] Fix registering new metric categories from plugins Signed-off-by: Fabio Di Fabio --- CHANGELOG.md | 1 + .../acceptance/plugins/TestMetricsPlugin.java | 76 +++++++++++++++++++ .../acceptance/plugins/MetricsPluginTest.java | 73 ++++++++++++++++++ .../org/hyperledger/besu/cli/BesuCommand.java | 18 ++--- .../converter/MetricCategoryConverter.java | 73 ------------------ .../cli/options/stable/MetricsOptions.java | 47 +++++++++++- .../hyperledger/besu/cli/BesuCommandTest.java | 41 ++++++---- .../MetricCategoryConverterTest.java | 63 --------------- .../besu/cli/options/MetricsOptionsTest.java | 16 +++- .../metrics/MetricCategoryRegistryImpl.java | 47 +++++++++--- 10 files changed, 279 insertions(+), 176 deletions(-) create mode 100644 acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/TestMetricsPlugin.java create mode 100644 acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/MetricsPluginTest.java delete mode 100644 besu/src/main/java/org/hyperledger/besu/cli/converter/MetricCategoryConverter.java delete mode 100644 besu/src/test/java/org/hyperledger/besu/cli/converter/MetricCategoryConverterTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bfb4ebd3ec..4b20bc15aea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) ### Bug fixes +- Fix registering new metric categories from plugins [#7825](https://github.com/hyperledger/besu/pull/7825) ## 24.10.0 diff --git a/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/TestMetricsPlugin.java b/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/TestMetricsPlugin.java new file mode 100644 index 00000000000..7e1ac2ccdec --- /dev/null +++ b/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/TestMetricsPlugin.java @@ -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 getApplicationPrefix() { + return Optional.of("plugin_test_"); + } + } +} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/MetricsPluginTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/MetricsPluginTest.java new file mode 100644 index 00000000000..75592007204 --- /dev/null +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/MetricsPluginTest.java @@ -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"); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index 886e3d68383..45feaf10052 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -37,7 +37,6 @@ import org.hyperledger.besu.cli.config.EthNetworkConfig; import org.hyperledger.besu.cli.config.NetworkName; 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.error.BesuExecutionExceptionHandler; import org.hyperledger.besu.cli.error.BesuParameterExceptionHandler; @@ -170,7 +169,6 @@ import org.hyperledger.besu.plugin.services.TransactionSelectionService; import org.hyperledger.besu.plugin.services.TransactionSimulationService; 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.p2p.P2PService; import org.hyperledger.besu.plugin.services.rlp.RlpConverterService; @@ -332,7 +330,6 @@ public class BesuCommand implements DefaultCommandValues, Runnable { private final Map environment; private final MetricCategoryRegistryImpl metricCategoryRegistry = new MetricCategoryRegistryImpl(); - private final MetricCategoryConverter metricCategoryConverter = new MetricCategoryConverter(); private final PreSynchronizationTaskRunner preSynchronizationTaskRunner = new PreSynchronizationTaskRunner(); @@ -1136,10 +1133,6 @@ private void registerConverters() { commandLine.registerConverter(Hash.class, Hash::fromHexString); commandLine.registerConverter(Optional.class, Optional::of); commandLine.registerConverter(Double.class, Double::parseDouble); - - metricCategoryConverter.addCategories(BesuMetricCategory.class); - metricCategoryConverter.addCategories(StandardMetricCategory.class); - commandLine.registerConverter(MetricCategory.class, metricCategoryConverter); } private void handleStableOptions() { @@ -1174,6 +1167,9 @@ private void preparePlugins() { besuPluginContext.addService(PicoCLIOptions.class, new PicoCLIOptionsImpl(commandLine)); besuPluginContext.addService(SecurityModuleService.class, securityModuleService); besuPluginContext.addService(StorageService.class, storageService); + + metricCategoryRegistry.addCategories(BesuMetricCategory.class); + metricCategoryRegistry.addCategories(StandardMetricCategory.class); besuPluginContext.addService(MetricCategoryRegistry.class, metricCategoryRegistry); besuPluginContext.addService(PermissioningService.class, permissioningService); besuPluginContext.addService(PrivacyPluginService.class, privacyPluginService); @@ -1191,10 +1187,6 @@ private void preparePlugins() { rocksDBPlugin.register(besuPluginContext); new InMemoryStoragePlugin().register(besuPluginContext); - metricCategoryRegistry - .getMetricCategories() - .forEach(metricCategoryConverter::addRegistryCategory); - // register default security module securityModuleService.register( DEFAULT_SECURITY_MODULE, Suppliers.memoize(this::defaultSecurityModule)); @@ -1891,6 +1883,10 @@ public MetricsConfiguration metricsConfiguration() { "--metrics-push-interval", "--metrics-push-prometheus-job")); + metricsOptions.setMetricCategoryRegistry(metricCategoryRegistry); + + metricsOptions.validate(commandLine); + final MetricsConfiguration.Builder metricsConfigurationBuilder = metricsOptions.toDomainObject(); metricsConfigurationBuilder diff --git a/besu/src/main/java/org/hyperledger/besu/cli/converter/MetricCategoryConverter.java b/besu/src/main/java/org/hyperledger/besu/cli/converter/MetricCategoryConverter.java deleted file mode 100644 index 339da86b2d7..00000000000 --- a/besu/src/main/java/org/hyperledger/besu/cli/converter/MetricCategoryConverter.java +++ /dev/null @@ -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 { - - private final Map 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 the type parameter - * @param categoryEnum the category enum - */ - public & MetricCategory> void addCategories(final Class 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 getMetricCategories() { - return metricCategories; - } -} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/MetricsOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/MetricsOptions.java index 4906cf538e4..beba20e29b5 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/MetricsOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/MetricsOptions.java @@ -14,6 +14,8 @@ */ 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_INTEGER_FORMAT_HELP; import static org.hyperledger.besu.cli.DefaultCommandValues.MANDATORY_PORT_FORMAT_HELP; @@ -24,6 +26,7 @@ import org.hyperledger.besu.cli.options.CLIOptions; import org.hyperledger.besu.cli.util.CommandLineUtils; +import org.hyperledger.besu.metrics.MetricCategoryRegistryImpl; import org.hyperledger.besu.metrics.MetricsProtocol; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; import org.hyperledger.besu.plugin.services.metrics.MetricCategory; @@ -36,6 +39,7 @@ /** Command line options for configuring metrics. */ // TODO: implement CLIOption public class MetricsOptions implements CLIOptions { + private MetricCategoryRegistryImpl metricCategoryRegistry; /** * Returns a MetricsConfiguration.Builder because fields are often overridden from other domains, @@ -77,7 +81,10 @@ public static MetricsOptions fromConfiguration(final MetricsConfiguration config metricsOptions.metricsHost = config.getHost(); metricsOptions.metricsPort = config.getPort(); metricsOptions.metricsProtocol = config.getProtocol(); - metricsOptions.metricCategories = config.getMetricCategories(); + metricsOptions.metricCategories = + config.getMetricCategories().stream() + .map(MetricCategory::getName) + .collect(toUnmodifiableSet()); metricsOptions.metricsPrometheusJob = config.getPrometheusJob(); metricsOptions.isMetricsPushEnabled = config.isPushEnabled(); metricsOptions.metricsPushHost = config.getPushHost(); @@ -126,7 +133,8 @@ public List getCLIOptions() { arity = "1..*", description = "Comma separated list of categories to track metrics for (default: ${DEFAULT-VALUE})") - private Set metricCategories = DEFAULT_METRIC_CATEGORIES; + private Set metricCategories = + DEFAULT_METRIC_CATEGORIES.stream().map(MetricCategory::getName).collect(toUnmodifiableSet()); @CommandLine.Option( names = {"--metrics-push-enabled"}, @@ -216,7 +224,13 @@ public Integer getMetricsPort() { * @return the metric categories */ public Set 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 String getMetricsPrometheusJob() { 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) private final MetricsOptions.Unstable unstableOptions = new MetricsOptions.Unstable(); diff --git a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java index 019e8d83cd3..cf3c40860d3 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java @@ -40,6 +40,7 @@ import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import org.hyperledger.besu.BesuInfo; import org.hyperledger.besu.cli.config.EthNetworkConfig; @@ -99,7 +100,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import picocli.CommandLine; @@ -566,7 +566,7 @@ public void genesisAndNetworkMustNotBeUsedTogether() throws Exception { parseCommand("--genesis-file", genesisFile.toString(), "--network", "mainnet"); - Mockito.verifyNoInteractions(mockRunnerBuilder); + verifyNoInteractions(mockRunnerBuilder); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)) @@ -578,7 +578,7 @@ public void nonExistentGenesisGivesError() { final String nonExistentGenesis = "non-existent-genesis.json"; parseCommand("--genesis-file", nonExistentGenesis); - Mockito.verifyNoInteractions(mockRunnerBuilder); + verifyNoInteractions(mockRunnerBuilder); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).startsWith("Unable to load genesis file"); @@ -1177,7 +1177,7 @@ public void remoteConnectionsPercentageWithInvalidFormatMustFail() { parseCommand( "--remote-connections-limit-enabled", "--remote-connections-max-percentage", "invalid"); - Mockito.verifyNoInteractions(mockRunnerBuilder); + verifyNoInteractions(mockRunnerBuilder); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)) .contains( @@ -1190,7 +1190,7 @@ public void remoteConnectionsPercentageWithOutOfRangeMustFail() { parseCommand( "--remote-connections-limit-enabled", "--remote-connections-max-percentage", "150"); - Mockito.verifyNoInteractions(mockRunnerBuilder); + verifyNoInteractions(mockRunnerBuilder); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)) .contains( @@ -1225,7 +1225,7 @@ public void syncMode_full_requires_bonsaiLimitTrieLogsToBeDisabled() { @Test public void syncMode_invalid() { parseCommand("--sync-mode", "bogus"); - Mockito.verifyNoInteractions(mockRunnerBuilder); + verifyNoInteractions(mockRunnerBuilder); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)) @@ -1275,7 +1275,7 @@ public void syncMode_snap_by_default() { public void helpShouldDisplayFastSyncOptions() { parseCommand("--help"); - Mockito.verifyNoInteractions(mockRunnerBuilder); + verifyNoInteractions(mockRunnerBuilder); assertThat(commandOutput.toString(UTF_8)).contains("--fast-sync-min-peers"); // whitelist is now a hidden option @@ -1335,7 +1335,7 @@ public void parsesValidSyncMinPeersOption() { public void parsesInvalidFastSyncMinPeersOptionWrongFormatShouldFail() { parseCommand("--sync-mode", "FAST", "--fast-sync-min-peers", "ten"); - Mockito.verifyNoInteractions(mockRunnerBuilder); + verifyNoInteractions(mockRunnerBuilder); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)) .contains("Invalid value for option '--fast-sync-min-peers': 'ten' is not an int"); @@ -1358,7 +1358,7 @@ public void netRestrictParsedCorrectly() { public void netRestrictInvalidShouldFail() { final String subnet = "127.0.0.1/abc"; parseCommand("--net-restrict", subnet); - Mockito.verifyNoInteractions(mockRunnerBuilder); + verifyNoInteractions(mockRunnerBuilder); assertThat(commandErrorOutput.toString(UTF_8)) .contains("Invalid value for option '--net-restrict'"); } @@ -1382,7 +1382,7 @@ public void ethStatsContactOptionIsParsedCorrectly() { @Test public void ethStatsContactOptionCannotBeUsedWithoutEthStatsServerProvided() { parseCommand("--ethstats-contact", "besu-updated"); - Mockito.verifyNoInteractions(mockRunnerBuilder); + verifyNoInteractions(mockRunnerBuilder); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)) .contains( @@ -1435,7 +1435,7 @@ public void bonsaiLimitTrieLogsDisabledWhenFullSyncEnabled() { public void parsesInvalidWhenFullSyncAndBonsaiLimitTrieLogsExplicitlyTrue() { parseCommand("--sync-mode=FULL", "--bonsai-limit-trie-logs-enabled=true"); - Mockito.verifyNoInteractions(mockRunnerBuilder); + verifyNoInteractions(mockRunnerBuilder); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)) .contains( @@ -1467,7 +1467,7 @@ public void parsesInvalidBonsaiHistoricalBlockLimitOption() { parseCommand("--data-storage-format", "BONSAI", "--bonsai-maximum-back-layers-to-load", "ten"); - Mockito.verifyNoInteractions(mockRunnerBuilder); + verifyNoInteractions(mockRunnerBuilder); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)) .contains( @@ -1501,7 +1501,7 @@ public void dnsUpdateEnabledOptionIsParsedCorrectly() { @Test public void dnsUpdateEnabledOptionCannotBeUsedWithoutDnsEnabled() { parseCommand("--Xdns-update-enabled", "true"); - Mockito.verifyNoInteractions(mockRunnerBuilder); + verifyNoInteractions(mockRunnerBuilder); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)) .contains( @@ -1720,6 +1720,17 @@ public void metricsCategoryPropertyMustBeUsed() { 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 public void metricsPushEnabledPropertyMustBeUsed() { parseCommand("--metrics-push-enabled"); @@ -1799,7 +1810,7 @@ public void metricsPrometheusJobMustBeUsed() { public void metricsAndMetricsPushMustNotBeUsedTogether() { parseCommand("--metrics-enabled", "--metrics-push-enabled"); - Mockito.verifyNoInteractions(mockRunnerBuilder); + verifyNoInteractions(mockRunnerBuilder); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)) @@ -2023,7 +2034,7 @@ private void networkValuesCanBeOverridden(final String network) { public void fullCLIOptionsShown() { parseCommand("--help"); - Mockito.verifyNoInteractions(mockRunnerBuilder); + verifyNoInteractions(mockRunnerBuilder); assertThat(commandOutput.toString(UTF_8)).contains("--config-file"); assertThat(commandOutput.toString(UTF_8)).contains("--data-path"); diff --git a/besu/src/test/java/org/hyperledger/besu/cli/converter/MetricCategoryConverterTest.java b/besu/src/test/java/org/hyperledger/besu/cli/converter/MetricCategoryConverterTest.java deleted file mode 100644 index 8c78b7fecc2..00000000000 --- a/besu/src/test/java/org/hyperledger/besu/cli/converter/MetricCategoryConverterTest.java +++ /dev/null @@ -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"); - } -} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/MetricsOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/MetricsOptionsTest.java index 992dcc437d4..7f35effb7d6 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/options/MetricsOptionsTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/MetricsOptionsTest.java @@ -15,14 +15,26 @@ package org.hyperledger.besu.cli.options; 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.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) public class MetricsOptionsTest extends AbstractCLIOptionsTest { + private MetricCategoryRegistryImpl categoryRegistry; + + @BeforeEach + public void setUp() { + categoryRegistry = new MetricCategoryRegistryImpl(); + categoryRegistry.addCategories(BesuMetricCategory.class); + categoryRegistry.addCategories(StandardMetricCategory.class); + } @Override protected MetricsConfiguration.Builder createDefaultDomainObject() { @@ -39,7 +51,9 @@ protected MetricsConfiguration.Builder createCustomizedDomainObject() { @Override protected MetricsOptions optionsFromDomainObject( final MetricsConfiguration.Builder domainObject) { - return MetricsOptions.fromConfiguration(domainObject.build()); + final var options = MetricsOptions.fromConfiguration(domainObject.build()); + options.setMetricCategoryRegistry(categoryRegistry); + return options; } @Override diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricCategoryRegistryImpl.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricCategoryRegistryImpl.java index b0a31793bcc..b78bcf18a8b 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricCategoryRegistryImpl.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricCategoryRegistryImpl.java @@ -17,28 +17,55 @@ import org.hyperledger.besu.plugin.services.metrics.MetricCategory; import org.hyperledger.besu.plugin.services.metrics.MetricCategoryRegistry; -import java.util.ArrayList; -import java.util.List; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; /** The Metric category registry implementation. */ public class MetricCategoryRegistryImpl implements MetricCategoryRegistry { - - private final List metricCategories = new ArrayList<>(); + private final Map metricCategories = new HashMap<>(); /** Default constructor */ public MetricCategoryRegistryImpl() {} /** - * Gets metric categories. + * Add Metrics categories. * - * @return the metric categories + * @param the type parameter + * @param categoryEnum the category enum */ - public List getMetricCategories() { - return metricCategories; + public & MetricCategory> void addCategories(final Class categoryEnum) { + EnumSet.allOf(categoryEnum).forEach(this::addMetricCategory); } + /** + * Add registry category. + * + * @param metricCategory the metric category + */ @Override - public void addMetricCategory(final MetricCategory newMetricCategory) { - metricCategories.add(newMetricCategory); + public void addMetricCategory(final MetricCategory metricCategory) { + 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)); } }