diff --git a/CHANGELOG.md b/CHANGELOG.md index 63ba82f3ab..1fd14d5336 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * Updated the libraries for secp256k1 and AltBN series precompiles. These updates provide significant performance improvements to those areas. [\#1499](https://github.com/hyperledger/besu/pull/1499) * Provide MegaGas/second measurements in the log when doing a full block import, such as the catch up phase of a fast sync. [\#1512](https://github.com/hyperledger/besu/pull/1512) * Added new endpoints to get miner data, `eth_getMinerDataByBlockHash` and `eth_getMinerDataByBlockNumber`. [\#1538](https://github.com/hyperledger/besu/pull/1538) +* Added direct support for OpenTelemetry metrics [\#1492](https://github.com/hyperledger/besu/pull/1492) ### Bug Fixes diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java index 4531403925..e4621b9ac0 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java @@ -34,8 +34,8 @@ import org.hyperledger.besu.ethereum.p2p.peers.EnodeURL; import org.hyperledger.besu.ethereum.permissioning.PermissioningConfiguration; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStorageProvider; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStorageProviderBuilder; +import org.hyperledger.besu.metrics.MetricsSystemFactory; import org.hyperledger.besu.metrics.ObservableMetricsSystem; -import org.hyperledger.besu.metrics.prometheus.PrometheusMetricsSystem; import org.hyperledger.besu.plugin.services.BesuConfiguration; import org.hyperledger.besu.plugin.services.BesuEvents; import org.hyperledger.besu.plugin.services.PicoCLIOptions; @@ -128,7 +128,7 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner { node, storageService, securityModuleService, commonPluginConfiguration)); final ObservableMetricsSystem metricsSystem = - PrometheusMetricsSystem.init(node.getMetricsConfiguration()); + MetricsSystemFactory.create(node.getMetricsConfiguration()); final List bootnodes = node.getConfiguration().getBootnodes().stream() .map(EnodeURL::fromURI) diff --git a/besu/build.gradle b/besu/build.gradle index 3aa2cdffed..936fce81d8 100644 --- a/besu/build.gradle +++ b/besu/build.gradle @@ -70,6 +70,7 @@ dependencies { runtimeOnly 'org.apache.logging.log4j:log4j-slf4j-impl' + runtimeOnly 'org.apache.logging.log4j:log4j-jul' runtimeOnly 'com.splunk.logging:splunk-library-javalogging' runtimeOnly 'org.fusesource.jansi:jansi' // for color logging in windows diff --git a/besu/src/main/java/org/hyperledger/besu/Runner.java b/besu/src/main/java/org/hyperledger/besu/Runner.java index e2b346e74c..7180d04209 100644 --- a/besu/src/main/java/org/hyperledger/besu/Runner.java +++ b/besu/src/main/java/org/hyperledger/besu/Runner.java @@ -25,7 +25,7 @@ import org.hyperledger.besu.ethereum.p2p.network.NetworkRunner; import org.hyperledger.besu.ethereum.p2p.peers.EnodeURL; import org.hyperledger.besu.ethereum.stratum.StratumServer; import org.hyperledger.besu.ethstats.EthStatsService; -import org.hyperledger.besu.metrics.prometheus.MetricsService; +import org.hyperledger.besu.metrics.MetricsService; import org.hyperledger.besu.nat.NatService; import java.io.File; diff --git a/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java b/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java index b4f28667d5..63c626953a 100644 --- a/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java @@ -93,9 +93,9 @@ import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.ethstats.EthStatsService; import org.hyperledger.besu.ethstats.util.NetstatsUrl; +import org.hyperledger.besu.metrics.MetricsService; import org.hyperledger.besu.metrics.ObservableMetricsSystem; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; -import org.hyperledger.besu.metrics.prometheus.MetricsService; import org.hyperledger.besu.nat.NatMethod; import org.hyperledger.besu.nat.NatService; import org.hyperledger.besu.nat.core.NatManager; @@ -603,10 +603,7 @@ public class RunnerBuilder { createPrivateTransactionObserver(subscriptionManager, privacyParameters); } - Optional metricsService = Optional.empty(); - if (metricsConfiguration.isEnabled() || metricsConfiguration.isPushEnabled()) { - metricsService = Optional.of(createMetricsService(vertx, metricsConfiguration)); - } + Optional metricsService = createMetricsService(vertx, metricsConfiguration); final Optional ethStatsService; if (!Strings.isNullOrEmpty(ethstatsUrl)) { @@ -884,7 +881,7 @@ public class RunnerBuilder { return new WebSocketService(vertx, configuration, websocketRequestHandler); } - private MetricsService createMetricsService( + private Optional createMetricsService( final Vertx vertx, final MetricsConfiguration configuration) { return MetricsService.create(vertx, configuration, metricsSystem); } 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 43d4d66ea1..49b32af3ce 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -28,6 +28,7 @@ import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.DEFAULT_JSON_RPC import static org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration.DEFAULT_WEBSOCKET_PORT; import static org.hyperledger.besu.ethereum.permissioning.QuorumPermissioningConfiguration.QIP714_DEFAULT_BLOCK; import static org.hyperledger.besu.metrics.BesuMetricCategory.DEFAULT_METRIC_CATEGORIES; +import static org.hyperledger.besu.metrics.MetricsProtocol.PROMETHEUS; import static org.hyperledger.besu.metrics.prometheus.MetricsConfiguration.DEFAULT_METRICS_PORT; import static org.hyperledger.besu.metrics.prometheus.MetricsConfiguration.DEFAULT_METRICS_PUSH_PORT; import static org.hyperledger.besu.nat.kubernetes.KubernetesNatManager.DEFAULT_BESU_SERVICE_NAME_FILTER; @@ -116,10 +117,11 @@ import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStorageProviderBui import org.hyperledger.besu.ethereum.worldstate.PrunerConfiguration; import org.hyperledger.besu.metrics.BesuMetricCategory; import org.hyperledger.besu.metrics.MetricCategoryRegistryImpl; +import org.hyperledger.besu.metrics.MetricsProtocol; +import org.hyperledger.besu.metrics.MetricsSystemFactory; import org.hyperledger.besu.metrics.ObservableMetricsSystem; import org.hyperledger.besu.metrics.StandardMetricCategory; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; -import org.hyperledger.besu.metrics.prometheus.PrometheusMetricsSystem; import org.hyperledger.besu.metrics.vertx.VertxMetricsAdapterFactory; import org.hyperledger.besu.nat.NatMethod; import org.hyperledger.besu.plugin.services.BesuConfiguration; @@ -648,6 +650,13 @@ public class BesuCommand implements DefaultCommandValues, Runnable { description = "Set to start the metrics exporter (default: ${DEFAULT-VALUE})") private final Boolean isMetricsEnabled = false; + @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. + @Option( + names = {"--metrics-protocol"}, + description = + "Metrics protocol, one of PROMETHEUS, OPENTELEMETRY or NONE. (default: ${DEFAULT-VALUE})") + private MetricsProtocol metricsProtocol = PROMETHEUS; + @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. @Option( names = {"--metrics-host"}, @@ -1022,7 +1031,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { private BesuController besuController; private BesuConfiguration pluginCommonConfiguration; private final Supplier metricsSystem = - Suppliers.memoize(() -> PrometheusMetricsSystem.init(metricsConfiguration())); + Suppliers.memoize(() -> MetricsSystemFactory.create(metricsConfiguration())); private Vertx vertx; private EnodeDnsConfiguration enodeDnsConfiguration; @@ -1147,6 +1156,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { commandLine.registerConverter(Bytes.class, Bytes::fromHexString); commandLine.registerConverter(Level.class, Level::valueOf); commandLine.registerConverter(SyncMode.class, SyncMode::fromString); + commandLine.registerConverter(MetricsProtocol.class, MetricsProtocol::fromString); commandLine.registerConverter(UInt256.class, (arg) -> UInt256.valueOf(new BigInteger(arg))); commandLine.registerConverter(Wei.class, (arg) -> Wei.of(Long.parseUnsignedLong(arg))); commandLine.registerConverter(PositiveNumber.class, PositiveNumber::fromString); @@ -1734,6 +1744,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { .enabled(isMetricsEnabled) .host(metricsHost) .port(metricsPort) + .protocol(metricsProtocol) .metricCategories(metricCategories) .pushEnabled(isMetricsPushEnabled) .pushHost(metricsPushHost) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/blocks/BlocksSubCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/blocks/BlocksSubCommand.java index c3c88d66d2..d57c953755 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/blocks/BlocksSubCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/blocks/BlocksSubCommand.java @@ -33,8 +33,8 @@ import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.Address; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Wei; +import org.hyperledger.besu.metrics.MetricsService; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; -import org.hyperledger.besu.metrics.prometheus.MetricsService; import java.io.File; import java.io.FileNotFoundException; @@ -434,18 +434,13 @@ public class BlocksSubCommand implements Runnable { } private static Optional initMetrics(final BlocksSubCommand parentCommand) { - Optional metricsService = Optional.empty(); final MetricsConfiguration metricsConfiguration = parentCommand.parentCommand.metricsConfiguration(); - if (metricsConfiguration.isEnabled() || metricsConfiguration.isPushEnabled()) { - metricsService = - Optional.of( - MetricsService.create( - Vertx.vertx(), - metricsConfiguration, - parentCommand.parentCommand.getMetricsSystem())); - metricsService.ifPresent(MetricsService::start); - } + + Optional metricsService = + MetricsService.create( + Vertx.vertx(), metricsConfiguration, parentCommand.parentCommand.getMetricsSystem()); + metricsService.ifPresent(MetricsService::start); return metricsService; } } diff --git a/besu/src/test/resources/everything_config.toml b/besu/src/test/resources/everything_config.toml index 702db9448d..3b32888c72 100644 --- a/besu/src/test/resources/everything_config.toml +++ b/besu/src/test/resources/everything_config.toml @@ -92,6 +92,7 @@ rpc-ws-authentication-jwt-public-key-file="none" # Prometheus Metrics Endpoint metrics-enabled=false +metrics-protocol="prometheus" metrics-host="8.6.7.5" metrics-port=309 metrics-category=["RPC"] @@ -164,4 +165,4 @@ Xethstats-contact="contact@mail.n" # feature flags Xsecp256k1-native-enabled=false -Xaltbn128-native-enabled=false \ No newline at end of file +Xaltbn128-native-enabled=false diff --git a/build.gradle b/build.gradle index 2d0381b5bd..0bad48c070 100644 --- a/build.gradle +++ b/build.gradle @@ -469,6 +469,8 @@ applicationDefaultJvmArgs = [ // We shutdown log4j ourselves, as otherwise this shutdown hook runs before our own and whatever // happens during shutdown is not logged. '-Dlog4j.shutdownHookEnabled=false', + // Redirect java.util.logging loggers to use log4j2. + '-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager', // Suppress Java JPMS warnings. Document the reason for each suppression. // Bouncy Castle needs access to sun.security.provider, which is not open by default. '--add-opens', diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/DefaultBlockchainTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/DefaultBlockchainTest.java index e6caf26a26..76d312715d 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/DefaultBlockchainTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/DefaultBlockchainTest.java @@ -28,9 +28,9 @@ import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; +import org.hyperledger.besu.metrics.MetricsSystemFactory; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; -import org.hyperledger.besu.metrics.prometheus.PrometheusMetricsSystem; import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; @@ -164,7 +164,7 @@ public class DefaultBlockchainTest { final Blockchain blockchain = DefaultBlockchain.create( createStorage(kvStore), - PrometheusMetricsSystem.init(MetricsConfiguration.builder().enabled(true).build()), + MetricsSystemFactory.create(MetricsConfiguration.builder().enabled(true).build()), 0); for (int i = 0; i < blocks.size(); i++) { diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java index 036d278908..6da512163c 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java @@ -185,7 +185,7 @@ public class EvmToolCommand implements Runnable { : GenesisFileModule.createGenesisModule(genesisFile) : GenesisFileModule.createGenesisModule(network)) .evmToolCommandOptionsModule(daggerOptions) - .metricsSystemModule(new PrometheusMetricsSystemModule()) + .metricsSystemModule(new MetricsSystemModule()) .build(); final BlockHeader blockHeader = diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MetricsSystemModule.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MetricsSystemModule.java index 0f6a361b3b..975d2d7083 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MetricsSystemModule.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MetricsSystemModule.java @@ -16,6 +16,8 @@ package org.hyperledger.besu.evmtool; +import org.hyperledger.besu.metrics.MetricsSystemFactory; +import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; import org.hyperledger.besu.plugin.services.MetricsSystem; import dagger.Module; @@ -27,6 +29,6 @@ public class MetricsSystemModule { @Provides MetricsSystem getMetricsSystem() { - throw new RuntimeException("Abstract"); + return MetricsSystemFactory.create(MetricsConfiguration.builder().build()); } } diff --git a/gradle/versions.gradle b/gradle/versions.gradle index d9fdf4b341..a8c41ed6d0 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -45,12 +45,19 @@ dependencyManagement { dependency 'info.picocli:picocli:4.5.0' + dependency 'io.grpc:grpc-netty:1.33.0' + dependency 'io.kubernetes:client-java:5.0.0' dependency 'io.pkts:pkts-core:3.0.7' dependency group: 'io.opentelemetry.instrumentation.auto', name: 'opentelemetry-javaagent', version: '0.8.0', classifier: 'all' + dependency 'io.opentelemetry:opentelemetry-api:0.9.1' + dependency 'io.opentelemetry:opentelemetry-sdk:0.9.1' + dependency 'io.opentelemetry:opentelemetry-exporters-otlp:0.9.1' + dependency 'io.opentelemetry:opentelemetry-extension-runtime-metrics:0.9.1' + dependency 'io.prometheus:simpleclient:0.9.0' dependency 'io.prometheus:simpleclient_common:0.9.0' dependency 'io.prometheus:simpleclient_hotspot:0.9.0' @@ -75,6 +82,7 @@ dependencyManagement { dependency 'org.apache.logging.log4j:log4j-api:2.13.3' dependency 'org.apache.logging.log4j:log4j-core:2.13.3' + dependency 'org.apache.logging.log4j:log4j-jul:2.13.3' dependency 'org.apache.logging.log4j:log4j-slf4j-impl:2.13.3' dependency 'org.fusesource.jansi:jansi:1.8' diff --git a/metrics/core/build.gradle b/metrics/core/build.gradle index 019441fa2e..6be781d625 100644 --- a/metrics/core/build.gradle +++ b/metrics/core/build.gradle @@ -38,6 +38,12 @@ dependencies { implementation project(':plugin-api') implementation 'com.google.guava:guava' + implementation 'io.grpc:grpc-netty' + implementation 'io.opentelemetry:opentelemetry-api' + implementation 'io.opentelemetry:opentelemetry-sdk' + implementation 'io.opentelemetry:opentelemetry-extension-runtime-metrics' + implementation 'io.opentelemetry:opentelemetry-exporters-otlp' + implementation 'io.prometheus:simpleclient' implementation 'io.prometheus:simpleclient_common' implementation 'io.prometheus:simpleclient_hotspot' diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/PrometheusMetricsSystemModule.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsProtocol.java similarity index 58% rename from ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/PrometheusMetricsSystemModule.java rename to metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsProtocol.java index 64923138df..17af8f6b29 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/PrometheusMetricsSystemModule.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsProtocol.java @@ -11,19 +11,21 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ +package org.hyperledger.besu.metrics; -package org.hyperledger.besu.evmtool; - -import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; -import org.hyperledger.besu.metrics.prometheus.PrometheusMetricsSystem; -import org.hyperledger.besu.plugin.services.MetricsSystem; - -public class PrometheusMetricsSystemModule extends MetricsSystemModule { +/** Enumeration of metrics protocols supported by Besu. */ +public enum MetricsProtocol { + PROMETHEUS, + OPENTELEMETRY, + NONE; - @Override - public MetricsSystem getMetricsSystem() { - return PrometheusMetricsSystem.init(MetricsConfiguration.builder().build()); + public static MetricsProtocol fromString(final String str) { + for (final MetricsProtocol mode : MetricsProtocol.values()) { + if (mode.name().equalsIgnoreCase(str)) { + return mode; + } + } + return null; } } diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsService.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsService.java similarity index 52% rename from metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsService.java rename to metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsService.java index 6e0d5f0372..57bdaf94c6 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsService.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsService.java @@ -12,8 +12,13 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.metrics.prometheus; +package org.hyperledger.besu.metrics; +import org.hyperledger.besu.metrics.opentelemetry.MetricsOtelGrpcPushService; +import org.hyperledger.besu.metrics.opentelemetry.OpenTelemetrySystem; +import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; +import org.hyperledger.besu.metrics.prometheus.MetricsHttpService; +import org.hyperledger.besu.metrics.prometheus.MetricsPushGatewayService; import org.hyperledger.besu.plugin.services.MetricsSystem; import java.util.Optional; @@ -21,18 +26,33 @@ import java.util.concurrent.CompletableFuture; import io.vertx.core.Vertx; +/** + * Service responsible for exposing metrics to the outside, either through a port and network + * interface or pushing. + */ public interface MetricsService { - static MetricsService create( + static Optional create( final Vertx vertx, final MetricsConfiguration configuration, final MetricsSystem metricsSystem) { - if (configuration.isEnabled()) { - return new MetricsHttpService(vertx, configuration, metricsSystem); - } else if (configuration.isPushEnabled()) { - return new MetricsPushGatewayService(configuration, metricsSystem); + if (configuration.getProtocol() == MetricsProtocol.PROMETHEUS) { + if (configuration.isEnabled()) { + return Optional.of(new MetricsHttpService(vertx, configuration, metricsSystem)); + } else if (configuration.isPushEnabled()) { + return Optional.of(new MetricsPushGatewayService(configuration, metricsSystem)); + } else { + return Optional.empty(); + } + } else if (configuration.getProtocol() == MetricsProtocol.OPENTELEMETRY) { + if (configuration.isEnabled()) { + return Optional.of( + new MetricsOtelGrpcPushService(configuration, (OpenTelemetrySystem) metricsSystem)); + } else { + return Optional.empty(); + } } else { - throw new RuntimeException("No metrics service enabled."); + return Optional.empty(); } } diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsSystemFactory.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsSystemFactory.java new file mode 100644 index 0000000000..b80b617973 --- /dev/null +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsSystemFactory.java @@ -0,0 +1,59 @@ +/* + * 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.metrics; + +import static org.hyperledger.besu.metrics.MetricsProtocol.OPENTELEMETRY; +import static org.hyperledger.besu.metrics.MetricsProtocol.PROMETHEUS; + +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.metrics.opentelemetry.OpenTelemetrySystem; +import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; +import org.hyperledger.besu.metrics.prometheus.PrometheusMetricsSystem; + +/** Creates a new metric system based on configuration. */ +public class MetricsSystemFactory { + + private MetricsSystemFactory() {} + + /** + * Creates and starts a new metric system to observe the behavior of the client + * + * @param metricsConfiguration the configuration of the metric system + * @return a new metric system + */ + public static ObservableMetricsSystem create(final MetricsConfiguration metricsConfiguration) { + if (!metricsConfiguration.isEnabled() && !metricsConfiguration.isPushEnabled()) { + return new NoOpMetricsSystem(); + } + if (PROMETHEUS.equals(metricsConfiguration.getProtocol())) { + final PrometheusMetricsSystem metricsSystem = + new PrometheusMetricsSystem( + metricsConfiguration.getMetricCategories(), metricsConfiguration.isTimersEnabled()); + metricsSystem.init(); + return metricsSystem; + } else if (OPENTELEMETRY.equals(metricsConfiguration.getProtocol())) { + final OpenTelemetrySystem metricsSystem = + new OpenTelemetrySystem( + metricsConfiguration.getMetricCategories(), + metricsConfiguration.isTimersEnabled(), + metricsConfiguration.getPrometheusJob()); + metricsSystem.initDefaults(); + return metricsSystem; + } else { + throw new IllegalArgumentException( + "Invalid metrics protocol " + metricsConfiguration.getProtocol()); + } + } +} diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/ObservableMetricsSystem.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/ObservableMetricsSystem.java index 99f29fde5c..757263923c 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/ObservableMetricsSystem.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/ObservableMetricsSystem.java @@ -17,6 +17,7 @@ package org.hyperledger.besu.metrics; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.plugin.services.metrics.MetricCategory; +import java.util.Set; import java.util.stream.Stream; public interface ObservableMetricsSystem extends MetricsSystem { @@ -24,4 +25,22 @@ public interface ObservableMetricsSystem extends MetricsSystem { Stream streamObservations(MetricCategory category); Stream streamObservations(); + + /** + * Provides an immutable view into the metric categories enabled for metric collection. + * + * @return the set of enabled metric categories. + */ + Set getEnabledCategories(); + + /** + * Checks if a particular category of metrics is enabled. + * + * @param category the category to check + * @return true if the category is enabled, false otherwise + */ + default boolean isCategoryEnabled(final MetricCategory category) { + return getEnabledCategories().stream() + .anyMatch(metricCategory -> metricCategory.getName().equals(category.getName())); + } } diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/noop/NoOpMetricsSystem.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/noop/NoOpMetricsSystem.java index 5551ccc5e7..c037fc744c 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/noop/NoOpMetricsSystem.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/noop/NoOpMetricsSystem.java @@ -21,6 +21,8 @@ import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; import org.hyperledger.besu.plugin.services.metrics.MetricCategory; import org.hyperledger.besu.plugin.services.metrics.OperationTimer; +import java.util.Collections; +import java.util.Set; import java.util.function.DoubleSupplier; import java.util.stream.Stream; @@ -98,6 +100,11 @@ public class NoOpMetricsSystem implements ObservableMetricsSystem { return Stream.empty(); } + @Override + public Set getEnabledCategories() { + return Collections.emptySet(); + } + public static class LabelCountingNoOpMetric implements LabelledMetric { final int labelCount; diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/MetricsOtelGrpcPushService.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/MetricsOtelGrpcPushService.java new file mode 100644 index 0000000000..337d83388e --- /dev/null +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/MetricsOtelGrpcPushService.java @@ -0,0 +1,67 @@ +/* + * 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.metrics.opentelemetry; + +import org.hyperledger.besu.metrics.MetricsService; +import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; + +import java.util.Collections; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +import io.opentelemetry.exporters.otlp.OtlpGrpcMetricExporter; +import io.opentelemetry.sdk.metrics.export.IntervalMetricReader; + +public class MetricsOtelGrpcPushService implements MetricsService { + + private final MetricsConfiguration configuration; + private final OpenTelemetrySystem metricsSystem; + private IntervalMetricReader periodicReader; + + public MetricsOtelGrpcPushService( + final MetricsConfiguration configuration, final OpenTelemetrySystem metricsSystem) { + this.configuration = configuration; + this.metricsSystem = metricsSystem; + } + + @Override + public CompletableFuture start() { + OtlpGrpcMetricExporter exporter = OtlpGrpcMetricExporter.getDefault(); + IntervalMetricReader.Builder builder = + IntervalMetricReader.builder() + .setExportIntervalMillis(configuration.getPushInterval() * 1000L) + .readEnvironmentVariables() + .readSystemProperties() + .setMetricProducers( + Collections.singleton(metricsSystem.getMeterSdkProvider().getMetricProducer())) + .setMetricExporter(exporter); + this.periodicReader = builder.build(); + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletableFuture stop() { + if (periodicReader != null) { + periodicReader.shutdown(); + } + return CompletableFuture.completedFuture(null); + } + + @Override + public Optional getPort() { + return Optional.empty(); + } +} diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetryCounter.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetryCounter.java new file mode 100644 index 0000000000..73a718357d --- /dev/null +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetryCounter.java @@ -0,0 +1,65 @@ +/* + * 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.metrics.opentelemetry; + +import org.hyperledger.besu.plugin.services.metrics.Counter; +import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; + +import java.util.ArrayList; +import java.util.List; + +import io.opentelemetry.common.Labels; +import io.opentelemetry.metrics.LongCounter; + +public class OpenTelemetryCounter implements LabelledMetric { + + private final LongCounter counter; + private final String[] labelNames; + + public OpenTelemetryCounter(final LongCounter counter, final String... labelNames) { + this.counter = counter; + this.labelNames = labelNames; + } + + @Override + public Counter labels(final String... labelValues) { + List labelKeysAndValues = new ArrayList<>(); + for (int i = 0; i < labelNames.length; i++) { + labelKeysAndValues.add(labelNames[i]); + labelKeysAndValues.add(labelValues[i]); + } + final Labels labels = Labels.of(labelKeysAndValues.toArray(new String[] {})); + LongCounter.BoundLongCounter boundLongCounter = counter.bind(labels); + return new OpenTelemetryCounter.UnlabelledCounter(boundLongCounter); + } + + private static class UnlabelledCounter implements Counter { + private final LongCounter.BoundLongCounter counter; + + private UnlabelledCounter(final LongCounter.BoundLongCounter counter) { + this.counter = counter; + } + + @Override + public void inc() { + counter.add(1); + } + + @Override + public void inc(final long amount) { + counter.add(amount); + } + } +} diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetrySystem.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetrySystem.java new file mode 100644 index 0000000000..4f1c9fd06f --- /dev/null +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetrySystem.java @@ -0,0 +1,290 @@ +/* + * 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.metrics.opentelemetry; + +import org.hyperledger.besu.metrics.BesuMetricCategory; +import org.hyperledger.besu.metrics.ObservableMetricsSystem; +import org.hyperledger.besu.metrics.Observation; +import org.hyperledger.besu.metrics.StandardMetricCategory; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.metrics.Counter; +import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; +import org.hyperledger.besu.plugin.services.metrics.MetricCategory; +import org.hyperledger.besu.plugin.services.metrics.OperationTimer; + +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryPoolMXBean; +import java.lang.management.MemoryUsage; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.DoubleSupplier; +import java.util.stream.Stream; + +import com.google.common.collect.ImmutableSet; +import io.opentelemetry.common.Attributes; +import io.opentelemetry.common.Labels; +import io.opentelemetry.metrics.DoubleValueObserver; +import io.opentelemetry.metrics.DoubleValueRecorder; +import io.opentelemetry.metrics.LongCounter; +import io.opentelemetry.metrics.LongSumObserver; +import io.opentelemetry.metrics.LongUpDownSumObserver; +import io.opentelemetry.metrics.Meter; +import io.opentelemetry.sdk.metrics.MeterSdkProvider; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.resources.ResourceAttributes; + +/** Metrics system relying on the native OpenTelemetry format. */ +public class OpenTelemetrySystem implements ObservableMetricsSystem { + private static final String TYPE_LABEL_KEY = "type"; + private static final String AREA_LABEL_KEY = "area"; + private static final String POOL_LABEL_KEY = "pool"; + private static final String USED = "used"; + private static final String COMMITTED = "committed"; + private static final String MAX = "max"; + private static final String HEAP = "heap"; + private static final String NON_HEAP = "non_heap"; + + private final Set enabledCategories; + private final boolean timersEnabled; + private final Map> cachedCounters = new ConcurrentHashMap<>(); + private final Map> cachedTimers = + new ConcurrentHashMap<>(); + private final MeterSdkProvider meterSdkProvider; + + public OpenTelemetrySystem( + final Set enabledCategories, + final boolean timersEnabled, + final String jobName) { + this.enabledCategories = ImmutableSet.copyOf(enabledCategories); + this.timersEnabled = timersEnabled; + Resource resource = + Resource.getDefault() + .merge( + Resource.create( + Attributes.newBuilder() + .setAttribute(ResourceAttributes.SERVICE_NAME, jobName) + .build())); + this.meterSdkProvider = MeterSdkProvider.builder().setResource(resource).build(); + } + + MeterSdkProvider getMeterSdkProvider() { + return meterSdkProvider; + } + + @Override + public Stream streamObservations(final MetricCategory category) { + return streamObservations().filter(metricData -> metricData.getCategory().equals(category)); + } + + @Override + public Stream streamObservations() { + Collection metricsList = meterSdkProvider.getMetricProducer().collectAllMetrics(); + return metricsList.stream().map(this::convertToObservations).flatMap(stream -> stream); + } + + private Stream convertToObservations(final MetricData metricData) { + List observations = new ArrayList<>(); + MetricCategory category = + categoryNameToMetricCategory(metricData.getInstrumentationLibraryInfo().getName()); + for (MetricData.Point point : metricData.getPoints()) { + List labels = new ArrayList<>(); + point.getLabels().forEach((k, v) -> labels.add(v)); + observations.add( + new Observation( + category, metricData.getName(), extractValue(metricData.getType(), point), labels)); + } + return observations.stream(); + } + + private MetricCategory categoryNameToMetricCategory(final String name) { + Set categories = + ImmutableSet.builder() + .addAll(EnumSet.allOf(BesuMetricCategory.class)) + .addAll(EnumSet.allOf(StandardMetricCategory.class)) + .build(); + for (MetricCategory category : categories) { + if (category.getName().equals(name)) { + return category; + } + } + throw new IllegalArgumentException("Invalid metric category: " + name); + } + + private Object extractValue(final MetricData.Type type, final MetricData.Point point) { + switch (type) { + case NON_MONOTONIC_LONG: + case MONOTONIC_LONG: + return ((MetricData.LongPoint) point).getValue(); + case NON_MONOTONIC_DOUBLE: + case MONOTONIC_DOUBLE: + return ((MetricData.DoublePoint) point).getValue(); + case SUMMARY: + return ((MetricData.SummaryPoint) point).getPercentileValues(); + default: + throw new UnsupportedOperationException("Unsupported type " + type); + } + } + + @Override + public LabelledMetric createLabelledCounter( + final MetricCategory category, + final String name, + final String help, + final String... labelNames) { + return cachedCounters.computeIfAbsent( + name, + (k) -> { + if (isCategoryEnabled(category)) { + final Meter meter = meterSdkProvider.get(category.getName()); + + final LongCounter counter = meter.longCounterBuilder(name).setDescription(help).build(); + return new OpenTelemetryCounter(counter, labelNames); + } else { + return NoOpMetricsSystem.getCounterLabelledMetric(labelNames.length); + } + }); + } + + @Override + public LabelledMetric createLabelledTimer( + final MetricCategory category, + final String name, + final String help, + final String... labelNames) { + return cachedTimers.computeIfAbsent( + name, + (k) -> { + if (timersEnabled && isCategoryEnabled(category)) { + final Meter meter = meterSdkProvider.get(category.getName()); + + final DoubleValueRecorder recorder = + meter.doubleValueRecorderBuilder(name).setDescription(help).build(); + return new OpenTelemetryTimer(recorder, labelNames); + } else { + return NoOpMetricsSystem.getOperationTimerLabelledMetric(labelNames.length); + } + }); + } + + @Override + public void createGauge( + final MetricCategory category, + final String name, + final String help, + final DoubleSupplier valueSupplier) { + if (isCategoryEnabled(category)) { + final Meter meter = meterSdkProvider.get(category.getName()); + DoubleValueObserver observer = + meter.doubleValueObserverBuilder(name).setDescription(help).build(); + observer.setCallback(result -> result.observe(valueSupplier.getAsDouble(), Labels.empty())); + } + } + + @Override + public Set getEnabledCategories() { + return enabledCategories; + } + + public void initDefaults() { + if (isCategoryEnabled(StandardMetricCategory.JVM)) { + collectGC(); + } + } + + private void collectGC() { + final List garbageCollectors = + ManagementFactory.getGarbageCollectorMXBeans(); + final MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean(); + final List poolBeans = ManagementFactory.getMemoryPoolMXBeans(); + final Meter meter = meterSdkProvider.get(StandardMetricCategory.JVM.getName()); + final LongSumObserver gcMetric = + meter + .longSumObserverBuilder("jvm.gc.collection") + .setDescription("Time spent in a given JVM garbage collector in milliseconds.") + .setUnit("ms") + .build(); + final List labelSets = new ArrayList<>(garbageCollectors.size()); + for (final GarbageCollectorMXBean gc : garbageCollectors) { + labelSets.add(Labels.of("gc", gc.getName())); + } + + gcMetric.setCallback( + resultLongObserver -> { + for (int i = 0; i < garbageCollectors.size(); i++) { + resultLongObserver.observe( + garbageCollectors.get(i).getCollectionTime(), labelSets.get(i)); + } + }); + + final LongUpDownSumObserver areaMetric = + meter + .longUpDownSumObserverBuilder("jvm.memory.area") + .setDescription("Bytes of a given JVM memory area.") + .setUnit("By") + .build(); + final Labels usedHeap = Labels.of(TYPE_LABEL_KEY, USED, AREA_LABEL_KEY, HEAP); + final Labels usedNonHeap = Labels.of(TYPE_LABEL_KEY, USED, AREA_LABEL_KEY, NON_HEAP); + final Labels committedHeap = Labels.of(TYPE_LABEL_KEY, COMMITTED, AREA_LABEL_KEY, HEAP); + final Labels committedNonHeap = Labels.of(TYPE_LABEL_KEY, COMMITTED, AREA_LABEL_KEY, NON_HEAP); + // TODO: Decide if max is needed or not. May be derived with some approximation from max(used). + final Labels maxHeap = Labels.of(TYPE_LABEL_KEY, MAX, AREA_LABEL_KEY, HEAP); + final Labels maxNonHeap = Labels.of(TYPE_LABEL_KEY, MAX, AREA_LABEL_KEY, NON_HEAP); + areaMetric.setCallback( + resultLongObserver -> { + MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage(); + MemoryUsage nonHeapUsage = memoryBean.getNonHeapMemoryUsage(); + resultLongObserver.observe(heapUsage.getUsed(), usedHeap); + resultLongObserver.observe(nonHeapUsage.getUsed(), usedNonHeap); + resultLongObserver.observe(heapUsage.getUsed(), committedHeap); + resultLongObserver.observe(nonHeapUsage.getUsed(), committedNonHeap); + resultLongObserver.observe(heapUsage.getUsed(), maxHeap); + resultLongObserver.observe(nonHeapUsage.getUsed(), maxNonHeap); + }); + + final LongUpDownSumObserver poolMetric = + meter + .longUpDownSumObserverBuilder("jvm.memory.pool") + .setDescription("Bytes of a given JVM memory pool.") + .setUnit("By") + .build(); + final List usedLabelSets = new ArrayList<>(poolBeans.size()); + final List committedLabelSets = new ArrayList<>(poolBeans.size()); + final List maxLabelSets = new ArrayList<>(poolBeans.size()); + for (final MemoryPoolMXBean pool : poolBeans) { + usedLabelSets.add(Labels.of(TYPE_LABEL_KEY, USED, POOL_LABEL_KEY, pool.getName())); + committedLabelSets.add(Labels.of(TYPE_LABEL_KEY, COMMITTED, POOL_LABEL_KEY, pool.getName())); + maxLabelSets.add(Labels.of(TYPE_LABEL_KEY, MAX, POOL_LABEL_KEY, pool.getName())); + } + poolMetric.setCallback( + resultLongObserver -> { + for (int i = 0; i < poolBeans.size(); i++) { + MemoryUsage poolUsage = poolBeans.get(i).getUsage(); + resultLongObserver.observe(poolUsage.getUsed(), usedLabelSets.get(i)); + resultLongObserver.observe(poolUsage.getCommitted(), committedLabelSets.get(i)); + // TODO: Decide if max is needed or not. May be derived with some approximation from + // max(used). + resultLongObserver.observe(poolUsage.getMax(), maxLabelSets.get(i)); + } + }); + } +} diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetryTimer.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetryTimer.java new file mode 100644 index 0000000000..359ad82b7c --- /dev/null +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetryTimer.java @@ -0,0 +1,53 @@ +/* + * 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.metrics.opentelemetry; + +import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; +import org.hyperledger.besu.plugin.services.metrics.OperationTimer; + +import java.util.ArrayList; +import java.util.List; + +import io.opentelemetry.common.Labels; +import io.opentelemetry.metrics.DoubleValueRecorder; + +public class OpenTelemetryTimer implements LabelledMetric { + + private final DoubleValueRecorder recorder; + private final String[] labelNames; + + public OpenTelemetryTimer(final DoubleValueRecorder recorder, final String... labelNames) { + this.recorder = recorder; + this.labelNames = labelNames; + } + + @Override + public OperationTimer labels(final String... labelValues) { + List labelKeysAndValues = new ArrayList<>(); + for (int i = 0; i < labelNames.length; i++) { + labelKeysAndValues.add(labelNames[i]); + labelKeysAndValues.add(labelValues[i]); + } + final Labels labels = Labels.of(labelKeysAndValues.toArray(new String[] {})); + return () -> { + final long startTime = System.nanoTime(); + return () -> { + long elapsed = System.nanoTime() - startTime; + recorder.record(elapsed, labels); + return elapsed / 1e9; + }; + }; + } +} diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsConfiguration.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsConfiguration.java index a437a1f644..0b863f9d0e 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsConfiguration.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsConfiguration.java @@ -16,6 +16,7 @@ package org.hyperledger.besu.metrics.prometheus; import static org.hyperledger.besu.metrics.BesuMetricCategory.DEFAULT_METRIC_CATEGORIES; +import org.hyperledger.besu.metrics.MetricsProtocol; import org.hyperledger.besu.plugin.services.metrics.MetricCategory; import java.util.Arrays; @@ -30,12 +31,13 @@ import com.google.common.base.MoreObjects; public class MetricsConfiguration { private static final String DEFAULT_METRICS_HOST = "127.0.0.1"; public static final int DEFAULT_METRICS_PORT = 9545; - + private static final MetricsProtocol DEFAULT_METRICS_PROTOCOL = MetricsProtocol.PROMETHEUS; private static final String DEFAULT_METRICS_PUSH_HOST = "127.0.0.1"; public static final int DEFAULT_METRICS_PUSH_PORT = 9001; public static final Boolean DEFAULT_TIMERS_ENABLED = true; private final boolean enabled; + private final MetricsProtocol protocol; private final int port; private int actualPort; private final String host; @@ -55,6 +57,7 @@ public class MetricsConfiguration { private MetricsConfiguration( final boolean enabled, final int port, + final MetricsProtocol protocol, final String host, final Set metricCategories, final boolean pushEnabled, @@ -66,6 +69,7 @@ public class MetricsConfiguration { final boolean timersEnabled) { this.enabled = enabled; this.port = port; + this.protocol = protocol; this.host = host; this.metricCategories = metricCategories; this.pushEnabled = pushEnabled; @@ -81,6 +85,10 @@ public class MetricsConfiguration { return enabled; } + public MetricsProtocol getProtocol() { + return protocol; + } + public String getHost() { return host; } @@ -139,6 +147,7 @@ public class MetricsConfiguration { public String toString() { return MoreObjects.toStringHelper(this) .add("enabled", enabled) + .add("protocol", protocol) .add("port", port) .add("host", host) .add("metricCategories", metricCategories) @@ -161,6 +170,7 @@ public class MetricsConfiguration { } final MetricsConfiguration that = (MetricsConfiguration) o; return enabled == that.enabled + && Objects.equals(protocol, that.protocol) && port == that.port && pushEnabled == that.pushEnabled && pushPort == that.pushPort @@ -176,6 +186,7 @@ public class MetricsConfiguration { public int hashCode() { return Objects.hash( enabled, + protocol, port, host, metricCategories, @@ -189,6 +200,7 @@ public class MetricsConfiguration { public static class Builder { private boolean enabled = false; + private MetricsProtocol protocol = DEFAULT_METRICS_PROTOCOL; private int port = DEFAULT_METRICS_PORT; private String host = DEFAULT_METRICS_HOST; private Set metricCategories = DEFAULT_METRIC_CATEGORIES; @@ -207,6 +219,11 @@ public class MetricsConfiguration { return this; } + public Builder protocol(final MetricsProtocol protocol) { + this.protocol = protocol; + return this; + } + public Builder port(final int port) { this.port = port; return this; @@ -268,6 +285,7 @@ public class MetricsConfiguration { return new MetricsConfiguration( enabled, port, + protocol, host, metricCategories, pushEnabled, diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsHttpService.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsHttpService.java index 215ef06462..dadd373909 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsHttpService.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsHttpService.java @@ -17,6 +17,7 @@ package org.hyperledger.besu.metrics.prometheus; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Streams.stream; +import org.hyperledger.besu.metrics.MetricsService; import org.hyperledger.besu.plugin.services.MetricsSystem; import java.io.ByteArrayOutputStream; @@ -45,7 +46,7 @@ import io.vertx.ext.web.RoutingContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -class MetricsHttpService implements MetricsService { +public class MetricsHttpService implements MetricsService { private static final Logger LOG = LogManager.getLogger(); private static final InetSocketAddress EMPTY_SOCKET_ADDRESS = new InetSocketAddress("0.0.0.0", 0); @@ -56,7 +57,7 @@ class MetricsHttpService implements MetricsService { private HttpServer httpServer; - MetricsHttpService( + public MetricsHttpService( final Vertx vertx, final MetricsConfiguration configuration, final MetricsSystem metricsSystem) { diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsPushGatewayService.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsPushGatewayService.java index b02c69cd48..871deaebb9 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsPushGatewayService.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsPushGatewayService.java @@ -16,6 +16,7 @@ package org.hyperledger.besu.metrics.prometheus; import static com.google.common.base.Preconditions.checkArgument; +import org.hyperledger.besu.metrics.MetricsService; import org.hyperledger.besu.plugin.services.MetricsSystem; import java.io.IOException; @@ -29,7 +30,7 @@ import io.prometheus.client.exporter.PushGateway; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -class MetricsPushGatewayService implements MetricsService { +public class MetricsPushGatewayService implements MetricsService { private static final Logger LOG = LogManager.getLogger(); private PushGateway pushGateway; @@ -37,7 +38,7 @@ class MetricsPushGatewayService implements MetricsService { private final MetricsConfiguration config; private final MetricsSystem metricsSystem; - MetricsPushGatewayService( + public MetricsPushGatewayService( final MetricsConfiguration configuration, final MetricsSystem metricsSystem) { this.metricsSystem = metricsSystem; validateConfig(configuration); diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystem.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystem.java index f5b093f48f..b4be6eba0b 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystem.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystem.java @@ -14,9 +14,6 @@ */ package org.hyperledger.besu.metrics.prometheus; -import static java.util.Arrays.asList; -import static java.util.Collections.singleton; - import org.hyperledger.besu.metrics.ObservableMetricsSystem; import org.hyperledger.besu.metrics.Observation; import org.hyperledger.besu.metrics.StandardMetricCategory; @@ -33,6 +30,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.DoubleSupplier; +import java.util.function.Supplier; import java.util.stream.Stream; import com.google.common.collect.ImmutableSet; @@ -61,35 +59,24 @@ public class PrometheusMetricsSystem implements ObservableMetricsSystem { private final Set enabledCategories; private final boolean timersEnabled; - PrometheusMetricsSystem( + public PrometheusMetricsSystem( final Set enabledCategories, final boolean timersEnabled) { this.enabledCategories = ImmutableSet.copyOf(enabledCategories); this.timersEnabled = timersEnabled; } - public static ObservableMetricsSystem init(final MetricsConfiguration metricsConfiguration) { - if (!metricsConfiguration.isEnabled() && !metricsConfiguration.isPushEnabled()) { - return new NoOpMetricsSystem(); - } - final PrometheusMetricsSystem metricsSystem = - new PrometheusMetricsSystem( - metricsConfiguration.getMetricCategories(), metricsConfiguration.isTimersEnabled()); - if (metricsSystem.isCategoryEnabled(StandardMetricCategory.PROCESS)) { - metricsSystem.collectors.put( - StandardMetricCategory.PROCESS, - singleton(new StandardExports().register(metricsSystem.registry))); - } - if (metricsSystem.isCategoryEnabled(StandardMetricCategory.JVM)) { - metricsSystem.collectors.put( - StandardMetricCategory.JVM, - asList( - new MemoryPoolsExports().register(metricsSystem.registry), - new BufferPoolsExports().register(metricsSystem.registry), - new GarbageCollectorExports().register(metricsSystem.registry), - new ThreadExports().register(metricsSystem.registry), - new ClassLoadingExports().register(metricsSystem.registry))); - } - return metricsSystem; + public void init() { + addCollector(StandardMetricCategory.PROCESS, StandardExports::new); + addCollector(StandardMetricCategory.JVM, MemoryPoolsExports::new); + addCollector(StandardMetricCategory.JVM, BufferPoolsExports::new); + addCollector(StandardMetricCategory.JVM, GarbageCollectorExports::new); + addCollector(StandardMetricCategory.JVM, ThreadExports::new); + addCollector(StandardMetricCategory.JVM, ClassLoadingExports::new); + } + + @Override + public Set getEnabledCategories() { + return enabledCategories; } @Override @@ -154,14 +141,10 @@ public class PrometheusMetricsSystem implements ObservableMetricsSystem { } } - private boolean isCategoryEnabled(final MetricCategory category) { - return enabledCategories.stream() - .anyMatch(metricCategory -> metricCategory.getName().equals(category.getName())); - } - - public void addCollector(final MetricCategory category, final Collector metric) { + public void addCollector( + final MetricCategory category, final Supplier metricSupplier) { if (isCategoryEnabled(category)) { - addCollectorUnchecked(category, metric); + addCollectorUnchecked(category, metricSupplier.get()); } } diff --git a/metrics/core/src/test-support/java/org/hyperledger/besu/metrics/StubMetricsSystem.java b/metrics/core/src/test-support/java/org/hyperledger/besu/metrics/StubMetricsSystem.java index 89091a8696..492ebd3dd8 100644 --- a/metrics/core/src/test-support/java/org/hyperledger/besu/metrics/StubMetricsSystem.java +++ b/metrics/core/src/test-support/java/org/hyperledger/besu/metrics/StubMetricsSystem.java @@ -22,9 +22,11 @@ import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; import org.hyperledger.besu.plugin.services.metrics.MetricCategory; import org.hyperledger.besu.plugin.services.metrics.OperationTimer; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.DoubleSupplier; import java.util.stream.Stream; @@ -90,6 +92,11 @@ public class StubMetricsSystem implements ObservableMetricsSystem { throw new UnsupportedOperationException("Observations aren't actually recorded"); } + @Override + public Set getEnabledCategories() { + return Collections.emptySet(); + } + public static class StubLabelledCounter implements LabelledMetric { private final Map, StubCounter> metrics = new HashMap<>(); diff --git a/metrics/core/src/test/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetryMetricsSystemTest.java b/metrics/core/src/test/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetryMetricsSystemTest.java new file mode 100644 index 0000000000..b3a9172389 --- /dev/null +++ b/metrics/core/src/test/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetryMetricsSystemTest.java @@ -0,0 +1,253 @@ +/* + * 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.metrics.opentelemetry; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.metrics.BesuMetricCategory.DEFAULT_METRIC_CATEGORIES; +import static org.hyperledger.besu.metrics.BesuMetricCategory.NETWORK; +import static org.hyperledger.besu.metrics.BesuMetricCategory.PEERS; +import static org.hyperledger.besu.metrics.BesuMetricCategory.RPC; +import static org.hyperledger.besu.metrics.MetricsProtocol.OPENTELEMETRY; + +import org.hyperledger.besu.metrics.BesuMetricCategory; +import org.hyperledger.besu.metrics.MetricsSystemFactory; +import org.hyperledger.besu.metrics.ObservableMetricsSystem; +import org.hyperledger.besu.metrics.Observation; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; +import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.metrics.Counter; +import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; +import org.hyperledger.besu.plugin.services.metrics.OperationTimer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import com.google.common.collect.ImmutableSet; +import io.opentelemetry.sdk.metrics.data.MetricData; +import org.junit.Test; + +public class OpenTelemetryMetricsSystemTest { + + private static final Comparator IGNORE_VALUES = + Comparator.comparing(observation -> observation.getCategory().getName()) + .thenComparing(Observation::getMetricName) + .thenComparing((o1, o2) -> o1.getLabels().equals(o2.getLabels()) ? 0 : 1); + + private final ObservableMetricsSystem metricsSystem = + new OpenTelemetrySystem(DEFAULT_METRIC_CATEGORIES, true, "job"); + + @Test + public void shouldCreateObservationFromCounter() { + final Counter counter = metricsSystem.createCounter(PEERS, "connected", "Some help string"); + + counter.inc(); + assertThat(metricsSystem.streamObservations()) + .containsExactly(new Observation(PEERS, "connected", 1L, emptyList())); + + counter.inc(); + assertThat(metricsSystem.streamObservations()) + .containsExactly(new Observation(PEERS, "connected", 2L, emptyList())); + } + + @Test + public void shouldHandleDuplicateCounterCreation() { + final LabelledMetric counter1 = + metricsSystem.createLabelledCounter(PEERS, "connected", "Some help string"); + final LabelledMetric counter2 = + metricsSystem.createLabelledCounter(PEERS, "connected", "Some help string"); + assertThat(counter1).isEqualTo(counter2); + + counter1.labels().inc(); + assertThat(metricsSystem.streamObservations()) + .containsExactly(new Observation(PEERS, "connected", 1L, emptyList())); + + counter2.labels().inc(); + assertThat(metricsSystem.streamObservations()) + .containsExactly(new Observation(PEERS, "connected", 2L, emptyList())); + } + + @Test + public void shouldCreateSeparateObservationsForEachCounterLabelValue() { + final LabelledMetric counter = + metricsSystem.createLabelledCounter(PEERS, "connected", "Some help string", "labelName"); + + counter.labels("value1").inc(); + counter.labels("value2").inc(); + counter.labels("value1").inc(); + + assertThat(metricsSystem.streamObservations()) + .containsExactlyInAnyOrder( + new Observation(PEERS, "connected", 2L, singletonList("value1")), + new Observation(PEERS, "connected", 1L, singletonList("value2"))); + } + + @Test + public void shouldIncrementCounterBySpecifiedAmount() { + final Counter counter = metricsSystem.createCounter(PEERS, "connected", "Some help string"); + + counter.inc(5); + assertThat(metricsSystem.streamObservations()) + .containsExactly(new Observation(PEERS, "connected", 5L, emptyList())); + + counter.inc(6); + assertThat(metricsSystem.streamObservations()) + .containsExactly(new Observation(PEERS, "connected", 11L, emptyList())); + } + + @Test + public void shouldCreateObservationsFromTimer() { + final OperationTimer timer = metricsSystem.createTimer(RPC, "request", "Some help"); + + final OperationTimer.TimingContext context = timer.startTimer(); + context.stopTimer(); + + assertThat(metricsSystem.streamObservations()) + .usingElementComparator(IGNORE_VALUES) + .containsExactlyInAnyOrder(new Observation(RPC, "request", null, Collections.emptyList())); + } + + @Test + public void shouldHandleDuplicateTimerCreation() { + final LabelledMetric timer1 = + metricsSystem.createLabelledTimer(RPC, "request", "Some help"); + final LabelledMetric timer2 = + metricsSystem.createLabelledTimer(RPC, "request", "Some help"); + assertThat(timer1).isEqualTo(timer2); + } + + @Test + public void shouldCreateObservationsFromTimerWithLabels() { + final LabelledMetric timer = + metricsSystem.createLabelledTimer(RPC, "request", "Some help", "methodName"); + + //noinspection EmptyTryBlock + try (final OperationTimer.TimingContext ignored = timer.labels("method").startTimer()) {} + //noinspection EmptyTryBlock + try (final OperationTimer.TimingContext ignored = timer.labels("method").startTimer()) {} + //noinspection EmptyTryBlock + try (final OperationTimer.TimingContext ignored = timer.labels("method").startTimer()) {} + //noinspection EmptyTryBlock + try (final OperationTimer.TimingContext ignored = timer.labels("method").startTimer()) {} + + assertThat(metricsSystem.streamObservations()) + .usingElementComparator(IGNORE_VALUES) // We don't know how long it will actually take. + .containsExactlyInAnyOrder(new Observation(RPC, "request", null, singletonList("method"))); + } + + @Test + public void shouldNotCreateObservationsFromTimerWhenTimersDisabled() { + final ObservableMetricsSystem metricsSystem = + new OpenTelemetrySystem(DEFAULT_METRIC_CATEGORIES, false, "job"); + final LabelledMetric timer = + metricsSystem.createLabelledTimer(RPC, "request", "Some help", "methodName"); + + //noinspection EmptyTryBlock + try (final OperationTimer.TimingContext ignored = timer.labels("method").startTimer()) {} + + assertThat(metricsSystem.streamObservations()).isEmpty(); + } + + @Test + public void shouldCreateObservationFromGauge() { + final MetricsConfiguration metricsConfiguration = + MetricsConfiguration.builder() + .metricCategories(ImmutableSet.of(BesuMetricCategory.RPC)) + .enabled(true) + .protocol(OPENTELEMETRY) + .build(); + final ObservableMetricsSystem localMetricSystem = + MetricsSystemFactory.create(metricsConfiguration); + localMetricSystem.createGauge(RPC, "myValue", "Help", () -> 7d); + List values = new ArrayList<>(); + values.add(MetricData.ValueAtPercentile.create(0, 7d)); + values.add(MetricData.ValueAtPercentile.create(100, 7d)); + + assertThat(localMetricSystem.streamObservations()) + .containsExactlyInAnyOrder(new Observation(RPC, "myValue", values, emptyList())); + } + + @Test + public void shouldOnlyObserveEnabledMetrics() { + final MetricsConfiguration metricsConfiguration = + MetricsConfiguration.builder() + .metricCategories(ImmutableSet.of(BesuMetricCategory.RPC)) + .enabled(true) + .protocol(OPENTELEMETRY) + .build(); + final ObservableMetricsSystem localMetricSystem = + MetricsSystemFactory.create(metricsConfiguration); + + // do a category we are not watching + final LabelledMetric counterN = + localMetricSystem.createLabelledCounter(NETWORK, "ABC", "Not that kind of network", "show"); + assertThat(counterN).isSameAs(NoOpMetricsSystem.NO_OP_LABELLED_1_COUNTER); + + counterN.labels("show").inc(); + assertThat(localMetricSystem.streamObservations()).isEmpty(); + + // do a category we are watching + final LabelledMetric counterR = + localMetricSystem.createLabelledCounter(RPC, "name", "Not useful", "method"); + assertThat(counterR).isNotSameAs(NoOpMetricsSystem.NO_OP_LABELLED_1_COUNTER); + + counterR.labels("op").inc(); + assertThat(localMetricSystem.streamObservations()) + .containsExactly(new Observation(RPC, "name", (long) 1, singletonList("op"))); + } + + @Test + public void returnsNoOpMetricsWhenAllDisabled() { + final MetricsConfiguration metricsConfiguration = + MetricsConfiguration.builder() + .enabled(false) + .pushEnabled(false) + .protocol(OPENTELEMETRY) + .build(); + final MetricsSystem localMetricSystem = MetricsSystemFactory.create(metricsConfiguration); + + assertThat(localMetricSystem).isInstanceOf(NoOpMetricsSystem.class); + } + + @Test + public void returnsPrometheusMetricsWhenEnabled() { + final MetricsConfiguration metricsConfiguration = + MetricsConfiguration.builder() + .enabled(true) + .pushEnabled(false) + .protocol(OPENTELEMETRY) + .build(); + final MetricsSystem localMetricSystem = MetricsSystemFactory.create(metricsConfiguration); + + assertThat(localMetricSystem).isInstanceOf(OpenTelemetrySystem.class); + } + + @Test + public void returnsNoOpMetricsWhenPushEnabled() { + final MetricsConfiguration metricsConfiguration = + MetricsConfiguration.builder() + .enabled(false) + .pushEnabled(true) + .protocol(OPENTELEMETRY) + .build(); + final MetricsSystem localMetricSystem = MetricsSystemFactory.create(metricsConfiguration); + + assertThat(localMetricSystem).isInstanceOf(OpenTelemetrySystem.class); + } +} diff --git a/metrics/core/src/test/java/org/hyperledger/besu/metrics/prometheus/MetricsHttpServiceTest.java b/metrics/core/src/test/java/org/hyperledger/besu/metrics/prometheus/MetricsHttpServiceTest.java index e1c27c6783..0d1ab758b1 100644 --- a/metrics/core/src/test/java/org/hyperledger/besu/metrics/prometheus/MetricsHttpServiceTest.java +++ b/metrics/core/src/test/java/org/hyperledger/besu/metrics/prometheus/MetricsHttpServiceTest.java @@ -18,6 +18,8 @@ import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.util.NetworkUtility.urlForSocketAddress; +import org.hyperledger.besu.metrics.MetricsSystemFactory; + import java.net.InetSocketAddress; import java.util.Properties; @@ -53,13 +55,13 @@ public class MetricsHttpServiceTest { } private static MetricsHttpService createMetricsHttpService(final MetricsConfiguration config) { - return new MetricsHttpService(vertx, config, PrometheusMetricsSystem.init(config)); + return new MetricsHttpService(vertx, config, MetricsSystemFactory.create(config)); } private static MetricsHttpService createMetricsHttpService() { final MetricsConfiguration metricsConfiguration = createMetricsConfig(); return new MetricsHttpService( - vertx, metricsConfiguration, PrometheusMetricsSystem.init(metricsConfiguration)); + vertx, metricsConfiguration, MetricsSystemFactory.create(metricsConfiguration)); } private static MetricsConfiguration createMetricsConfig() { diff --git a/metrics/core/src/test/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystemTest.java b/metrics/core/src/test/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystemTest.java index 5e6aa29b43..59dea6114f 100644 --- a/metrics/core/src/test/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystemTest.java +++ b/metrics/core/src/test/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystemTest.java @@ -26,6 +26,7 @@ import static org.hyperledger.besu.metrics.BesuMetricCategory.RPC; import static org.hyperledger.besu.metrics.StandardMetricCategory.JVM; import org.hyperledger.besu.metrics.BesuMetricCategory; +import org.hyperledger.besu.metrics.MetricsSystemFactory; import org.hyperledger.besu.metrics.ObservableMetricsSystem; import org.hyperledger.besu.metrics.Observation; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; @@ -34,6 +35,7 @@ import org.hyperledger.besu.plugin.services.metrics.Counter; import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; import org.hyperledger.besu.plugin.services.metrics.OperationTimer; +import java.util.Collections; import java.util.Comparator; import com.google.common.collect.ImmutableSet; @@ -160,7 +162,7 @@ public class PrometheusMetricsSystemTest { @Test public void shouldNotCreateObservationsFromTimerWhenTimersDisabled() { final ObservableMetricsSystem metricsSystem = - new PrometheusMetricsSystem(DEFAULT_METRIC_CATEGORIES, false); + new PrometheusMetricsSystem(Collections.emptySet(), false); final LabelledMetric timer = metricsSystem.createLabelledTimer(RPC, "request", "Some help", "methodName"); @@ -196,7 +198,7 @@ public class PrometheusMetricsSystemTest { .enabled(true) .build(); final ObservableMetricsSystem localMetricSystem = - PrometheusMetricsSystem.init(metricsConfiguration); + MetricsSystemFactory.create(metricsConfiguration); // do a category we are not watching final LabelledMetric counterN = @@ -220,7 +222,7 @@ public class PrometheusMetricsSystemTest { public void returnsNoOpMetricsWhenAllDisabled() { final MetricsConfiguration metricsConfiguration = MetricsConfiguration.builder().enabled(false).pushEnabled(false).build(); - final MetricsSystem localMetricSystem = PrometheusMetricsSystem.init(metricsConfiguration); + final MetricsSystem localMetricSystem = MetricsSystemFactory.create(metricsConfiguration); assertThat(localMetricSystem).isInstanceOf(NoOpMetricsSystem.class); } @@ -229,7 +231,7 @@ public class PrometheusMetricsSystemTest { public void returnsPrometheusMetricsWhenEnabled() { final MetricsConfiguration metricsConfiguration = MetricsConfiguration.builder().enabled(true).pushEnabled(false).build(); - final MetricsSystem localMetricSystem = PrometheusMetricsSystem.init(metricsConfiguration); + final MetricsSystem localMetricSystem = MetricsSystemFactory.create(metricsConfiguration); assertThat(localMetricSystem).isInstanceOf(PrometheusMetricsSystem.class); } @@ -238,7 +240,7 @@ public class PrometheusMetricsSystemTest { public void returnsNoOpMetricsWhenPushEnabled() { final MetricsConfiguration metricsConfiguration = MetricsConfiguration.builder().enabled(false).pushEnabled(true).build(); - final MetricsSystem localMetricSystem = PrometheusMetricsSystem.init(metricsConfiguration); + final MetricsSystem localMetricSystem = MetricsSystemFactory.create(metricsConfiguration); assertThat(localMetricSystem).isInstanceOf(PrometheusMetricsSystem.class); } diff --git a/metrics/rocksdb/src/main/java/org/hyperledger/besu/metrics/rocksdb/RocksDBStats.java b/metrics/rocksdb/src/main/java/org/hyperledger/besu/metrics/rocksdb/RocksDBStats.java index 5785c4b57c..f92accba86 100644 --- a/metrics/rocksdb/src/main/java/org/hyperledger/besu/metrics/rocksdb/RocksDBStats.java +++ b/metrics/rocksdb/src/main/java/org/hyperledger/besu/metrics/rocksdb/RocksDBStats.java @@ -173,6 +173,9 @@ public class RocksDBStats { final Statistics stats, final PrometheusMetricsSystem metricsSystem, final MetricCategory category) { + if (!metricsSystem.isCategoryEnabled(category)) { + return; + } for (final TickerType ticker : TICKERS) { final String promCounterName = ticker.name().toLowerCase(); metricsSystem.createLongGauge( @@ -183,7 +186,7 @@ public class RocksDBStats { } for (final HistogramType histogram : HISTOGRAMS) { - metricsSystem.addCollector(category, histogramToCollector(stats, histogram)); + metricsSystem.addCollector(category, () -> histogramToCollector(stats, histogram)); } }