From c4a31c87281cad361f699a175127538081ab9650 Mon Sep 17 00:00:00 2001 From: Fabio Di Fabio Date: Thu, 18 Jan 2024 18:15:10 +0100 Subject: [PATCH] Upgrade to Promethus java client 1.x and adapt the code to the new version Signed-off-by: Fabio Di Fabio --- .../org/hyperledger/besu/RunnerBuilder.java | 8 +- .../subcommands/blocks/BlocksSubCommand.java | 4 +- gradle/verification-metadata.xml | 277 +++++++++++++++ metrics/core/build.gradle | 11 +- .../besu/metrics/MetricsService.java | 10 +- .../opentelemetry/DebugMetricReader.java | 2 +- .../prometheus/AbstractPrometheusSummary.java | 82 +++++ .../CategorizedPrometheusCollector.java | 50 +++ .../prometheus/CurrentValueCollector.java | 59 ---- .../prometheus/MetricsHttpService.java | 209 ++++------- .../prometheus/MetricsPushGatewayService.java | 14 +- .../prometheus/PrometheusCollector.java | 66 ++++ .../metrics/prometheus/PrometheusCounter.java | 58 +++- .../prometheus/PrometheusExternalSummary.java | 73 ++++ .../metrics/prometheus/PrometheusGauge.java | 108 ++++-- .../prometheus/PrometheusGuavaCache.java | 73 ++++ .../prometheus/PrometheusMetricsSystem.java | 327 +++++------------- .../prometheus/PrometheusSimpleTimer.java | 69 +++- .../metrics/prometheus/PrometheusTimer.java | 47 ++- .../prometheus/MetricsHttpServiceTest.java | 76 ++-- .../PrometheusMetricsSystemTest.java | 106 ++---- platform/build.gradle | 4 +- plugin-api/build.gradle | 2 +- 23 files changed, 1096 insertions(+), 639 deletions(-) create mode 100644 metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/AbstractPrometheusSummary.java create mode 100644 metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/CategorizedPrometheusCollector.java delete mode 100644 metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/CurrentValueCollector.java create mode 100644 metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusCollector.java create mode 100644 metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusExternalSummary.java create mode 100644 metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusGuavaCache.java diff --git a/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java b/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java index 06dca6c6f0a2..0f398fae1eb4 100644 --- a/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java @@ -1034,8 +1034,7 @@ public Runner build() { subscriptionManager, privacyParameters, context.getBlockchain().getGenesisBlockHeader()); } - final Optional metricsService = - createMetricsService(vertx, metricsConfiguration); + final Optional metricsService = createMetricsService(metricsConfiguration); final Optional ethStatsService; if (isEthStatsEnabled()) { @@ -1469,9 +1468,8 @@ private WebSocketService createWebsocketService( vertx, configuration, websocketMessageHandler, authenticationService, metricsSystem); } - private Optional createMetricsService( - final Vertx vertx, final MetricsConfiguration configuration) { - return MetricsService.create(vertx, configuration, metricsSystem); + private Optional createMetricsService(final MetricsConfiguration configuration) { + return MetricsService.create(configuration, metricsSystem); } /** 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 95617fe8ca14..5862b11c19b4 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 @@ -53,7 +53,6 @@ import java.util.function.Function; import java.util.function.Supplier; -import io.vertx.core.Vertx; import jakarta.validation.constraints.NotBlank; import org.apache.tuweni.bytes.Bytes; import org.slf4j.Logger; @@ -458,8 +457,7 @@ private static Optional initMetrics(final BlocksSubCommand paren parentCommand.parentCommand.metricsConfiguration(); Optional metricsService = - MetricsService.create( - Vertx.vertx(), metricsConfiguration, parentCommand.parentCommand.getMetricsSystem()); + MetricsService.create(metricsConfiguration, parentCommand.parentCommand.getMetricsSystem()); metricsService.ifPresent(MetricsService::start); return metricsService; } diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index d4407855f092..8501147677c7 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -2334,10 +2334,31 @@ + + + + + + + + + + + + + + + + + + + + + @@ -2506,6 +2527,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2626,11 +2673,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/metrics/core/build.gradle b/metrics/core/build.gradle index 160093c74903..e10057d466f3 100644 --- a/metrics/core/build.gradle +++ b/metrics/core/build.gradle @@ -55,11 +55,12 @@ dependencies { implementation 'io.opentelemetry:opentelemetry-sdk-extension-autoconfigure' implementation 'io.opentelemetry.semconv:opentelemetry-semconv' - implementation 'io.prometheus:simpleclient' - implementation 'io.prometheus:simpleclient_common' - implementation 'io.prometheus:simpleclient_guava' - implementation 'io.prometheus:simpleclient_hotspot' - implementation 'io.prometheus:simpleclient_pushgateway' + implementation 'io.prometheus:prometheus-metrics-core' + implementation 'io.prometheus:prometheus-metrics-instrumentation-guava' + implementation 'io.prometheus:prometheus-metrics-instrumentation-jvm' + implementation 'io.prometheus:prometheus-metrics-exporter-httpserver' + implementation 'io.prometheus:prometheus-metrics-exporter-pushgateway' + implementation 'io.vertx:vertx-core' implementation 'io.vertx:vertx-web' diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsService.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsService.java index 747ed9cc762a..7eb370615031 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsService.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsService.java @@ -18,12 +18,12 @@ 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.metrics.prometheus.PrometheusMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; import java.util.Optional; import java.util.concurrent.CompletableFuture; -import io.vertx.core.Vertx; import org.slf4j.LoggerFactory; /** @@ -35,20 +35,18 @@ public interface MetricsService { /** * Create Metrics Service. * - * @param vertx the vertx * @param configuration the configuration * @param metricsSystem the metrics system * @return the optional Metrics Service */ static Optional create( - final Vertx vertx, - final MetricsConfiguration configuration, - final MetricsSystem metricsSystem) { + final MetricsConfiguration configuration, final MetricsSystem metricsSystem) { LoggerFactory.getLogger(MetricsService.class) .trace("Creating metrics service {}", configuration.getProtocol()); if (configuration.getProtocol() == MetricsProtocol.PROMETHEUS) { if (configuration.isEnabled()) { - return Optional.of(new MetricsHttpService(vertx, configuration, metricsSystem)); + return Optional.of( + new MetricsHttpService(configuration, (PrometheusMetricsSystem) metricsSystem)); } else if (configuration.isPushEnabled()) { return Optional.of(new MetricsPushGatewayService(configuration, metricsSystem)); } else { diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/DebugMetricReader.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/DebugMetricReader.java index efa4215eef15..bffb00c9fdfb 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/DebugMetricReader.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/DebugMetricReader.java @@ -1,5 +1,5 @@ /* - * Copyright Besu Contributors + * Copyright contributors to Hyperledger 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 diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/AbstractPrometheusSummary.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/AbstractPrometheusSummary.java new file mode 100644 index 000000000000..08abbed718e9 --- /dev/null +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/AbstractPrometheusSummary.java @@ -0,0 +1,82 @@ +/* + * 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.metrics.prometheus; + +import org.hyperledger.besu.metrics.Observation; +import org.hyperledger.besu.plugin.services.metrics.MetricCategory; + +import java.util.ArrayList; +import java.util.stream.Stream; + +import io.prometheus.metrics.model.snapshots.SummarySnapshot; + +abstract class AbstractPrometheusSummary extends CategorizedPrometheusCollector { + + /** + * Create a new collector assigned to the given category and with the given name, and computed the + * prefixed name. + * + * @param category The {@link MetricCategory} this collector is assigned to + * @param name The name of this collector + */ + protected AbstractPrometheusSummary(final MetricCategory category, final String name) { + super(category, name); + } + + protected abstract SummarySnapshot collect(); + + @Override + public Stream streamObservations() { + return collect().getDataPoints().stream() + .flatMap( + dataPoint -> { + final var labelValues = PrometheusCollector.getLabelValues(dataPoint.getLabels()); + final var quantiles = dataPoint.getQuantiles(); + final var observations = new ArrayList(quantiles.size() + 2); + + if (dataPoint.hasSum()) { + observations.add( + new Observation( + category, + name, + dataPoint.getSum(), + PrometheusCollector.addLabelValues(labelValues, "sum"))); + } + + if (dataPoint.hasCount()) { + observations.add( + new Observation( + category, + name, + dataPoint.getCount(), + PrometheusCollector.addLabelValues(labelValues, "count"))); + } + + quantiles.forEach( + quantile -> + observations.add( + new Observation( + category, + name, + quantile.getValue(), + PrometheusCollector.addLabelValues( + labelValues, + "quantile", + Double.toString(quantile.getQuantile()))))); + + return observations.stream(); + }); + } +} diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/CategorizedPrometheusCollector.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/CategorizedPrometheusCollector.java new file mode 100644 index 000000000000..753968966be0 --- /dev/null +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/CategorizedPrometheusCollector.java @@ -0,0 +1,50 @@ +/* + * 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.metrics.prometheus; + +import org.hyperledger.besu.plugin.services.metrics.MetricCategory; + +/** A Prometheus collector that is assigned to a category */ +public abstract class CategorizedPrometheusCollector implements PrometheusCollector { + /** The {@link MetricCategory} this collector is assigned to */ + protected final MetricCategory category; + + /** The name of this collector */ + protected final String name; + + /** The prefixed name of this collector */ + protected final String prefixedName; + + /** + * Create a new collector assigned to the given category and with the given name, and computed the + * prefixed name. + * + * @param category The {@link MetricCategory} this collector is assigned to + * @param name The name of this collector + */ + protected CategorizedPrometheusCollector(final MetricCategory category, final String name) { + this.category = category; + this.name = name; + this.prefixedName = prefixedName(category, name); + } + + private static String categoryPrefix(final MetricCategory category) { + return category.getApplicationPrefix().orElse("") + category.getName() + "_"; + } + + private static String prefixedName(final MetricCategory category, final String name) { + return categoryPrefix(category) + name; + } +} diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/CurrentValueCollector.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/CurrentValueCollector.java deleted file mode 100644 index fdcc32bafdf1..000000000000 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/CurrentValueCollector.java +++ /dev/null @@ -1,59 +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.metrics.prometheus; - -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; - -import java.util.List; -import java.util.function.DoubleSupplier; - -import io.prometheus.client.Collector; -import io.prometheus.client.Collector.MetricFamilySamples.Sample; - -class CurrentValueCollector extends Collector { - - private final String metricName; - private final String help; - private final DoubleSupplier valueSupplier; - private final List labelNames; - private final List labelValues; - - public CurrentValueCollector( - final String metricName, final String help, final DoubleSupplier valueSupplier) { - this(metricName, help, emptyList(), emptyList(), valueSupplier); - } - - public CurrentValueCollector( - final String metricName, - final String help, - final List labelNames, - final List labelValues, - final DoubleSupplier valueSupplier) { - this.metricName = metricName; - this.help = help; - this.valueSupplier = valueSupplier; - this.labelNames = labelNames; - this.labelValues = labelValues; - } - - @Override - public List collect() { - final Sample sample = - new Sample(metricName, labelNames, labelValues, valueSupplier.getAsDouble()); - return singletonList( - new MetricFamilySamples(metricName, Type.GAUGE, help, singletonList(sample))); - } -} 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 94a198dc0e4f..c3de0da2d388 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,59 +17,43 @@ import static com.google.common.base.Preconditions.checkArgument; import org.hyperledger.besu.metrics.MetricsService; -import org.hyperledger.besu.plugin.services.MetricsSystem; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.OutputStreamWriter; import java.net.InetSocketAddress; -import java.net.SocketException; -import java.nio.charset.StandardCharsets; import java.util.Locale; import java.util.Optional; -import java.util.Set; -import java.util.TreeSet; import java.util.concurrent.CompletableFuture; -import io.netty.handler.codec.http.HttpResponseStatus; -import io.prometheus.client.exporter.common.TextFormat; -import io.vertx.core.Handler; -import io.vertx.core.Vertx; -import io.vertx.core.http.HttpMethod; -import io.vertx.core.http.HttpServer; -import io.vertx.core.http.HttpServerOptions; -import io.vertx.core.http.HttpServerResponse; +import com.sun.net.httpserver.Authenticator; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpPrincipal; +import io.prometheus.metrics.exporter.httpserver.DefaultHandler; +import io.prometheus.metrics.exporter.httpserver.HTTPServer; import io.vertx.core.net.HostAndPort; -import io.vertx.ext.web.Router; -import io.vertx.ext.web.RoutingContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** The Metrics http service. */ public class MetricsHttpService implements MetricsService { private static final Logger LOG = LoggerFactory.getLogger(MetricsHttpService.class); - + private static final Authenticator.Result AUTHORIZED = + new Authenticator.Success(new HttpPrincipal("metrics", "metrics")); + private static final Authenticator.Result NOT_AUTHORIZED = new Authenticator.Failure(403); private static final InetSocketAddress EMPTY_SOCKET_ADDRESS = new InetSocketAddress("0.0.0.0", 0); - private final Vertx vertx; private final MetricsConfiguration config; - private final MetricsSystem metricsSystem; - - private HttpServer httpServer; + private final PrometheusMetricsSystem metricsSystem; + private HTTPServer httpServer; /** * Instantiates a new Metrics http service. * - * @param vertx the vertx * @param configuration the configuration * @param metricsSystem the metrics system */ public MetricsHttpService( - final Vertx vertx, - final MetricsConfiguration configuration, - final MetricsSystem metricsSystem) { + final MetricsConfiguration configuration, final PrometheusMetricsSystem metricsSystem) { validateConfig(configuration); - this.vertx = vertx; this.config = configuration; this.metricsSystem = metricsSystem; } @@ -85,78 +69,39 @@ private void validateConfig(final MetricsConfiguration config) { @Override public CompletableFuture start() { LOG.info("Starting metrics http service on {}:{}", config.getHost(), config.getPort()); - // Create the HTTP server and a router object. - httpServer = - vertx.createHttpServer( - new HttpServerOptions() - .setHost(config.getHost()) - .setPort(config.getPort()) - .setIdleTimeout(config.getIdleTimeout()) - .setHandle100ContinueAutomatically(true) - .setCompressionSupported(true)); - - final Router router = Router.router(vertx); - - // Verify Host header. - router.route().handler(checkAllowlistHostHeader()); - - // Endpoint for AWS health check. - router.route("/").method(HttpMethod.GET).handler(this::handleEmptyRequest); - // Endpoint for Prometheus metrics monitoring. - router.route("/metrics").method(HttpMethod.GET).handler(this::metricsRequest); + try { + httpServer = + HTTPServer.builder() + .hostname(config.getHost()) + .port(config.getPort()) + .registry(metricsSystem.getRegistry()) + .authenticator( + new Authenticator() { + @Override + public Result authenticate(final HttpExchange exch) { + return checkAllowlistHostHeader(exch); + } + }) + .defaultHandler(new RestrictedDefaultHandler()) + .buildAndStart(); - final CompletableFuture resultFuture = new CompletableFuture<>(); - httpServer - .requestHandler(router) - .listen( - res -> { - if (!res.failed()) { - resultFuture.complete(null); - final int actualPort = httpServer.actualPort(); - config.setActualPort(actualPort); - LOG.info( - "Metrics service started and listening on {}:{}", config.getHost(), actualPort); - return; - } - httpServer = null; - final Throwable cause = res.cause(); - if (cause instanceof SocketException) { - resultFuture.completeExceptionally( - new RuntimeException( - String.format( - "Failed to bind metrics listener to %s:%s (actual port %s): %s", - config.getHost(), - config.getPort(), - config.getActualPort(), - cause.getMessage()))); - return; - } - resultFuture.completeExceptionally(cause); - }); - return resultFuture; + return CompletableFuture.completedFuture(null); + } catch (final Throwable e) { + return CompletableFuture.failedFuture(e); + } } - private Handler checkAllowlistHostHeader() { - return event -> { - final Optional hostHeader = getAndValidateHostHeader(event); - if (config.getHostsAllowlist().contains("*") - || (hostHeader.isPresent() && hostIsInAllowlist(hostHeader.get()))) { - event.next(); - } else { - final HttpServerResponse response = event.response(); - if (!response.closed()) { - response - .setStatusCode(403) - .putHeader("Content-Type", "application/json; charset=utf-8") - .end("{\"message\":\"Host not authorized.\"}"); - } - } - }; - } + private Authenticator.Result checkAllowlistHostHeader(final HttpExchange exch) { + if (config.getHostsAllowlist().contains("*")) { + return AUTHORIZED; + } - private Optional getAndValidateHostHeader(final RoutingContext event) { - return Optional.ofNullable(event.request().authority()).map(HostAndPort::host); + return Optional.ofNullable(exch.getRequestHeaders().getFirst("Host")) + .map(host -> HostAndPort.parseAuthority(host, -1).host()) + .filter(this::hostIsInAllowlist) + .map(unused -> AUTHORIZED) + .orElse(NOT_AUTHORIZED); } private boolean hostIsInAllowlist(final String hostHeader) { @@ -179,56 +124,12 @@ public CompletableFuture stop() { return CompletableFuture.completedFuture(null); } - final CompletableFuture resultFuture = new CompletableFuture<>(); - httpServer.close( - res -> { - if (res.failed()) { - resultFuture.completeExceptionally(res.cause()); - } else { - httpServer = null; - resultFuture.complete(null); - } - }); - return resultFuture; - } - - private void metricsRequest(final RoutingContext routingContext) { - final Set names = new TreeSet<>(routingContext.queryParam("name[]")); - final HttpServerResponse response = routingContext.response(); - vertx.executeBlocking( - future -> { - try { - final ByteArrayOutputStream metrics = new ByteArrayOutputStream(16 * 1024); - final OutputStreamWriter osw = new OutputStreamWriter(metrics, StandardCharsets.UTF_8); - TextFormat.write004( - osw, - ((PrometheusMetricsSystem) (metricsSystem)) - .getRegistry() - .filteredMetricFamilySamples(names)); - osw.flush(); - osw.close(); - metrics.flush(); - metrics.close(); - future.complete(metrics.toString(StandardCharsets.UTF_8.name())); - } catch (final IOException ioe) { - future.fail(ioe); - } - }, - false, - (res) -> { - if (response.closed()) { - // Request for metrics closed before response was generated - return; - } - if (res.failed()) { - LOG.error("Request for metrics failed", res.cause()); - response.setStatusCode(HttpResponseStatus.INTERNAL_SERVER_ERROR.code()).end(); - } else { - response.setStatusCode(HttpResponseStatus.OK.code()); - response.putHeader("Content-Type", TextFormat.CONTENT_TYPE_004); - response.end(res.result()); - } - }); + try { + httpServer.stop(); + return CompletableFuture.completedFuture(null); + } catch (final Throwable e) { + return CompletableFuture.failedFuture(e); + } } /** @@ -240,7 +141,7 @@ InetSocketAddress socketAddress() { if (httpServer == null) { return EMPTY_SOCKET_ADDRESS; } - return new InetSocketAddress(config.getHost(), httpServer.actualPort()); + return new InetSocketAddress(config.getHost(), httpServer.getPort()); } @Override @@ -248,11 +149,21 @@ public Optional getPort() { if (httpServer == null) { return Optional.empty(); } - return Optional.of(httpServer.actualPort()); + return Optional.of(httpServer.getPort()); } - // Facilitate remote health-checks in AWS, inter alia. - private void handleEmptyRequest(final RoutingContext routingContext) { - routingContext.response().setStatusCode(201).end(); + private static class RestrictedDefaultHandler extends DefaultHandler { + @Override + public void handle(final HttpExchange exchange) throws IOException { + if (!exchange.getRequestURI().getPath().equals("/")) { + try { + exchange.sendResponseHeaders(404, -1); + } finally { + exchange.close(); + } + } else { + super.handle(exchange); + } + } } } 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 fa9cf53e8098..ad60a2fd203a 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 @@ -26,7 +26,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import io.prometheus.client.exporter.PushGateway; +import io.prometheus.metrics.exporter.pushgateway.PushGateway; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -71,7 +71,12 @@ public CompletableFuture start() { config.getPushHost(), config.getPushPort()); - pushGateway = new PushGateway(config.getPushHost() + ":" + config.getPushPort()); + pushGateway = + PushGateway.builder() + .registry(((PrometheusMetricsSystem) metricsSystem).getRegistry()) + .address(config.getPushHost() + ":" + config.getPushPort()) + .job(config.getPrometheusJob()) + .build(); // Create the executor scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); @@ -91,7 +96,7 @@ public CompletableFuture stop() { scheduledExecutorService.shutdownNow(); scheduledExecutorService.awaitTermination(30, TimeUnit.SECONDS); try { - pushGateway.delete(config.getPrometheusJob()); + pushGateway.delete(); } catch (final Exception e) { LOG.error("Could not clean up results on the Prometheus Push Gateway.", e); // Do not complete exceptionally, the gateway may be down and failures @@ -112,8 +117,7 @@ public Optional getPort() { private void pushMetrics() { try { - pushGateway.pushAdd( - ((PrometheusMetricsSystem) metricsSystem).getRegistry(), config.getPrometheusJob()); + pushGateway.pushAdd(); } catch (final IOException e) { LOG.warn("Could not push metrics", e); } diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusCollector.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusCollector.java new file mode 100644 index 000000000000..ed649679d12a --- /dev/null +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusCollector.java @@ -0,0 +1,66 @@ +/* + * 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.metrics.prometheus; + +import org.hyperledger.besu.metrics.Observation; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.Label; +import io.prometheus.metrics.model.snapshots.Labels; + +/** Wraps a native Prometheus collector inside the metric system */ +public interface PrometheusCollector { + + /** + * Get the name of the collector + * + * @return the name of the collector + */ + String getName(); + + /** Get the native Prometheus collector */ + void register(final PrometheusRegistry registry); + + void unregister(final PrometheusRegistry registry); + + /** + * Stream the data points of this collector + * + * @return a stream of the data points of this collector + */ + Stream streamObservations(); + + /** + * Utility to get the label values as strings from native Prometheus labels + * + * @param labels the Prometheus labels + * @return the label values as strings + */ + static List getLabelValues(final Labels labels) { + return labels.stream().map(Label::getValue).toList(); + } + + static List addLabelValues(final List labelValues, final String... values) { + final var newList = new ArrayList(labelValues.size() + values.length); + newList.addAll(labelValues); + Collections.addAll(newList, values); + return newList; + } +} diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusCounter.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusCounter.java index 85b3750b7787..bd25063ba0b4 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusCounter.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusCounter.java @@ -14,28 +14,66 @@ */ package org.hyperledger.besu.metrics.prometheus; +import org.hyperledger.besu.metrics.Observation; import org.hyperledger.besu.plugin.services.metrics.Counter; import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; +import org.hyperledger.besu.plugin.services.metrics.MetricCategory; -class PrometheusCounter implements LabelledMetric { +import java.util.stream.Stream; - private final io.prometheus.client.Counter counter; +import io.prometheus.metrics.core.datapoints.CounterDataPoint; +import io.prometheus.metrics.model.registry.PrometheusRegistry; - public PrometheusCounter(final io.prometheus.client.Counter counter) { - this.counter = counter; +class PrometheusCounter extends CategorizedPrometheusCollector implements LabelledMetric { + private final io.prometheus.metrics.core.metrics.Counter counter; + + public PrometheusCounter( + final MetricCategory category, + final String name, + final String help, + final String... labelNames) { + super(category, name); + this.counter = + io.prometheus.metrics.core.metrics.Counter.builder() + .name(this.prefixedName) + .help(help) + .labelNames(labelNames) + .build(); } @Override public Counter labels(final String... labels) { - return new UnlabelledCounter(counter.labels(labels)); + return new UnlabelledCounter(counter.labelValues(labels)); } - private static class UnlabelledCounter implements Counter { - private final io.prometheus.client.Counter.Child counter; + @Override + public String getName() { + return counter.getPrometheusName(); + } - private UnlabelledCounter(final io.prometheus.client.Counter.Child counter) { - this.counter = counter; - } + @Override + public void register(final PrometheusRegistry registry) { + registry.register(counter); + } + + @Override + public void unregister(final PrometheusRegistry registry) { + registry.unregister(counter); + } + + @Override + public Stream streamObservations() { + return counter.collect().getDataPoints().stream() + .map( + sample -> + new Observation( + category, + name, + sample.getValue(), + PrometheusCollector.getLabelValues(sample.getLabels()))); + } + + private record UnlabelledCounter(CounterDataPoint counter) implements Counter { @Override public void inc() { diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusExternalSummary.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusExternalSummary.java new file mode 100644 index 000000000000..b83cee9f33be --- /dev/null +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusExternalSummary.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.metrics.prometheus; + +import org.hyperledger.besu.plugin.services.metrics.ExternalSummary; +import org.hyperledger.besu.plugin.services.metrics.MetricCategory; + +import java.util.function.Supplier; + +import io.prometheus.metrics.core.metrics.SummaryWithCallback; +import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.Quantile; +import io.prometheus.metrics.model.snapshots.Quantiles; +import io.prometheus.metrics.model.snapshots.SummarySnapshot; + +class PrometheusExternalSummary extends AbstractPrometheusSummary { + + private final SummaryWithCallback summary; + + public PrometheusExternalSummary( + final MetricCategory category, + final String name, + final String help, + final Supplier summarySupplier) { + super(category, name); + summary = + SummaryWithCallback.builder() + .name(name) + .help(help) + .callback( + cb -> { + final var externalSummary = summarySupplier.get(); + final var quantilesBuilder = Quantiles.builder(); + externalSummary.quantiles().stream() + .map(pq -> new Quantile(pq.quantile(), pq.value())) + .forEach(quantilesBuilder::quantile); + cb.call(externalSummary.count(), externalSummary.sum(), quantilesBuilder.build()); + }) + .build(); + } + + @Override + public String getName() { + return summary.getPrometheusName(); + } + + @Override + public void register(final PrometheusRegistry registry) { + registry.register(summary); + } + + @Override + public void unregister(final PrometheusRegistry registry) { + registry.unregister(summary); + } + + @Override + protected SummarySnapshot collect() { + return summary.collect(); + } +} diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusGauge.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusGauge.java index b69e3f90626d..242c8612e87f 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusGauge.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusGauge.java @@ -14,68 +14,110 @@ */ package org.hyperledger.besu.metrics.prometheus; +import org.hyperledger.besu.metrics.Observation; import org.hyperledger.besu.plugin.services.metrics.LabelledGauge; +import org.hyperledger.besu.plugin.services.metrics.MetricCategory; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.DoubleSupplier; +import java.util.stream.Stream; -import io.prometheus.client.Collector; +import io.prometheus.metrics.core.metrics.GaugeWithCallback; +import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.GaugeSnapshot; /** The Prometheus gauge. */ -public class PrometheusGauge extends Collector implements LabelledGauge { - private final String metricName; - private final String help; - private final List labelNames; - private final Map, DoubleSupplier> observationsMap = new ConcurrentHashMap<>(); +public class PrometheusGauge extends CategorizedPrometheusCollector implements LabelledGauge { + private final GaugeWithCallback gauge; + private final Map, CallbackData> labelledCallbackData = new ConcurrentHashMap<>(); /** - * Instantiates a new Prometheus gauge. + * Instantiates a new labelled Prometheus gauge. * - * @param metricName the metric name + * @param category the {@link MetricCategory} this gauge is assigned to + * @param name the metric name * @param help the help * @param labelNames the label names */ public PrometheusGauge( - final String metricName, final String help, final List labelNames) { - this.metricName = metricName; - this.help = help; - this.labelNames = labelNames; + final MetricCategory category, + final String name, + final String help, + final String... labelNames) { + super(category, name); + this.gauge = + GaugeWithCallback.builder() + .name(this.prefixedName) + .help(help) + .labelNames(labelNames) + .callback(this::callback) + .build(); + } + + /** + * Instantiates a new unlabelled Prometheus gauge. + * + * @param category the {@link MetricCategory} this gauge is assigned to + * @param name the metric name + * @param help the help + * @param valueSupplier the supplier of the value + */ + public PrometheusGauge( + final MetricCategory category, + final String name, + final String help, + final DoubleSupplier valueSupplier) { + this(category, name, help); + labelledCallbackData.put(List.of(), new CallbackData(valueSupplier, new String[0])); + } + + private void callback(final GaugeWithCallback.Callback callback) { + labelledCallbackData + .values() + .forEach( + callbackData -> + callback.call(callbackData.valueSupplier.getAsDouble(), callbackData.labelValues)); } @Override public synchronized void labels(final DoubleSupplier valueSupplier, final String... labelValues) { - validateLabelsCardinality(labelValues); - if (observationsMap.putIfAbsent(List.of(labelValues), valueSupplier) != null) { - final String labelValuesString = String.join(",", labelValues); + final var valueList = List.of(labelValues); + if (labelledCallbackData.containsKey(valueList)) { throw new IllegalArgumentException( - String.format("A gauge has already been created for label values %s", labelValuesString)); + String.format("A gauge has already been created for label values %s", valueList)); } + + labelledCallbackData.put(valueList, new CallbackData(valueSupplier, labelValues)); } @Override - public boolean isLabelsObserved(final String... labelValues) { - validateLabelsCardinality(labelValues); - return observationsMap.containsKey(List.of(labelValues)); + public String getName() { + return gauge.getPrometheusName(); } @Override - public List collect() { - final List samples = new ArrayList<>(); - observationsMap.forEach( - (labels, valueSupplier) -> - samples.add( - new MetricFamilySamples.Sample( - metricName, labelNames, labels, valueSupplier.getAsDouble()))); - return List.of(new MetricFamilySamples(metricName, Type.GAUGE, help, samples)); + public void register(final PrometheusRegistry registry) { + registry.register(gauge); } - private void validateLabelsCardinality(final String... labelValues) { - if (labelValues.length != labelNames.size()) { - throw new IllegalArgumentException( - "Label values and label names must be the same cardinality"); - } + @Override + public void unregister(final PrometheusRegistry registry) { + registry.unregister(gauge); + } + + private Observation convertToObservation(final GaugeSnapshot.GaugeDataPointSnapshot sample) { + final List labelValues = PrometheusCollector.getLabelValues(sample.getLabels()); + + return new Observation(category, name, sample.getValue(), labelValues); } + + @Override + public Stream streamObservations() { + final var snapshot = gauge.collect(); + return snapshot.getDataPoints().stream().map(this::convertToObservation); + } + + private record CallbackData(DoubleSupplier valueSupplier, String[] labelValues) {} } diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusGuavaCache.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusGuavaCache.java new file mode 100644 index 000000000000..62bd47f9ab47 --- /dev/null +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusGuavaCache.java @@ -0,0 +1,73 @@ +/* + * 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.prometheus; + +import org.hyperledger.besu.metrics.Observation; +import org.hyperledger.besu.plugin.services.metrics.MetricCategory; + +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Stream; + +import com.google.common.cache.Cache; +import io.prometheus.metrics.instrumentation.guava.CacheMetricsCollector; +import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.vertx.core.impl.ConcurrentHashSet; + +class PrometheusGuavaCache extends CategorizedPrometheusCollector { + private static final String NAME = "__guavaCacheMetricsCollector__"; + private static final CacheMetricsCollector cacheMetricsCollector = new CacheMetricsCollector(); + private static final Set cacheNames = new ConcurrentHashSet<>(); + private static final AtomicBoolean collectorRegistered = new AtomicBoolean(false); + + private final Cache cache; + + public PrometheusGuavaCache( + final MetricCategory category, final String name, final Cache cache) { + super(category, NAME); + if (cacheNames.contains(name)) { + throw new IllegalStateException("Cache already registered: " + name); + } + cacheNames.add(name); + this.cache = cache; + } + + @Override + public String getName() { + return category.getName() + "." + NAME + "." + name; + } + + @Override + public void register(final PrometheusRegistry registry) { + cacheMetricsCollector.addCache(name, cache); + if (collectorRegistered.compareAndSet(false, true)) { + registry.register(cacheMetricsCollector); + } + } + + @Override + public void unregister(final PrometheusRegistry registry) { + cacheMetricsCollector.removeCache(name); + cacheNames.remove(name); + if (cacheNames.isEmpty() && collectorRegistered.compareAndSet(true, false)) { + registry.unregister(cacheMetricsCollector); + } + } + + @Override + public Stream streamObservations() { + return Stream.empty(); + } +} 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 b001eb0b3be4..11082beecceb 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,6 +14,8 @@ */ package org.hyperledger.besu.metrics.prometheus; +import static java.util.Map.entry; + import org.hyperledger.besu.metrics.ObservableMetricsSystem; import org.hyperledger.besu.metrics.Observation; import org.hyperledger.besu.metrics.StandardMetricCategory; @@ -24,10 +26,8 @@ import org.hyperledger.besu.plugin.services.metrics.MetricCategory; import org.hyperledger.besu.plugin.services.metrics.OperationTimer; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -37,33 +37,38 @@ import com.google.common.cache.Cache; import com.google.common.collect.ImmutableSet; -import io.prometheus.client.Collector; -import io.prometheus.client.Collector.MetricFamilySamples; -import io.prometheus.client.Collector.MetricFamilySamples.Sample; -import io.prometheus.client.CollectorRegistry; -import io.prometheus.client.Counter; -import io.prometheus.client.Histogram; -import io.prometheus.client.Summary; -import io.prometheus.client.guava.cache.CacheMetricsCollector; -import io.prometheus.client.hotspot.BufferPoolsExports; -import io.prometheus.client.hotspot.ClassLoadingExports; -import io.prometheus.client.hotspot.GarbageCollectorExports; -import io.prometheus.client.hotspot.MemoryPoolsExports; -import io.prometheus.client.hotspot.StandardExports; -import io.prometheus.client.hotspot.ThreadExports; +import io.prometheus.metrics.instrumentation.guava.CacheMetricsCollector; +import io.prometheus.metrics.instrumentation.jvm.JvmBufferPoolMetrics; +import io.prometheus.metrics.instrumentation.jvm.JvmClassLoadingMetrics; +import io.prometheus.metrics.instrumentation.jvm.JvmCompilationMetrics; +import io.prometheus.metrics.instrumentation.jvm.JvmGarbageCollectorMetrics; +import io.prometheus.metrics.instrumentation.jvm.JvmMemoryMetrics; +import io.prometheus.metrics.instrumentation.jvm.JvmMemoryPoolAllocationMetrics; +import io.prometheus.metrics.instrumentation.jvm.JvmNativeMemoryMetrics; +import io.prometheus.metrics.instrumentation.jvm.JvmRuntimeInfoMetric; +import io.prometheus.metrics.instrumentation.jvm.JvmThreadsMetrics; +import io.prometheus.metrics.instrumentation.jvm.ProcessMetrics; +import io.prometheus.metrics.model.registry.PrometheusRegistry; import io.vertx.core.impl.ConcurrentHashSet; /** The Prometheus metrics system. */ public class PrometheusMetricsSystem implements ObservableMetricsSystem { - private static final List EXTERNAL_SUMMARY_LABELS = List.of("quantile"); - - private final Map> collectors = new ConcurrentHashMap<>(); - private final CollectorRegistry registry = new CollectorRegistry(true); + private static final Map DEFAULT_SUMMARY_QUANTILES = + Map.ofEntries( + entry(0.2, 0.02), + entry(0.5, 0.05), + entry(0.8, 0.02), + entry(0.95, 0.005), + entry(0.99, 0.001), + entry(1.0, 0.0)); + + private final Map> collectors = + new ConcurrentHashMap<>(); + private final PrometheusRegistry registry = PrometheusRegistry.defaultRegistry; private final Map> cachedCounters = new ConcurrentHashMap<>(); private final Map> cachedTimers = new ConcurrentHashMap<>(); - private final Set totalSuffixedCounters = new ConcurrentHashSet<>(); private final Map guavaCacheCollectors = new ConcurrentHashMap<>(); private final Set guavaCacheNames = new ConcurrentHashSet<>(); @@ -85,15 +90,19 @@ public PrometheusMetricsSystem( /** Init. */ public void init() { - if (isCategoryEnabled(StandardMetricCategory.PROCESS)) { - registerCollector(StandardMetricCategory.PROCESS, new StandardExports()); - } if (isCategoryEnabled(StandardMetricCategory.JVM)) { - registerCollector(StandardMetricCategory.JVM, new MemoryPoolsExports()); - registerCollector(StandardMetricCategory.JVM, new BufferPoolsExports()); - registerCollector(StandardMetricCategory.JVM, new GarbageCollectorExports()); - registerCollector(StandardMetricCategory.JVM, new ThreadExports()); - registerCollector(StandardMetricCategory.JVM, new ClassLoadingExports()); + JvmThreadsMetrics.builder().register(registry); + JvmBufferPoolMetrics.builder().register(registry); + JvmClassLoadingMetrics.builder().register(registry); + JvmCompilationMetrics.builder().register(registry); + JvmGarbageCollectorMetrics.builder().register(registry); + JvmMemoryMetrics.builder().register(registry); + JvmMemoryPoolAllocationMetrics.builder().register(registry); + JvmNativeMemoryMetrics.builder().register(registry); + JvmRuntimeInfoMetric.builder().register(registry); + } + if (isCategoryEnabled(StandardMetricCategory.PROCESS)) { + ProcessMetrics.builder().register(registry); } } @@ -108,14 +117,13 @@ public LabelledMetric crea final String name, final String help, final String... labelNames) { - final String metricName = convertToPrometheusCounterName(category, name); return cachedCounters.computeIfAbsent( - metricName, + name, (k) -> { if (isCategoryEnabled(category)) { - final Counter counter = Counter.build(metricName, help).labelNames(labelNames).create(); + final var counter = new PrometheusCounter(category, name, help, labelNames); registerCollector(category, counter); - return new PrometheusCounter(counter); + return counter; } else { return NoOpMetricsSystem.getCounterLabelledMetric(labelNames.length); } @@ -128,23 +136,14 @@ public LabelledMetric createLabelledTimer( final String name, final String help, final String... labelNames) { - final String metricName = convertToPrometheusName(category, name); return cachedTimers.computeIfAbsent( - metricName, + name, (k) -> { if (timersEnabled && isCategoryEnabled(category)) { - final Summary summary = - Summary.build(metricName, help) - .quantile(0.2, 0.02) - .quantile(0.5, 0.05) - .quantile(0.8, 0.02) - .quantile(0.95, 0.005) - .quantile(0.99, 0.001) - .quantile(1.0, 0) - .labelNames(labelNames) - .create(); + final var summary = + new PrometheusTimer(category, name, help, DEFAULT_SUMMARY_QUANTILES, labelNames); registerCollector(category, summary); - return new PrometheusTimer(summary); + return summary; } else { return NoOpMetricsSystem.getOperationTimerLabelledMetric(labelNames.length); } @@ -157,34 +156,20 @@ public LabelledMetric createSimpleLabelledTimer( final String name, final String help, final String... labelNames) { - final String metricName = convertToPrometheusName(category, name); return cachedTimers.computeIfAbsent( - metricName, + name, (k) -> { if (timersEnabled && isCategoryEnabled(category)) { - final Histogram histogram = - Histogram.build(metricName, help).labelNames(labelNames).buckets(1D).create(); + final var histogram = + new PrometheusSimpleTimer(category, name, help, new double[] {1D}, labelNames); registerCollector(category, histogram); - return new PrometheusSimpleTimer(histogram); + return histogram; } else { return NoOpMetricsSystem.getOperationTimerLabelledMetric(labelNames.length); } }); } - @Override - public void createGauge( - final MetricCategory category, - final String name, - final String help, - final DoubleSupplier valueSupplier) { - final String metricName = convertToPrometheusName(category, name); - if (isCategoryEnabled(category)) { - final Collector collector = new CurrentValueCollector(metricName, help, valueSupplier); - registerCollector(category, collector); - } - } - @Override public void trackExternalSummary( final MetricCategory category, @@ -192,50 +177,21 @@ public void trackExternalSummary( final String help, final Supplier summarySupplier) { if (isCategoryEnabled(category)) { - final var externalSummaryCollector = - new Collector() { - @Override - public List collect() { - final var externalSummary = summarySupplier.get(); - - final var quantileValues = - externalSummary.quantiles().stream() - .map( - quantile -> - new Sample( - name, - EXTERNAL_SUMMARY_LABELS, - List.of(Double.toString(quantile.quantile())), - quantile.value())) - .toList(); - - return List.of( - new MetricFamilySamples( - name, Type.SUMMARY, "RocksDB histogram for " + name, quantileValues)); - } - }; + final PrometheusExternalSummary externalSummary = + new PrometheusExternalSummary(category, name, help, summarySupplier); - registerCollector(category, externalSummaryCollector); + registerCollector(category, externalSummary); } } @Override - public void createGuavaCacheCollector( - final MetricCategory category, final String name, final Cache cache) { + public void createGauge( + final MetricCategory category, + final String name, + final String help, + final DoubleSupplier valueSupplier) { if (isCategoryEnabled(category)) { - if (guavaCacheNames.contains(name)) { - throw new IllegalStateException("Cache already registered: " + name); - } - guavaCacheNames.add(name); - final var guavaCacheCollector = - guavaCacheCollectors.computeIfAbsent( - category, - unused -> { - final var cmc = new CacheMetricsCollector(); - registerCollector(category, cmc); - return cmc; - }); - guavaCacheCollector.addCache(name, cache); + registerCollector(category, new PrometheusGauge(category, name, help, valueSupplier)); } } @@ -245,43 +201,50 @@ public LabelledGauge createLabelledGauge( final String name, final String help, final String... labelNames) { - final String metricName = convertToPrometheusName(category, name); if (isCategoryEnabled(category)) { - final PrometheusGauge gauge = new PrometheusGauge(metricName, help, List.of(labelNames)); + final PrometheusGauge gauge = new PrometheusGauge(category, name, help, labelNames); registerCollector(category, gauge); return gauge; } return NoOpMetricsSystem.getLabelledGauge(labelNames.length); } - private void registerCollector(final MetricCategory category, final Collector collector) { - final Collection categoryCollectors = - this.collectors.computeIfAbsent( - category, key -> Collections.newSetFromMap(new ConcurrentHashMap<>())); + @Override + public void createGuavaCacheCollector( + final MetricCategory category, final String name, final Cache cache) { + if (isCategoryEnabled(category)) { + if (guavaCacheNames.contains(name)) { + throw new IllegalStateException("Cache already registered: " + name); + } + guavaCacheNames.add(name); + final var cacheCollector = new PrometheusGuavaCache(category, name, cache); + registerCollector(category, cacheCollector); + } + } - final List newSamples = - collector.collect().stream().map(metricFamilySamples -> metricFamilySamples.name).toList(); + private void registerCollector( + final MetricCategory category, final PrometheusCollector collector) { + final Collection categoryCollectors = + this.collectors.computeIfAbsent(category, key -> new ConcurrentHashSet<>()); + // unregister if already present categoryCollectors.stream() - .filter( - c -> - c.collect().stream() - .anyMatch(metricFamilySamples -> newSamples.contains(metricFamilySamples.name))) + .filter(c -> c.getName().equals(collector.getName())) .findFirst() .ifPresent( c -> { categoryCollectors.remove(c); - registry.unregister(c); + c.unregister(registry); }); - categoryCollectors.add(collector.register(registry)); + collector.register(registry); + categoryCollectors.add(collector); } @Override public Stream streamObservations(final MetricCategory category) { return collectors.getOrDefault(category, Collections.emptySet()).stream() - .flatMap(collector -> collector.collect().stream()) - .flatMap(familySamples -> convertSamplesToObservations(category, familySamples)); + .flatMap(PrometheusCollector::streamObservations); } @Override @@ -289,6 +252,10 @@ public Stream streamObservations() { return collectors.keySet().stream().flatMap(this::streamObservations); } + PrometheusRegistry getRegistry() { + return registry; + } + @Override public void shutdown() { registry.clear(); @@ -298,128 +265,4 @@ public void shutdown() { guavaCacheCollectors.clear(); guavaCacheNames.clear(); } - - private Stream convertSamplesToObservations( - final MetricCategory category, final MetricFamilySamples familySamples) { - return familySamples.samples.stream() - .map(sample -> createObservationFromSample(category, sample, familySamples)); - } - - private Observation createObservationFromSample( - final MetricCategory category, final Sample sample, final MetricFamilySamples familySamples) { - if (familySamples.type == Collector.Type.HISTOGRAM) { - return convertHistogramSampleNamesToLabels(category, sample, familySamples); - } - if (familySamples.type == Collector.Type.SUMMARY) { - return convertSummarySampleNamesToLabels(category, sample, familySamples); - } - if (familySamples.type == Collector.Type.COUNTER) { - return convertCounterNamesToLabels(category, sample, familySamples); - } - return new Observation( - category, - convertFromPrometheusName(category, sample.name), - sample.value, - sample.labelValues); - } - - private Observation convertCounterNamesToLabels( - final MetricCategory category, final Sample sample, final MetricFamilySamples familySamples) { - final List labelValues = new ArrayList<>(sample.labelValues); - if (sample.name.endsWith("_created")) { - labelValues.add("created"); - } - - return new Observation( - category, - convertFromPrometheusCounterName(category, familySamples.name), - sample.value, - labelValues); - } - - private Observation convertHistogramSampleNamesToLabels( - final MetricCategory category, final Sample sample, final MetricFamilySamples familySamples) { - final List labelValues = new ArrayList<>(sample.labelValues); - if (sample.name.endsWith("_bucket")) { - labelValues.add(labelValues.size() - 1, "bucket"); - } else { - labelValues.add(sample.name.substring(sample.name.lastIndexOf("_") + 1)); - } - return new Observation( - category, - convertFromPrometheusName(category, familySamples.name), - sample.value, - labelValues); - } - - private Observation convertSummarySampleNamesToLabels( - final MetricCategory category, final Sample sample, final MetricFamilySamples familySamples) { - final List labelValues = new ArrayList<>(sample.labelValues); - if (sample.name.endsWith("_sum")) { - labelValues.add("sum"); - } else if (sample.name.endsWith("_count")) { - labelValues.add("count"); - } else if (sample.name.endsWith("_created")) { - labelValues.add("created"); - } else { - labelValues.add(labelValues.size() - 1, "quantile"); - } - return new Observation( - category, - convertFromPrometheusName(category, familySamples.name), - sample.value, - labelValues); - } - - /** - * Convert to prometheus name. - * - * @param category the category - * @param name the name - * @return the name as string - */ - public String convertToPrometheusName(final MetricCategory category, final String name) { - return prometheusPrefix(category) + name; - } - - /** - * Convert to prometheus counter name. Prometheus adds a _total suffix to the name if not present, - * so we remember if the original name already has it, to be able to convert back correctly - * - * @param category the category - * @param name the name - * @return the name as string - */ - public String convertToPrometheusCounterName(final MetricCategory category, final String name) { - if (name.endsWith("_total")) { - totalSuffixedCounters.add(name); - } - return convertToPrometheusName(category, name); - } - - private String convertFromPrometheusName(final MetricCategory category, final String metricName) { - final String prefix = prometheusPrefix(category); - return metricName.startsWith(prefix) ? metricName.substring(prefix.length()) : metricName; - } - - private String convertFromPrometheusCounterName( - final MetricCategory category, final String metricName) { - final String unPrefixedName = convertFromPrometheusName(category, metricName); - return totalSuffixedCounters.contains(unPrefixedName + "_total") - ? unPrefixedName + "_total" - : unPrefixedName; - } - - private String prometheusPrefix(final MetricCategory category) { - return category.getApplicationPrefix().orElse("") + category.getName() + "_"; - } - - /** - * Gets registry. - * - * @return the registry - */ - CollectorRegistry getRegistry() { - return registry; - } } diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusSimpleTimer.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusSimpleTimer.java index 24799cb33d2d..14bd54652260 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusSimpleTimer.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusSimpleTimer.java @@ -14,22 +14,79 @@ */ package org.hyperledger.besu.metrics.prometheus; +import org.hyperledger.besu.metrics.Observation; 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 io.prometheus.client.Histogram; +import java.util.stream.Stream; -class PrometheusSimpleTimer implements LabelledMetric { +import io.prometheus.metrics.core.metrics.Histogram; +import io.prometheus.metrics.model.registry.PrometheusRegistry; + +class PrometheusSimpleTimer extends CategorizedPrometheusCollector + implements LabelledMetric { private final Histogram histogram; - public PrometheusSimpleTimer(final Histogram histogram) { - this.histogram = histogram; + public PrometheusSimpleTimer( + final MetricCategory category, + final String name, + final String help, + final double[] buckets, + final String... labelNames) { + super(category, name); + this.histogram = + Histogram.builder() + .name(this.prefixedName) + .help(help) + .labelNames(labelNames) + .classicOnly() + .classicUpperBounds(buckets) + .build(); } @Override public OperationTimer labels(final String... labels) { - final Histogram.Child metric = histogram.labels(labels); - return () -> metric.startTimer()::observeDuration; + final var ddp = histogram.labelValues(labels); + return () -> ddp.startTimer()::observeDuration; + } + + @Override + public String getName() { + return histogram.getPrometheusName(); + } + + @Override + public void register(final PrometheusRegistry registry) { + registry.register(histogram); + } + + @Override + public void unregister(final PrometheusRegistry registry) { + registry.unregister(histogram); + } + + @Override + public Stream streamObservations() { + final var snapshot = histogram.collect(); + return snapshot.getDataPoints().stream() + .flatMap( + dataPoint -> { + final var labelValues = PrometheusCollector.getLabelValues(dataPoint.getLabels()); + if (!dataPoint.hasClassicHistogramData()) { + throw new IllegalStateException("Only classic histogram are supported"); + } + + return dataPoint.getClassicBuckets().stream() + .map( + bucket -> + new Observation( + category, + name, + bucket.getCount(), + PrometheusCollector.addLabelValues( + labelValues, Double.toString(bucket.getUpperBound())))); + }); } } diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusTimer.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusTimer.java index ad50ab7149b9..47c545d8e7d9 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusTimer.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusTimer.java @@ -15,21 +15,56 @@ package org.hyperledger.besu.metrics.prometheus; 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 io.prometheus.client.Summary; +import java.util.Map; -class PrometheusTimer implements LabelledMetric { +import io.prometheus.metrics.core.datapoints.DistributionDataPoint; +import io.prometheus.metrics.core.metrics.Summary; +import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.SummarySnapshot; - private final Summary summary; +class PrometheusTimer extends AbstractPrometheusSummary implements LabelledMetric { - public PrometheusTimer(final Summary summary) { - this.summary = summary; + private final io.prometheus.metrics.core.metrics.Summary summary; + + public PrometheusTimer( + final MetricCategory category, + final String name, + final String help, + final Map quantiles, + final String... labelNames) { + super(category, name); + final var summaryBuilder = + Summary.builder().name(this.prefixedName).help(help).labelNames(labelNames); + quantiles.forEach(summaryBuilder::quantile); + this.summary = summaryBuilder.build(); } @Override public OperationTimer labels(final String... labels) { - final Summary.Child metric = summary.labels(labels); + final DistributionDataPoint metric = summary.labelValues(labels); return () -> metric.startTimer()::observeDuration; } + + @Override + public String getName() { + return summary.getPrometheusName(); + } + + @Override + public void register(final PrometheusRegistry registry) { + registry.register(summary); + } + + @Override + public void unregister(final PrometheusRegistry registry) { + registry.unregister(summary); + } + + @Override + protected SummarySnapshot collect() { + return summary.collect(); + } } 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 064dabfe331f..b9bf700929a9 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 @@ -24,43 +24,50 @@ import java.util.Properties; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.prometheus.client.exporter.common.TextFormat; -import io.vertx.core.Vertx; +import io.prometheus.metrics.expositionformats.PrometheusTextFormatWriter; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; public class MetricsHttpServiceTest { - private static final Vertx vertx = Vertx.vertx(); - - private static MetricsHttpService service; - private static OkHttpClient client; - private static String baseUrl; - - @BeforeAll - public static void initServerAndClient() { - service = createMetricsHttpService(); - service.start().join(); + private PrometheusMetricsSystem metricsSystem; + private MetricsHttpService service; + private OkHttpClient client; + private String baseUrl; + + private void initServerAndClient( + final MetricsConfiguration metricsConfiguration, final boolean start) { + metricsSystem = (PrometheusMetricsSystem) MetricsSystemFactory.create(metricsConfiguration); + service = createMetricsHttpService(metricsConfiguration, metricsSystem); + if (start) { + service.start().join(); + } - // Build an OkHttp client. client = new OkHttpClient(); baseUrl = urlForSocketAddress("http", service.socketAddress()); } - private static MetricsHttpService createMetricsHttpService(final MetricsConfiguration config) { - GlobalOpenTelemetry.resetForTest(); - return new MetricsHttpService(vertx, config, MetricsSystemFactory.create(config)); + private void initServerAndClient(final boolean start) { + initServerAndClient(createMetricsConfig(), start); + } + + private void initServerAndClient() { + initServerAndClient(createMetricsConfig(), true); } - private static MetricsHttpService createMetricsHttpService() { + @AfterEach + public void stopServer() { + metricsSystem.shutdown(); + service.stop(); + } + + private MetricsHttpService createMetricsHttpService( + final MetricsConfiguration config, final PrometheusMetricsSystem metricsSystem) { GlobalOpenTelemetry.resetForTest(); - final MetricsConfiguration metricsConfiguration = createMetricsConfig(); - return new MetricsHttpService( - vertx, metricsConfiguration, MetricsSystemFactory.create(metricsConfiguration)); + return new MetricsHttpService(config, metricsSystem); } private static MetricsConfiguration createMetricsConfig() { @@ -71,15 +78,9 @@ private static MetricsConfiguration.Builder createMetricsConfigBuilder() { return MetricsConfiguration.builder().enabled(true).port(0).hostsAllowlist(singletonList("*")); } - /** Tears down the HTTP server. */ - @AfterAll - public static void shutdownServer() { - service.stop().join(); - vertx.close(); - } - @Test public void invalidCallToStart() { + initServerAndClient(); service .start() .whenComplete( @@ -88,6 +89,7 @@ public void invalidCallToStart() { @Test public void http404() throws Exception { + initServerAndClient(); try (final Response resp = client.newCall(buildGetRequest("/foo")).execute()) { assertThat(resp.code()).isEqualTo(404); } @@ -95,13 +97,15 @@ public void http404() throws Exception { @Test public void handleEmptyRequest() throws Exception { + initServerAndClient(); try (final Response resp = client.newCall(buildGetRequest("")).execute()) { - assertThat(resp.code()).isEqualTo(201); + assertThat(resp.code()).isEqualTo(200); } } @Test public void getSocketAddressWhenActive() { + initServerAndClient(); final InetSocketAddress socketAddress = service.socketAddress(); assertThat("127.0.0.1").isEqualTo(socketAddress.getAddress().getHostAddress()); assertThat(socketAddress.getPort() > 0).isTrue(); @@ -109,7 +113,7 @@ public void getSocketAddressWhenActive() { @Test public void getSocketAddressWhenStoppedIsEmpty() { - final MetricsHttpService service = createMetricsHttpService(); + initServerAndClient(false); final InetSocketAddress socketAddress = service.socketAddress(); assertThat("0.0.0.0").isEqualTo(socketAddress.getAddress().getHostAddress()); @@ -119,9 +123,7 @@ public void getSocketAddressWhenStoppedIsEmpty() { @Test public void getSocketAddressWhenBindingToAllInterfaces() { - final MetricsConfiguration config = createMetricsConfigBuilder().host("0.0.0.0").build(); - final MetricsHttpService service = createMetricsHttpService(config); - service.start().join(); + initServerAndClient(createMetricsConfigBuilder().host("0.0.0.0").build(), true); try { final InetSocketAddress socketAddress = service.socketAddress(); @@ -134,6 +136,7 @@ public void getSocketAddressWhenBindingToAllInterfaces() { @Test public void metricsArePresent() throws Exception { + initServerAndClient(); final Request metricsRequest = new Request.Builder().url(baseUrl + "/metrics").build(); try (final Response resp = client.newCall(metricsRequest).execute()) { assertThat(resp.code()).isEqualTo(200); @@ -148,6 +151,7 @@ public void metricsArePresent() throws Exception { @Test public void metricsArePresentWhenFiltered() throws Exception { + initServerAndClient(); final Request metricsRequest = new Request.Builder().url(baseUrl + "/metrics?name[]=jvm_threads_deadlocked").build(); try (final Response resp = client.newCall(metricsRequest).execute()) { @@ -163,6 +167,7 @@ public void metricsArePresentWhenFiltered() throws Exception { @Test public void metricsAreAbsentWhenFiltered() throws Exception { + initServerAndClient(); final Request metricsRequest = new Request.Builder().url(baseUrl + "/metrics?name[]=does_not_exist").build(); try (final Response resp = client.newCall(metricsRequest).execute()) { @@ -179,6 +184,7 @@ public void metricsAreAbsentWhenFiltered() throws Exception { @Test // There is only one available representation so content negotiation should not be used public void acceptHeaderIgnored() throws Exception { + initServerAndClient(); final Request metricsRequest = new Request.Builder().addHeader("Accept", "text/xml").url(baseUrl + "/metrics").build(); try (final Response resp = client.newCall(metricsRequest).execute()) { @@ -189,7 +195,7 @@ public void acceptHeaderIgnored() throws Exception { // We should have JVM metrics already loaded, verify a simple key. assertThat(props).containsKey("jvm_threads_deadlocked"); - assertThat(resp.header("Content-Type")).contains(TextFormat.CONTENT_TYPE_004); + assertThat(resp.header("Content-Type")).contains(PrometheusTextFormatWriter.CONTENT_TYPE); } } 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 2ddf86d347ba..b3e25593f7d4 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 @@ -38,33 +38,28 @@ import org.hyperledger.besu.plugin.services.metrics.OperationTimer; import java.util.Collections; -import java.util.Comparator; import java.util.List; import com.google.common.collect.ImmutableSet; import io.opentelemetry.api.GlobalOpenTelemetry; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; public class PrometheusMetricsSystemTest { - 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 static final Comparator WITH_VALUES = - Comparator.comparing(observation -> observation.getCategory().getName()) - .thenComparing(Observation::getMetricName) - .thenComparing((o1, o2) -> o1.getLabels().equals(o2.getLabels()) ? 0 : 1) - .thenComparing((o1, o2) -> o1.getValue().equals(o2.getValue()) ? 0 : 1); + private PrometheusMetricsSystem metricsSystem; @BeforeEach - public void resetGlobalOpenTelemetry() { + public void setUp() { + metricsSystem = new PrometheusMetricsSystem(DEFAULT_METRIC_CATEGORIES, true); GlobalOpenTelemetry.resetForTest(); } - private final ObservableMetricsSystem metricsSystem = - new PrometheusMetricsSystem(DEFAULT_METRIC_CATEGORIES, true); + @AfterEach + public void tearDown() { + metricsSystem.shutdown(); + } @Test public void shouldCreateObservationFromCounter() { @@ -72,17 +67,11 @@ public void shouldCreateObservationFromCounter() { counter.inc(); assertThat(metricsSystem.streamObservations()) - .usingElementComparator(this::compareCounters) - .containsExactlyInAnyOrder( - new Observation(PEERS, "connected", 1.0, emptyList()), - new Observation(PEERS, "connected", null, List.of("created"))); + .containsExactlyInAnyOrder(new Observation(PEERS, "connected", 1.0, emptyList())); counter.inc(); assertThat(metricsSystem.streamObservations()) - .usingElementComparator(this::compareCounters) - .containsExactly( - new Observation(PEERS, "connected", 2.0, emptyList()), - new Observation(PEERS, "connected", null, List.of("created"))); + .containsExactly(new Observation(PEERS, "connected", 2.0, emptyList())); } @Test @@ -95,17 +84,11 @@ public void shouldHandleDuplicateCounterCreation() { counter1.labels().inc(); assertThat(metricsSystem.streamObservations()) - .usingElementComparator(this::compareCounters) - .containsExactly( - new Observation(PEERS, "connected", 1.0, emptyList()), - new Observation(PEERS, "connected", null, List.of("created"))); + .containsExactly(new Observation(PEERS, "connected", 1.0, emptyList())); counter2.labels().inc(); assertThat(metricsSystem.streamObservations()) - .usingElementComparator(this::compareCounters) - .containsExactly( - new Observation(PEERS, "connected", 2.0, emptyList()), - new Observation(PEERS, "connected", null, List.of("created"))); + .containsExactly(new Observation(PEERS, "connected", 2.0, emptyList())); } @Test @@ -119,12 +102,9 @@ public void shouldCreateSeparateObservationsForEachCounterLabelValue() { counter.labels("value1").inc(); assertThat(metricsSystem.streamObservations()) - .usingElementComparator(this::compareCounters) .containsExactlyInAnyOrder( new Observation(PEERS, "connected_total", 2.0, singletonList("value1")), - new Observation(PEERS, "connected_total", 1.0, singletonList("value2")), - new Observation(PEERS, "connected_total", null, List.of("value1", "created")), - new Observation(PEERS, "connected_total", null, List.of("value2", "created"))); + new Observation(PEERS, "connected_total", 1.0, singletonList("value2"))); } @Test @@ -160,18 +140,12 @@ public void shouldIncrementCounterBySpecifiedAmount() { counter.inc(5); assertThat(metricsSystem.streamObservations()) - .usingElementComparator(this::compareCounters) - .containsExactly( - new Observation(PEERS, "connected", 5.0, emptyList()), - new Observation(PEERS, "connected", null, List.of("created"))); + .containsExactly(new Observation(PEERS, "connected", 5.0, emptyList())); counter.inc(6); assertThat(metricsSystem.streamObservations()) .usingDefaultElementComparator() - .usingElementComparator(this::compareCounters) - .containsExactly( - new Observation(PEERS, "connected", 11.0, emptyList()), - new Observation(PEERS, "connected", null, List.of("created"))); + .containsExactly(new Observation(PEERS, "connected", 11.0, emptyList())); } @Test @@ -179,20 +153,18 @@ public void shouldCreateObservationsFromTimer() { final OperationTimer timer = metricsSystem.createTimer(RPC, "request", "Some help"); final OperationTimer.TimingContext context = timer.startTimer(); - context.stopTimer(); + final var expected = context.stopTimer(); assertThat(metricsSystem.streamObservations()) - .usingElementComparator(IGNORE_VALUES) .containsExactlyInAnyOrder( - new Observation(RPC, "request", null, asList("quantile", "0.2")), - new Observation(RPC, "request", null, asList("quantile", "0.5")), - new Observation(RPC, "request", null, asList("quantile", "0.8")), - new Observation(RPC, "request", null, asList("quantile", "0.95")), - new Observation(RPC, "request", null, asList("quantile", "0.99")), - new Observation(RPC, "request", null, asList("quantile", "1.0")), - new Observation(RPC, "request", null, singletonList("sum")), - new Observation(RPC, "request", null, singletonList("count")), - new Observation(RPC, "request", null, singletonList("created"))); + new Observation(RPC, "request", expected, asList("quantile", "0.2")), + new Observation(RPC, "request", expected, asList("quantile", "0.5")), + new Observation(RPC, "request", expected, asList("quantile", "0.8")), + new Observation(RPC, "request", expected, asList("quantile", "0.95")), + new Observation(RPC, "request", expected, asList("quantile", "0.99")), + new Observation(RPC, "request", expected, asList("quantile", "1.0")), + new Observation(RPC, "request", expected, singletonList("sum")), + new Observation(RPC, "request", 1L, singletonList("count"))); } @Test @@ -209,21 +181,19 @@ public void shouldCreateObservationsFromTimerWithLabels() { final LabelledMetric timer = metricsSystem.createLabelledTimer(RPC, "request", "Some help", "methodName"); - //noinspection EmptyTryBlock - try (final OperationTimer.TimingContext ignored = timer.labels("method").startTimer()) {} + final OperationTimer.TimingContext context = timer.labels("method").startTimer(); + final double expected = context.stopTimer(); assertThat(metricsSystem.streamObservations()) - .usingElementComparator(IGNORE_VALUES) // We don't know how long it will actually take. .containsExactlyInAnyOrder( - new Observation(RPC, "request", null, asList("method", "quantile", "0.2")), - new Observation(RPC, "request", null, asList("method", "quantile", "0.5")), - new Observation(RPC, "request", null, asList("method", "quantile", "0.8")), - new Observation(RPC, "request", null, asList("method", "quantile", "0.95")), - new Observation(RPC, "request", null, asList("method", "quantile", "0.99")), - new Observation(RPC, "request", null, asList("method", "quantile", "1.0")), - new Observation(RPC, "request", null, asList("method", "sum")), - new Observation(RPC, "request", null, asList("method", "count")), - new Observation(RPC, "request", null, asList("method", "created"))); + new Observation(RPC, "request", expected, asList("method", "quantile", "0.2")), + new Observation(RPC, "request", expected, asList("method", "quantile", "0.5")), + new Observation(RPC, "request", expected, asList("method", "quantile", "0.8")), + new Observation(RPC, "request", expected, asList("method", "quantile", "0.95")), + new Observation(RPC, "request", expected, asList("method", "quantile", "0.99")), + new Observation(RPC, "request", expected, asList("method", "quantile", "1.0")), + new Observation(RPC, "request", expected, asList("method", "sum")), + new Observation(RPC, "request", 1L, asList("method", "count"))); } @Test @@ -319,12 +289,4 @@ private boolean isCreatedSample(final Observation obs) { // may want to ignore return obs.getLabels().contains("created"); } - - private int compareCounters(final Observation obs1, final Observation obs2) { - // for created samples ignore values - if (obs1.getLabels().contains("created") && obs2.getLabels().contains("created")) { - return IGNORE_VALUES.compare(obs1, obs2); - } - return WITH_VALUES.compare(obs1, obs2); - } } diff --git a/platform/build.gradle b/platform/build.gradle index d614603f17ff..6225b21a809e 100644 --- a/platform/build.gradle +++ b/platform/build.gradle @@ -30,7 +30,7 @@ dependencies { api platform('io.grpc:grpc-bom:1.68.0') api platform('io.netty:netty-bom:4.1.114.Final') api platform('io.opentelemetry:opentelemetry-bom:1.43.0') - api platform('io.prometheus:simpleclient_bom:0.16.0') + api platform('io.prometheus:prometheus-metrics-bom:1.3.3') api platform('io.vertx:vertx-stack-depchain:4.5.10') api platform('org.apache.logging.log4j:log4j-bom:2.24.1') api platform('org.assertj:assertj-bom:3.26.3') @@ -104,6 +104,8 @@ dependencies { api 'io.opentracing:opentracing-util:0.33.0' api 'io.opentracing.contrib:opentracing-okhttp3:3.0.0' + api 'io.prometheus:prometheus-metrics-instrumentation-guava:1.3.3' + api 'io.pkts:pkts-core:3.0.10' api 'io.tmio:tuweni-bytes:2.4.2' diff --git a/plugin-api/build.gradle b/plugin-api/build.gradle index 2d3d5191df41..42587278f9c1 100644 --- a/plugin-api/build.gradle +++ b/plugin-api/build.gradle @@ -71,7 +71,7 @@ Calculated : ${currentHash} tasks.register('checkAPIChanges', FileStateChecker) { description = "Checks that the API for the Plugin-API project does not change without deliberate thought" files = sourceSets.main.allJava.files - knownHash = 'aYWbsgPoKTGDgq9d4QUBvQEaZYbKNJGMiBufzyKnusA=' + knownHash = '/uVL++40w0GJqDavP8+z3Pv8qIdA+G9jvj4os2JvQ/4=' } check.dependsOn('checkAPIChanges')