Migrate to Prometheus lib 1.x (#7880)

* Upgrade to Promethus java client 1.x and adapt the code to the new version

Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>

* Update CHANGELOG.md

Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>
Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>

---------

Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>
Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>
pull/7945/head
Fabio Di Fabio 1 week ago committed by GitHub
parent e8a282fbcd
commit 5b2da5a068
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 16
      CHANGELOG.md
  2. 8
      besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java
  3. 4
      besu/src/main/java/org/hyperledger/besu/cli/subcommands/blocks/BlocksSubCommand.java
  4. 12
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugMetrics.java
  5. 2
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/DefaultBlockchain.java
  6. 546
      gradle/verification-metadata.xml
  7. 11
      metrics/core/build.gradle
  8. 10
      metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsService.java
  9. 93
      metrics/core/src/main/java/org/hyperledger/besu/metrics/Observation.java
  10. 78
      metrics/core/src/main/java/org/hyperledger/besu/metrics/noop/NoOpMetricsSystem.java
  11. 12
      metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetrySystem.java
  12. 132
      metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/AbstractPrometheusSummary.java
  13. 96
      metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/AbstractPrometheusSuppliedValueCollector.java
  14. 50
      metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/CategorizedPrometheusCollector.java
  15. 59
      metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/CurrentValueCollector.java
  16. 202
      metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsHttpService.java
  17. 14
      metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsPushGatewayService.java
  18. 82
      metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusCollector.java
  19. 72
      metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusCounter.java
  20. 171
      metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusGuavaCache.java
  21. 374
      metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystem.java
  22. 76
      metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusSimpleTimer.java
  23. 73
      metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusSuppliedCounter.java
  24. 73
      metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusSuppliedGauge.java
  25. 83
      metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusSuppliedSummary.java
  26. 81
      metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusSuppliedValueCollector.java
  27. 28
      metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusTimer.java
  28. 9
      metrics/core/src/test-support/java/org/hyperledger/besu/metrics/StubMetricsSystem.java
  29. 10
      metrics/core/src/test/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetryMetricsSystemTest.java
  30. 72
      metrics/core/src/test/java/org/hyperledger/besu/metrics/prometheus/MetricsHttpServiceTest.java
  31. 135
      metrics/core/src/test/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystemTest.java
  32. 2
      metrics/rocksdb/src/main/java/org/hyperledger/besu/metrics/rocksdb/RocksDBStats.java
  33. 2
      platform/build.gradle
  34. 2
      plugin-api/build.gradle
  35. 34
      plugin-api/src/main/java/org/hyperledger/besu/plugin/services/MetricsSystem.java
  36. 28
      plugin-api/src/main/java/org/hyperledger/besu/plugin/services/metrics/LabelledSuppliedSummary.java

@ -4,6 +4,21 @@
### Breaking Changes
- Removed Retesteth rpc service and commands [#7833](https://github.com/hyperledger/besu/pull/7783)
- With the upgrade of the Prometheus Java Metrics library, there are the following changes:
- Gauge names are not allowed to end with `total`, therefore the metric `besu_blockchain_difficulty_total` is losing the `_total` suffix
- The `_created` timestamps are not returned by default, you can set the env var `BESU_OPTS="-Dio.prometheus.exporter.includeCreatedTimestamps=true"` to enable them
- Some JVM metrics have changed name to adhere to the OTEL standard (see the table below), [Besu Full Grafana dashboard](https://grafana.com/grafana/dashboards/16455-besu-full/) is updated to support both names
| Old Name | New Name |
|---------------------------------|---------------------------------|
| jvm_memory_bytes_committed | jvm_memory_committed_bytes |
| jvm_memory_bytes_init | jvm_memory_init_bytes |
| jvm_memory_bytes_max | jvm_memory_max_bytes |
| jvm_memory_bytes_used | jvm_memory_used_bytes |
| jvm_memory_pool_bytes_committed | jvm_memory_pool_committed_bytes |
| jvm_memory_pool_bytes_init | jvm_memory_pool_init_bytes |
| jvm_memory_pool_bytes_max | jvm_memory_pool_max_bytes |
| jvm_memory_pool_bytes_used | jvm_memory_pool_used_bytes |
### Upcoming Breaking Changes
- Plugin API will be deprecating the BesuContext interface to be replaced with the ServiceManager interface.
@ -26,6 +41,7 @@
- Add a method to check if a metric category is enabled to the plugin API [#7832](https://github.com/hyperledger/besu/pull/7832)
- Add a new metric collector for counters which get their value from suppliers [#7894](https://github.com/hyperledger/besu/pull/7894)
- Add account and state overrides to `eth_call` [#7801](https://github.com/hyperledger/besu/pull/7801) and `eth_estimateGas` [#7890](https://github.com/hyperledger/besu/pull/7890)
- Prometheus Java Metrics library upgraded to version 1.3.3 [#7880](https://github.com/hyperledger/besu/pull/7880)
### Bug fixes
- Fix registering new metric categories from plugins [#7825](https://github.com/hyperledger/besu/pull/7825)

@ -1034,8 +1034,7 @@ public class RunnerBuilder {
subscriptionManager, privacyParameters, context.getBlockchain().getGenesisBlockHeader());
}
final Optional<MetricsService> metricsService =
createMetricsService(vertx, metricsConfiguration);
final Optional<MetricsService> metricsService = createMetricsService(metricsConfiguration);
final Optional<EthStatsService> ethStatsService;
if (isEthStatsEnabled()) {
@ -1469,9 +1468,8 @@ public class RunnerBuilder {
vertx, configuration, websocketMessageHandler, authenticationService, metricsSystem);
}
private Optional<MetricsService> createMetricsService(
final Vertx vertx, final MetricsConfiguration configuration) {
return MetricsService.create(vertx, configuration, metricsSystem);
private Optional<MetricsService> createMetricsService(final MetricsConfiguration configuration) {
return MetricsService.create(configuration, metricsSystem);
}
/**

@ -53,7 +53,6 @@ import java.util.Optional;
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 @@ public class BlocksSubCommand implements Runnable {
parentCommand.parentCommand.metricsConfiguration();
Optional<MetricsService> metricsService =
MetricsService.create(
Vertx.vertx(), metricsConfiguration, parentCommand.parentCommand.getMetricsSystem());
MetricsService.create(metricsConfiguration, parentCommand.parentCommand.getMetricsSystem());
metricsService.ifPresent(MetricsService::start);
return metricsService;
}

@ -50,9 +50,9 @@ public class DebugMetrics implements JsonRpcMethod {
private void addObservation(
final Map<String, Object> observations, final Observation observation) {
final Map<String, Object> categoryObservations =
getNextMapLevel(observations, observation.getCategory().getName());
if (observation.getLabels().isEmpty()) {
categoryObservations.put(observation.getMetricName(), observation.getValue());
getNextMapLevel(observations, observation.category().getName());
if (observation.labels().isEmpty()) {
categoryObservations.put(observation.metricName(), observation.value());
} else {
addLabelledObservation(categoryObservations, observation);
}
@ -60,12 +60,12 @@ public class DebugMetrics implements JsonRpcMethod {
private void addLabelledObservation(
final Map<String, Object> categoryObservations, final Observation observation) {
final List<String> labels = observation.getLabels();
Map<String, Object> values = getNextMapLevel(categoryObservations, observation.getMetricName());
final List<String> labels = observation.labels();
Map<String, Object> values = getNextMapLevel(categoryObservations, observation.metricName());
for (int i = 0; i < labels.size() - 1; i++) {
values = getNextMapLevel(values, labels.get(i));
}
values.put(labels.get(labels.size() - 1), observation.getValue());
values.put(labels.get(labels.size() - 1), observation.value());
}
@SuppressWarnings("unchecked")

@ -183,7 +183,7 @@ public class DefaultBlockchain implements MutableBlockchain {
metricsSystem.createGauge(
BLOCKCHAIN,
"difficulty_total",
"difficulty",
"Total difficulty of the chainhead",
() -> this.getChainHead().getTotalDifficulty().toBigInteger().doubleValue());

@ -464,35 +464,6 @@
<sha256 value="463f4f89638aae82c654b3f2842e8d03f7a0d48194c426046dd9c95ce06326ee" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.docker-java" name="docker-java-api" version="3.4.0">
<artifact name="docker-java-api-3.4.0.jar">
<sha256 value="ad8e9f748380985e0b702bfb2356749a0966afa28a7d637aa3211217ae6a6f2e" origin="Generated by Gradle"/>
</artifact>
<artifact name="docker-java-api-3.4.0.pom">
<sha256 value="241a015f07a6044f3e23dd33ad9a9c19a32acaa84a36cbda872d9fb19e4d1f6a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.docker-java" name="docker-java-parent" version="3.4.0">
<artifact name="docker-java-parent-3.4.0.pom">
<sha256 value="80fd330ede28e225aa43dee9de1f06bb2100f4c438de7af2f2c8f706412abbb0" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.docker-java" name="docker-java-transport" version="3.4.0">
<artifact name="docker-java-transport-3.4.0.jar">
<sha256 value="a1a8ce872dbf92423a948443b88b9417283ead5d56cb5ed77803353658b97b34" origin="Generated by Gradle"/>
</artifact>
<artifact name="docker-java-transport-3.4.0.pom">
<sha256 value="56953cb59d2b5f3288d9ba2dbc6545fecb6f4012cc2216f1e4140cf6bd8137d4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.docker-java" name="docker-java-transport-zerodep" version="3.4.0">
<artifact name="docker-java-transport-zerodep-3.4.0.jar">
<sha256 value="aac3ba9ed78c73961d13d7b7dac51ba51fe9089629efd4f77ffa4e6e8c6e4048" origin="Generated by Gradle"/>
</artifact>
<artifact name="docker-java-transport-zerodep-3.4.0.pom">
<sha256 value="63a381030f3d7c7b236ebdb2c49be1c9ed377cb0e4ef1a945f21565abc7c4823" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.jk1" name="gradle-license-report" version="2.9">
<artifact name="gradle-license-report-2.9.jar">
<sha256 value="ebfd6da851654c53216eea9eda1485c12e0cd6de5a9919bf5da9735a021f32af" origin="Generated by Gradle"/>
@ -1924,14 +1895,6 @@
<sha256 value="c8f826b8fcc7066adfd657d871540236a11424c18a4b38746855895f1112ac2a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-all" version="4.1.114.Final">
<artifact name="netty-all-4.1.114.Final.jar">
<sha256 value="bb34d3f8124cc27ed9a8f385ce216f409646ffddd1a9b9b163dec7720c7f849a" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-all-4.1.114.Final.pom">
<sha256 value="cf743ff8a0143fe3fc6a803f0b225014c23b5a2ea25632bf704c07845386d3af" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-all" version="4.1.115.Final">
<artifact name="netty-all-4.1.115.Final.jar">
<sha256 value="c1c3092e02e8037c18d14034919af68d573a71e7118db48129e6f299b9a71adc" origin="Generated by Gradle"/>
@ -1945,11 +1908,6 @@
<sha256 value="2f113902364208bbb32d551e062b05ab219692fbc12bd48de1d83c7576e5b470" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-bom" version="4.1.114.Final">
<artifact name="netty-bom-4.1.114.Final.pom">
<sha256 value="0dfd08abc106f625be1d6eb498fa993de65c8b558070dfb11afa9a3394bb6612" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-bom" version="4.1.115.Final">
<artifact name="netty-bom-4.1.115.Final.pom">
<sha256 value="25dc8bb8337dfc186c49fc8cf4f6a5b6c7cf4146362f5f440dacadcd0df9761b" origin="Generated by Gradle"/>
@ -1960,14 +1918,6 @@
<sha256 value="6afc7e751e0a90d2cb1168f6a4854d80bf3ba888104afab303233cd31684f4b4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-buffer" version="4.1.114.Final">
<artifact name="netty-buffer-4.1.114.Final.jar">
<sha256 value="436ea25725d92c1f590a857d46dc1c8f5c54d7f2775984db2655e5e3bc0d97c9" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-buffer-4.1.114.Final.pom">
<sha256 value="b3f07cf4271ee5a4a459ea8a0d9f4f16911a9355357831d05710ef64f8b8c5ce" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-buffer" version="4.1.115.Final">
<artifact name="netty-buffer-4.1.115.Final.jar">
<sha256 value="4a7b331d3770c566ab70eb02a0d1feed63b95cf6e4d68c8fe778c4c9de2d116d" origin="Generated by Gradle"/>
@ -1976,14 +1926,6 @@
<sha256 value="94a3581ea9e4ffc385bb237960f68ef1e84089f164eae8629e8fd66d8aaf0139" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-codec" version="4.1.114.Final">
<artifact name="netty-codec-4.1.114.Final.jar">
<sha256 value="71d145491456b53212e38e24787bbbed0c77d850f7352e24e535d26a6eea913b" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-codec-4.1.114.Final.pom">
<sha256 value="f55160abec463634c36459914cc60cb59489dcb723e87e9a71ac37bdf3706b84" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-codec" version="4.1.115.Final">
<artifact name="netty-codec-4.1.115.Final.jar">
<sha256 value="cd189afb70ec6eacfcdfdd3a5f472b4e705a5c91d5bd3ef0386421f2ae15ec77" origin="Generated by Gradle"/>
@ -1992,14 +1934,6 @@
<sha256 value="ac166597e81179ca8300276605408910cc030efec12236ce9c38fefc16801aa0" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-codec-dns" version="4.1.114.Final">
<artifact name="netty-codec-dns-4.1.114.Final.jar">
<sha256 value="7b97b856d062e7b2483384223dcee002ad23d526f827991f1140af6302ca120d" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-codec-dns-4.1.114.Final.pom">
<sha256 value="24577daa4d8ab270fd6dc51d1ecca5be383e3df14450d35ae83e048d9981aa32" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-codec-dns" version="4.1.115.Final">
<artifact name="netty-codec-dns-4.1.115.Final.jar">
<sha256 value="23dd6806bcc326855f13e69838c6411d0490e6b1aeb12e217a19a3dd6ad3f10d" origin="Generated by Gradle"/>
@ -2008,14 +1942,6 @@
<sha256 value="73976d702597a4d7597f82634fd17d1161a486972c7306b7e7cccb582533ea4e" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-codec-haproxy" version="4.1.114.Final">
<artifact name="netty-codec-haproxy-4.1.114.Final.jar">
<sha256 value="a96216d347c7219f69b2f4f8743ce0aee6ac750b3691739745ebbcc10eb49528" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-codec-haproxy-4.1.114.Final.pom">
<sha256 value="a399be4607a97549d249ad76f2fcf6e092af63d344e3272efd44df21757ccfe0" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-codec-haproxy" version="4.1.115.Final">
<artifact name="netty-codec-haproxy-4.1.115.Final.jar">
<sha256 value="7514f1d10dda3af9468b57bbb0f63fe0b8abb55dbb8190bede95319f37228322" origin="Generated by Gradle"/>
@ -2029,14 +1955,6 @@
<sha256 value="80372e3e6e61ba8b3abe623964c986376568b6bbec705adb29d2a959cd7b9f8c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-codec-http" version="4.1.114.Final">
<artifact name="netty-codec-http-4.1.114.Final.jar">
<sha256 value="56150ce900f6d931fce37a7fb05d7d75478d6d0b8b556a21781972eb9c3ed7df" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-codec-http-4.1.114.Final.pom">
<sha256 value="e8fe9ebb862f1dd5c3a6be87e851a3b3b28a7eec4af8d70acbecb0c1e816f3ae" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-codec-http" version="4.1.115.Final">
<artifact name="netty-codec-http-4.1.115.Final.jar">
<sha256 value="e6dbe971c59373bbae9802021c63b9bc1d8800fead382863d67e79e79b023166" origin="Generated by Gradle"/>
@ -2050,14 +1968,6 @@
<sha256 value="c7f0325aa4c75c9eae1e388306dc7d10426c6a2a6c8f712f876f58a4bc157ede" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-codec-http2" version="4.1.114.Final">
<artifact name="netty-codec-http2-4.1.114.Final.jar">
<sha256 value="e3e45427a46a8d5b03307a5bcd2eab3706b8b8d903c6a056fa3d1c9bf9738f24" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-codec-http2-4.1.114.Final.pom">
<sha256 value="a856ee4a1cc7d497b664b9125b3c90a690091ff771beca2a9858d51858a75850" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-codec-http2" version="4.1.115.Final">
<artifact name="netty-codec-http2-4.1.115.Final.jar">
<sha256 value="cbed9829a5d582e91e314e209edce9a0c2eb369f23bb4fb74a5bc8b7990222c2" origin="Generated by Gradle"/>
@ -2066,14 +1976,6 @@
<sha256 value="88747b3e38e21cc1f42eaa44d301423482c0fc26c467b3f78fb9edfbde93a3e1" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-codec-memcache" version="4.1.114.Final">
<artifact name="netty-codec-memcache-4.1.114.Final.jar">
<sha256 value="7ef9cfa35408cf43196ad41b7d31517d4a29fbc3c52f387f7c1930ebc02e3c3b" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-codec-memcache-4.1.114.Final.pom">
<sha256 value="9748096cc41e8de6c78d4cea259ea222a7af70756f783b612b83c9f36e62e848" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-codec-memcache" version="4.1.115.Final">
<artifact name="netty-codec-memcache-4.1.115.Final.jar">
<sha256 value="c44561d5cdb0e28e63b00dccb51c8f293c8b23012e0299042804e054e2b05fab" origin="Generated by Gradle"/>
@ -2082,14 +1984,6 @@
<sha256 value="cfcc9845b932975dfc13be8c19260fda48d0646003de0e017964bdcde24d37c6" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-codec-mqtt" version="4.1.114.Final">
<artifact name="netty-codec-mqtt-4.1.114.Final.jar">
<sha256 value="4e5d5e05a26399f8431beb61fb4860b554938bbfba9d633cfb8c69444a089cc8" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-codec-mqtt-4.1.114.Final.pom">
<sha256 value="e31ab2e3030a3a3b10ab85abe6547f9c214de55d5b69251dee76367c6bf6ab98" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-codec-mqtt" version="4.1.115.Final">
<artifact name="netty-codec-mqtt-4.1.115.Final.jar">
<sha256 value="54202f894eea7f235b8f9032b32a09396bbf92a68cb32a486981ced96eb85222" origin="Generated by Gradle"/>
@ -2098,14 +1992,6 @@
<sha256 value="dc966366eac6d123a56357a34495400435ed2c48f9bab5b363d648ddb525abba" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-codec-redis" version="4.1.114.Final">
<artifact name="netty-codec-redis-4.1.114.Final.jar">
<sha256 value="8b31e03a59c4d460f9f6306a6b8ef1ee856f93a24ed94f8e47fc2cd0d77ffd0d" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-codec-redis-4.1.114.Final.pom">
<sha256 value="520a82274a95583df659bb7b2a70ad8864396f02e6fefcd989f688f18f5748c9" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-codec-redis" version="4.1.115.Final">
<artifact name="netty-codec-redis-4.1.115.Final.jar">
<sha256 value="eddc930ecb65055a202b4d959f7b6636085a468d45daf283725037d466fa4de1" origin="Generated by Gradle"/>
@ -2114,14 +2000,6 @@
<sha256 value="3afc90b1246694810d9da24e60a057cc2db5e44f20d820dfee67357d621a2975" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-codec-smtp" version="4.1.114.Final">
<artifact name="netty-codec-smtp-4.1.114.Final.jar">
<sha256 value="78285748b6e50b2275e32579353638424afdc117e3919c9a01fd8deb4974a6d8" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-codec-smtp-4.1.114.Final.pom">
<sha256 value="20219aaaaf7a534b5f0d142ca39f83234273069eb6f59779818e94f4c3630b1c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-codec-smtp" version="4.1.115.Final">
<artifact name="netty-codec-smtp-4.1.115.Final.jar">
<sha256 value="caa622c91fae41a0396b576c3803955371ae9ccde3ba6bf46117f4b641d0f737" origin="Generated by Gradle"/>
@ -2130,14 +2008,6 @@
<sha256 value="f249c4744b45bea1147755c57a98d093b65925d684a249e8ec06d7bb360f687d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-codec-socks" version="4.1.114.Final">
<artifact name="netty-codec-socks-4.1.114.Final.jar">
<sha256 value="2e48cb5af7cae82acbb8dcb7b0c9d67b9dddff3d6dd262cb8911fd4b5fd62ee2" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-codec-socks-4.1.114.Final.pom">
<sha256 value="374f2d39e95f0106a22d4e4c42c5ed6f377027a61c6e8f1bf72b0fb12823da06" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-codec-socks" version="4.1.115.Final">
<artifact name="netty-codec-socks-4.1.115.Final.jar">
<sha256 value="e9b1cc744dc6195894450b1fd4d271a821ab167fe21ae3c459b27cdadc70e81f" origin="Generated by Gradle"/>
@ -2146,14 +2016,6 @@
<sha256 value="08b3c1acc77abdbadeef08c8cdf080e1bcceffe5f84751f60d89fc0bcbaaa2fc" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-codec-stomp" version="4.1.114.Final">
<artifact name="netty-codec-stomp-4.1.114.Final.jar">
<sha256 value="9b63156b727843485b137c87e2c7e4c7db2690f5ca93443080711a1804d685cf" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-codec-stomp-4.1.114.Final.pom">
<sha256 value="d4e8f0900d3dd2e6d9f74bbae8b4b427c21c8b18f388a41b0205ae7f0335ac20" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-codec-stomp" version="4.1.115.Final">
<artifact name="netty-codec-stomp-4.1.115.Final.jar">
<sha256 value="9470312b5e263595a4d0a6a3cb3e64e5d11e87285fbfc267b845118d1c26fc7a" origin="Generated by Gradle"/>
@ -2162,14 +2024,6 @@
<sha256 value="394fd6bdf7145aa180548d46f3297ed6a9e6a4a073fe0f743ca4df8217b1990a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-codec-xml" version="4.1.114.Final">
<artifact name="netty-codec-xml-4.1.114.Final.jar">
<sha256 value="3a2055a9b38b603ea9fcbfbe891172daad60df99aecd99161f1c8adbedc95522" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-codec-xml-4.1.114.Final.pom">
<sha256 value="e5fcbcb730801bf08dc5b8575b2545766a61331e0e285d05fd805602564deb5f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-codec-xml" version="4.1.115.Final">
<artifact name="netty-codec-xml-4.1.115.Final.jar">
<sha256 value="e1c5f043f027ca5f853076df16c233665011be60a81f5a6f7336093790a9dced" origin="Generated by Gradle"/>
@ -2183,14 +2037,6 @@
<sha256 value="b9c78337616bf8d54d5515bab6cba1df2db46fdb01b412434cc7fbafe78345c5" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-common" version="4.1.114.Final">
<artifact name="netty-common-4.1.114.Final.jar">
<sha256 value="d6b053b3b27cc568207f254902dcb6f95dd238c1b9d55ef719d2c4f8eb476223" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-common-4.1.114.Final.pom">
<sha256 value="3c2e7d18133ac6d8e4f442cc5fa5fe0492f5cbf878bf0f7157c6d410da719f5d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-common" version="4.1.115.Final">
<artifact name="netty-common-4.1.115.Final.jar">
<sha256 value="39f1b5a2aaa4eab5d036dfd0486e35a4276df412e092d36b2d88b494705a134d" origin="Generated by Gradle"/>
@ -2204,14 +2050,6 @@
<sha256 value="f575d446fa5cd546be4fce0bbf34c144041761b89da77971e30bf83d42905d1b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-handler" version="4.1.114.Final">
<artifact name="netty-handler-4.1.114.Final.jar">
<sha256 value="57be25ec6c8fa7052fe90119373d8bc979cd37fa0070a135c0e69f5f8e0ddad0" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-handler-4.1.114.Final.pom">
<sha256 value="694fe6fb8dfe9148a2326e77f56141fcebbccc2cb8b29f285776796164ec6a93" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-handler" version="4.1.115.Final">
<artifact name="netty-handler-4.1.115.Final.jar">
<sha256 value="5972028cc863b74927ce0d11fb8d58f65da2560bef5602fe8ce8903bd306ca07" origin="Generated by Gradle"/>
@ -2225,14 +2063,6 @@
<sha256 value="8577aa45f16f435b12420a37b8c8d731df11d4dcb7e132c00fc588946274ed89" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-handler-proxy" version="4.1.114.Final">
<artifact name="netty-handler-proxy-4.1.114.Final.jar">
<sha256 value="35555b41624c8384de773bc3d17eb2447f5449842119db0435a366372aceefd1" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-handler-proxy-4.1.114.Final.pom">
<sha256 value="6cdaeb2a8f240fa1b72382476e2776969535678203d9902f3e63ef586ac0dbb7" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-handler-proxy" version="4.1.115.Final">
<artifact name="netty-handler-proxy-4.1.115.Final.jar">
<sha256 value="807e67cfb17136927d11db42df62031169d1fa0883e13f254906994c84ffbe87" origin="Generated by Gradle"/>
@ -2241,14 +2071,6 @@
<sha256 value="f9936b2da7adcecef5c4ffad772067b7de5d0e359b83e6fd39b4a49d11706a10" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-handler-ssl-ocsp" version="4.1.114.Final">
<artifact name="netty-handler-ssl-ocsp-4.1.114.Final.jar">
<sha256 value="c97e307e99bb9555adae0f01ddcd690965408aa1b653122fc7a02030dd73d8f7" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-handler-ssl-ocsp-4.1.114.Final.pom">
<sha256 value="d217e882bd1fa6fbc6bfa3966b68fcade287ec6806c26ab9b0d80d4fc9daa694" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-handler-ssl-ocsp" version="4.1.115.Final">
<artifact name="netty-handler-ssl-ocsp-4.1.115.Final.jar">
<sha256 value="d958f728bffe9a2f23f32a70917a2842feb4fac59baec581f89411c734b59cfe" origin="Generated by Gradle"/>
@ -2262,11 +2084,6 @@
<sha256 value="96b055ae7af7f1e32db3d7261918d900fba15e1e589ccd682f9cfd5744249a51" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-parent" version="4.1.114.Final">
<artifact name="netty-parent-4.1.114.Final.pom">
<sha256 value="a1c8a553ae7e43bf0ed986d22f0379dcf0c2fb7fc3971dc6057ed5c9d04c5a68" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-parent" version="4.1.115.Final">
<artifact name="netty-parent-4.1.115.Final.pom">
<sha256 value="d832942b8e2a71c2ccfdd0247a8b840105ce40aaeec4b8de36358f28d93941e3" origin="Generated by Gradle"/>
@ -2277,14 +2094,6 @@
<sha256 value="d7524053459d425c9cbbbf93f0793504c4d0148c75fb49daab3e14d4e4d38ef0" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-resolver" version="4.1.114.Final">
<artifact name="netty-resolver-4.1.114.Final.jar">
<sha256 value="19661e7f1dbdee97fe99a227fbed0696d29c3cdf3f8f2d9839a790695c2bf0ac" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-resolver-4.1.114.Final.pom">
<sha256 value="86843905401f6605d575f4858b58c1eadcf1e3c0bccd417787cb0ad0621cc120" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-resolver" version="4.1.115.Final">
<artifact name="netty-resolver-4.1.115.Final.jar">
<sha256 value="7b3455d14f59828765a00573bc3967dc59379e874bd62a67eb1926d6512109d1" origin="Generated by Gradle"/>
@ -2298,14 +2107,6 @@
<sha256 value="9d4607157d264015d8e8ae825d49fb8d8bf48fe227de99d82fb5570ac519c70a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-resolver-dns" version="4.1.114.Final">
<artifact name="netty-resolver-dns-4.1.114.Final.jar">
<sha256 value="e29ab20626fee613cd44ab794dd80e4e7ed50551815ff2dc31e60ab473459f10" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-resolver-dns-4.1.114.Final.pom">
<sha256 value="ed126dff491fe7175d0069141ca53ecb594c486c290c2ee13e99d25d72456fb4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-resolver-dns" version="4.1.115.Final">
<artifact name="netty-resolver-dns-4.1.115.Final.jar">
<sha256 value="4aca31593e5896c64ab7e041bbc6c0d851bd9634ec3a4354208141a35576619f" origin="Generated by Gradle"/>
@ -2314,14 +2115,6 @@
<sha256 value="8d1e3143825e0244e1dd614b2340deb00f4ee07ef615faa855bd195100776789" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-resolver-dns-classes-macos" version="4.1.114.Final">
<artifact name="netty-resolver-dns-classes-macos-4.1.114.Final.jar">
<sha256 value="2bc4078cf269a99e09f28738d6022c385a31f4321e28115bae752ce16ce99618" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-resolver-dns-classes-macos-4.1.114.Final.pom">
<sha256 value="e530a68c73ea6f1c0a78c616ff29ce0a7a7a22f86ebcef27fa02965e6acbd660" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-resolver-dns-classes-macos" version="4.1.115.Final">
<artifact name="netty-resolver-dns-classes-macos-4.1.115.Final.jar">
<sha256 value="5fbac6346b3fc2fbdce7f7f24bb96a1b3190aa14d50021303a0a4437f3d373bc" origin="Generated by Gradle"/>
@ -2330,17 +2123,6 @@
<sha256 value="221aabb31073eea980d9de5a00a5b74ddaa99cccae638ad485dce8897d880c72" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-resolver-dns-native-macos" version="4.1.114.Final">
<artifact name="netty-resolver-dns-native-macos-4.1.114.Final-osx-aarch_64.jar">
<sha256 value="c63ffafc1becb7e1dca29c89b1c69ea74cc62abe3fcc3184ea7f089c76bac643" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-resolver-dns-native-macos-4.1.114.Final-osx-x86_64.jar">
<sha256 value="a8ccb464f93d10bd70c0b1fbc96835cfa234623f353f880edc7a76d3115509a4" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-resolver-dns-native-macos-4.1.114.Final.pom">
<sha256 value="710dbb92f3edbe204aea06dada1671f2382cd369f0ea47e6d5e03d0aa684929c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-resolver-dns-native-macos" version="4.1.115.Final">
<artifact name="netty-resolver-dns-native-macos-4.1.115.Final-osx-aarch_64.jar">
<sha256 value="a386a63e9c3d5a66130d1eebe9818820ca04d79769ab18b65d934e6e3a74e4e5" origin="Generated by Gradle"/>
@ -2357,14 +2139,6 @@
<sha256 value="ba89f115a8c30bdbcb6dba36849a047da803b496f3e856e4365d53df1403932d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-tcnative-boringssl-static" version="2.0.66.Final">
<artifact name="netty-tcnative-boringssl-static-2.0.66.Final.jar">
<sha256 value="df215103b6082caceef6b83ed5bbf61d2072688b8b248e9d86cc0bbdb785b5e4" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-tcnative-boringssl-static-2.0.66.Final.pom">
<sha256 value="7ea6c8ee65df3595dfc044ed2e95614acecab6da0b348b7a1e9361cba44fce2f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-tcnative-boringssl-static" version="2.0.69.Final">
<artifact name="netty-tcnative-boringssl-static-2.0.69.Final.jar">
<sha256 value="79dbae7f0c2a1f73a6b4bb992edb0ab94ebd3196f5e83a94784039fb96d9a040" origin="Generated by Gradle"/>
@ -2373,14 +2147,6 @@
<sha256 value="d55ff689622b75dfeca232da2a98cefa1773b7717a318be18113ebf718d232f8" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-tcnative-classes" version="2.0.66.Final">
<artifact name="netty-tcnative-classes-2.0.66.Final.jar">
<sha256 value="669a811a193dc1e7c9ef86cb547a4ab92f0f34cce8f9b842b9029bf5cfa07cc5" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-tcnative-classes-2.0.66.Final.pom">
<sha256 value="4942cd02e2916c1fb4f78cca8841f0f7219bb90f828ab7489a89027c2efa91eb" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-tcnative-classes" version="2.0.69.Final">
<artifact name="netty-tcnative-classes-2.0.69.Final.jar">
<sha256 value="0bbc2848cb099eb3f6f4ec36501b3600e1828457cda41d5330687f601ee04bef" origin="Generated by Gradle"/>
@ -2389,11 +2155,6 @@
<sha256 value="59da0f949e2bee89f5711eb3e0c6fd8cf7914e6b70c007f36b9eb0688243015c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-tcnative-parent" version="2.0.66.Final">
<artifact name="netty-tcnative-parent-2.0.66.Final.pom">
<sha256 value="2b017fb3c1deeab5d1eaebec134659b7deae78c14d5ebfc3d49c0b3084ea7365" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-tcnative-parent" version="2.0.69.Final">
<artifact name="netty-tcnative-parent-2.0.69.Final.pom">
<sha256 value="2953027196637610c894f32bda705093cd8768315c9f1a573951a75f17869afc" origin="Generated by Gradle"/>
@ -2404,14 +2165,6 @@
<sha256 value="8662bd5d7f62d372548f40ad896b87ea55c2b1d179029ef985837dce43a52269" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-transport" version="4.1.114.Final">
<artifact name="netty-transport-4.1.114.Final.jar">
<sha256 value="2a8609fe6a8b4c9d5965c6b901777b4bd0b26600647ee2aa7d4d93f4d5c780de" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-transport-4.1.114.Final.pom">
<sha256 value="9451b10917afd228c5892541d94acc3d6814c8d73589c4050c779a921fab9c82" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-transport" version="4.1.115.Final">
<artifact name="netty-transport-4.1.115.Final.jar">
<sha256 value="c3d71faaa736ffd2c9260ab0b498024b814c39c7d764bea8113fa98de6e2bdd2" origin="Generated by Gradle"/>
@ -2420,14 +2173,6 @@
<sha256 value="9677471c5409adced1f6cdb65a3ad8015e2e970990aba497a4eeca18abc363f9" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-transport-classes-epoll" version="4.1.114.Final">
<artifact name="netty-transport-classes-epoll-4.1.114.Final.jar">
<sha256 value="a90b4277df568be0562e08271060a28870c57ba5a91fe792057f11e986fe777e" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-transport-classes-epoll-4.1.114.Final.pom">
<sha256 value="66c775019d67b3a40a6880265b90dc88b01660c53db7ae2f65cfea5e74994715" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-transport-classes-epoll" version="4.1.115.Final">
<artifact name="netty-transport-classes-epoll-4.1.115.Final.jar">
<sha256 value="40aa67b4463cca0ab346e393c87f6c37e8954d18ec8b78567d95b55aa1f2b3aa" origin="Generated by Gradle"/>
@ -2436,14 +2181,6 @@
<sha256 value="e57cc702f24b733565741ce3a69d89a2b5f5dcbda34c52b779fd02ba68c40987" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-transport-classes-kqueue" version="4.1.114.Final">
<artifact name="netty-transport-classes-kqueue-4.1.114.Final.jar">
<sha256 value="8a07fed5985502dd68936f70b0dd5639d2e69c257c2300610936ce0e6da9847d" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-transport-classes-kqueue-4.1.114.Final.pom">
<sha256 value="90cbf66fe7c2a185aaf2dc28b0136f8ef8ba8eb51be1c319bcbd7f7372af09f5" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-transport-classes-kqueue" version="4.1.115.Final">
<artifact name="netty-transport-classes-kqueue-4.1.115.Final.jar">
<sha256 value="77d3a0930f86870b48e668635e214e0c81205f90f2012dc70c8417b59a839d04" origin="Generated by Gradle"/>
@ -2457,23 +2194,6 @@
<sha256 value="c14b3e677409871ae1cb80436dd06945591ae201ce3cc96db6a52957c7b9e574" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-transport-native-epoll" version="4.1.114.Final">
<artifact name="netty-transport-native-epoll-4.1.114.Final-linux-aarch_64.jar">
<sha256 value="fba192991a1179f4f04c82d69a43b06136216eac5974f8a30881887058fa66ad" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-transport-native-epoll-4.1.114.Final-linux-riscv64.jar">
<sha256 value="e0b6914ed5333aa0e2b21d360e6eeee962629c13eece9f7ea8674ce36fa539f7" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-transport-native-epoll-4.1.114.Final-linux-x86_64.jar">
<sha256 value="798713e4135de9bab7e4bd03a87b06e972e88d4cf4a1f951bbb2a3ea39ccca38" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-transport-native-epoll-4.1.114.Final.jar">
<sha256 value="d8991b8b096ea0f04d87fe3ade94804bbf6bce1e6019f886b7f71504741ba96f" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-transport-native-epoll-4.1.114.Final.pom">
<sha256 value="92d4dee865dc002fef64e19e602b6f6616667da22568869fb1135715a0344bf8" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-transport-native-epoll" version="4.1.115.Final">
<artifact name="netty-transport-native-epoll-4.1.115.Final-linux-aarch_64.jar">
<sha256 value="82051543cbb328b5b45803229ccddf921f3e1897f9d75358ea76143a65a82e48" origin="Generated by Gradle"/>
@ -2496,20 +2216,6 @@
<sha256 value="d1bf448ced9703e41e63f539f8523246f2a163434ddf4330d213262e731d5707" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-transport-native-kqueue" version="4.1.114.Final">
<artifact name="netty-transport-native-kqueue-4.1.114.Final-osx-aarch_64.jar">
<sha256 value="68b6ddfe5b37b4024c2543460a165c034a91cc3215324d4e47a1308ea3eb4046" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-transport-native-kqueue-4.1.114.Final-osx-x86_64.jar">
<sha256 value="5a38fc4ebf199077c4925fa5253ce6ef789baa54210a035091902f0607245a19" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-transport-native-kqueue-4.1.114.Final.jar">
<sha256 value="643b1bd9b3f74eea5411f42875bdc64fb82b3a9413d66cea2ae46620073f5780" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-transport-native-kqueue-4.1.114.Final.pom">
<sha256 value="0ca766af3bb08dfa44747c7cac67b408d5b6cc1fed051febc1b423edc077afb7" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-transport-native-kqueue" version="4.1.115.Final">
<artifact name="netty-transport-native-kqueue-4.1.115.Final-osx-aarch_64.jar">
<sha256 value="bd71c4e8f14098057963a0342ba0ce2e39592d9558f1cd0cfdea81f9686901f0" origin="Generated by Gradle"/>
@ -2529,14 +2235,6 @@
<sha256 value="2ea70a1864ab38fcec2bdc28c69ee9756dbad58a4626c664554d3c860d540981" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-transport-native-unix-common" version="4.1.114.Final">
<artifact name="netty-transport-native-unix-common-4.1.114.Final.jar">
<sha256 value="fd64c07c9e068f80dc271f6277278246328a171be669abdfe0bc8b2226d980de" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-transport-native-unix-common-4.1.114.Final.pom">
<sha256 value="832b43c284e2c0cf0f57d62928fa72967b6aa6e5dc8766c6c9b2d01fddf7951d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-transport-native-unix-common" version="4.1.115.Final">
<artifact name="netty-transport-native-unix-common-4.1.115.Final.jar">
<sha256 value="4b03e716272657c296b0204b57c140b2b2ca96b1a746c92da41f595892ec6d88" origin="Generated by Gradle"/>
@ -2545,14 +2243,6 @@
<sha256 value="96075271c578faadffec6f133991ea30884a69a41e07245a5ff8d5e7e5dd9f07" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-transport-rxtx" version="4.1.114.Final">
<artifact name="netty-transport-rxtx-4.1.114.Final.jar">
<sha256 value="8927af869c006d73921d9db739d747f82fe5568c82ad8c1982fef954c5ecb63d" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-transport-rxtx-4.1.114.Final.pom">
<sha256 value="b82852b37ecedf8342eeee3cd69aae7658e0ed485ef7c2b0f2ba384dd223dd17" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-transport-rxtx" version="4.1.115.Final">
<artifact name="netty-transport-rxtx-4.1.115.Final.jar">
<sha256 value="33e80b3c468be74f448416d59edbf4618ea4917d697a3cbc4a53e13516bfd5ed" origin="Generated by Gradle"/>
@ -2561,14 +2251,6 @@
<sha256 value="38713e766cdf792a6d309adaba05193419a1ee8f1aea63a68cdabfe736bf5acd" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-transport-sctp" version="4.1.114.Final">
<artifact name="netty-transport-sctp-4.1.114.Final.jar">
<sha256 value="5db192fb9c23615cf82862888c192f3ec98bae26410e1ac7ee40ecebe208e181" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-transport-sctp-4.1.114.Final.pom">
<sha256 value="95b0121474bc8938d7817cd969d345e04021cd77d60d7b3e9f5e875b426336bd" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-transport-sctp" version="4.1.115.Final">
<artifact name="netty-transport-sctp-4.1.115.Final.jar">
<sha256 value="016e8596b4b2357c2671a8691cafaaab7650689d01715cda2a0b85631f22f3c6" origin="Generated by Gradle"/>
@ -2577,14 +2259,6 @@
<sha256 value="844fdde08ab3dd22f5cac9e6f211f29a75caa8d80d986e5f5d459f95eab06d26" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-transport-udt" version="4.1.114.Final">
<artifact name="netty-transport-udt-4.1.114.Final.jar">
<sha256 value="2431ecd15cde787019de5aa0a48063e6d72758ff75e67ed5d0009f470f559009" origin="Generated by Gradle"/>
</artifact>
<artifact name="netty-transport-udt-4.1.114.Final.pom">
<sha256 value="faff00ad0c07c5316ce118b2a36276e03bb00feb2b86f4e80a351f417392cd1b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.netty" name="netty-transport-udt" version="4.1.115.Final">
<artifact name="netty-transport-udt-4.1.115.Final.jar">
<sha256 value="c505809dbb2d1b159eb1732dfa10c68f05da41ea9a479416690ff9a76908e4fb" origin="Generated by Gradle"/>
@ -2636,6 +2310,22 @@
<sha256 value="16284ec312a8398560d75e0aa48053c1764d164eb0d777fc0440b9b36c4eb4a1" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.opentelemetry" name="opentelemetry-bom" version="1.44.1">
<artifact name="opentelemetry-bom-1.44.1.module">
<sha256 value="325613776fb98e86c7c53a13b6d61fcede7f2584f7725ded6d84d1bdf458656d" origin="Generated by Gradle"/>
</artifact>
<artifact name="opentelemetry-bom-1.44.1.pom">
<sha256 value="185c9efbbce4a7e59667078812a215554ed514b17dc1e319bfbc22bc011e62d7" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.opentelemetry" name="opentelemetry-bom-alpha" version="1.44.1-alpha">
<artifact name="opentelemetry-bom-alpha-1.44.1-alpha.module">
<sha256 value="3bb69ad24a2ac599738138c7e7af71df0076f9ded15a176e78c9d0afdee67098" origin="Generated by Gradle"/>
</artifact>
<artifact name="opentelemetry-bom-alpha-1.44.1-alpha.pom">
<sha256 value="3347fb4d7a7ccb56def084d60b6416aead7360bcba57b8729dc3ef7e3db9d442" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.opentelemetry" name="opentelemetry-context" version="1.43.0">
<artifact name="opentelemetry-context-1.43.0.jar">
<sha256 value="83d54bed8a7aa74a8648d43974c743f470c9ceab3dea57f26a938667a2dc0160" origin="Generated by Gradle"/>
@ -2803,6 +2493,22 @@
<sha256 value="cbab9b04189b0ddf7654f9029b151e8fb9bf78d906ad1fe4393510b76c8e74c8" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.opentelemetry.instrumentation" name="opentelemetry-instrumentation-bom" version="2.10.0">
<artifact name="opentelemetry-instrumentation-bom-2.10.0.module">
<sha256 value="f7881be9e9c8f20cbccff71684ec2cc054bf12adf46dc671bb4e290bbd286b05" origin="Generated by Gradle"/>
</artifact>
<artifact name="opentelemetry-instrumentation-bom-2.10.0.pom">
<sha256 value="f785b7118ab4088d01662e73977f29a1495634caab1bb3c687f8fa00d2de3f96" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.opentelemetry.instrumentation" name="opentelemetry-instrumentation-bom-alpha" version="2.10.0-alpha">
<artifact name="opentelemetry-instrumentation-bom-alpha-2.10.0-alpha.module">
<sha256 value="f4f66eb95040a8a1ad0a1f55d0fd8d36fea9365ed577c29803402245ccd3c67e" origin="Generated by Gradle"/>
</artifact>
<artifact name="opentelemetry-instrumentation-bom-alpha-2.10.0-alpha.pom">
<sha256 value="5d7a99d3acddcdaf98abbaa5325324cbcdb8fd773d2e39c76a4319f6f2a6520a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.opentelemetry.instrumentation" name="opentelemetry-okhttp-3.0" version="2.9.0-alpha">
<artifact name="opentelemetry-okhttp-3.0-2.9.0-alpha.jar">
<sha256 value="9e44c7a49b7d6afa762ad0c11c1d4fdc68062b69550c8d8821185dc8951e6400" origin="Generated by Gradle"/>
@ -2923,11 +2629,138 @@
<sha256 value="5bf70db179616d067d416f5a779be9fc9e7cd736cfdb2bd82fb9eb245d44b150" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="client_java" version="1.3.4">
<artifact name="client_java-1.3.4.pom">
<sha256 value="5f8bc032a8fe3bc8e657dd6a3ab11b73a022e097a32337d1f28b2983b16f4f59" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="parent" version="0.16.0">
<artifact name="parent-0.16.0.pom">
<sha256 value="722b55119097b04d711479dfb60dd54b27b5920a1aeb7702027c44a215ffc596" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="prometheus-metrics-bom" version="1.3.4">
<artifact name="prometheus-metrics-bom-1.3.4.pom">
<sha256 value="6f2edcab929ea7862e3d755ab35527595c6d025f1d5e94ac4ac9c4ae268b7308" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="prometheus-metrics-config" version="1.3.4">
<artifact name="prometheus-metrics-config-1.3.4.jar">
<sha256 value="59a8736700c7618ab30469ef23d4d09c5a024e4c8c5bd2034e66e563e0ba4735" origin="Generated by Gradle"/>
</artifact>
<artifact name="prometheus-metrics-config-1.3.4.pom">
<sha256 value="c1916224973a819cd62d42f9c9dbcf7ac030afbc65efa0fc55f7f6c339bebc7f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="prometheus-metrics-core" version="1.3.4">
<artifact name="prometheus-metrics-core-1.3.4.jar">
<sha256 value="c046b237c30cd9172e14d81283090a7ef5f805113ae35246cd20a4b5691b076e" origin="Generated by Gradle"/>
</artifact>
<artifact name="prometheus-metrics-core-1.3.4.pom">
<sha256 value="85d17e5b45b5dc4d6f106873793463bd59fe12b8b059f502e2cb9eecff6bfaae" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="prometheus-metrics-exporter-common" version="1.3.4">
<artifact name="prometheus-metrics-exporter-common-1.3.4.jar">
<sha256 value="3c95789e5c43d90658d976274aad34b8132392a24ee0777324eb70b71ed3e408" origin="Generated by Gradle"/>
</artifact>
<artifact name="prometheus-metrics-exporter-common-1.3.4.pom">
<sha256 value="7a261664066f01f3092f84881292f7f266d5cdfbf376aa6b85d61a66db76f225" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="prometheus-metrics-exporter-httpserver" version="1.3.4">
<artifact name="prometheus-metrics-exporter-httpserver-1.3.4.jar">
<sha256 value="e4580db8417394b86946793e2450572c411fbb034722acbf684dee75b8cda052" origin="Generated by Gradle"/>
</artifact>
<artifact name="prometheus-metrics-exporter-httpserver-1.3.4.pom">
<sha256 value="faa4a4ba55771920ae83c8982d8a0977aed3649b0a503c5ce0c864acb78bde64" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="prometheus-metrics-exporter-pushgateway" version="1.3.4">
<artifact name="prometheus-metrics-exporter-pushgateway-1.3.4.jar">
<sha256 value="2955047d79335aa0e9d922587ec8b6ac1cc6b52a08ecf6f75fc37d5ea2c55568" origin="Generated by Gradle"/>
</artifact>
<artifact name="prometheus-metrics-exporter-pushgateway-1.3.4.pom">
<sha256 value="8328d9c944ecaeb9a3d5a1daf968d0a036ccb04454a828a254ffff3e2f05fe0b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="prometheus-metrics-exposition-formats" version="1.3.4">
<artifact name="prometheus-metrics-exposition-formats-1.3.4.jar">
<sha256 value="afcbce74029d4f77b6816004acfbb85b3382664a1cd7a462bb7718a1ad9dc134" origin="Generated by Gradle"/>
</artifact>
<artifact name="prometheus-metrics-exposition-formats-1.3.4.pom">
<sha256 value="20f9c6ab1697f787d09737bb4587ae9f012034145b8f4a94d77c03ce9c8a396d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="prometheus-metrics-exposition-textformats" version="1.3.4">
<artifact name="prometheus-metrics-exposition-textformats-1.3.4.jar">
<sha256 value="7fbb160e591fcbb1f95ab191f5c4b7159aa7396f72a21f4295e8fd2ea70eb419" origin="Generated by Gradle"/>
</artifact>
<artifact name="prometheus-metrics-exposition-textformats-1.3.4.pom">
<sha256 value="fb89c867af52b9d524cbbad604cbc9761e94c9242afaeac3dab3643cf24ec820" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="prometheus-metrics-instrumentation-guava" version="1.3.4">
<artifact name="prometheus-metrics-instrumentation-guava-1.3.4.jar">
<sha256 value="f2c4a23722d7bed640aad79a2c2503e7054cb3bfe3ac4f38b2524ffb666aa560" origin="Generated by Gradle"/>
</artifact>
<artifact name="prometheus-metrics-instrumentation-guava-1.3.4.pom">
<sha256 value="697022a098dafbfa73cf331be98d79f9287e35299cec03f3b5fcd0951013d179" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="prometheus-metrics-instrumentation-jvm" version="1.3.4">
<artifact name="prometheus-metrics-instrumentation-jvm-1.3.4.jar">
<sha256 value="cb0216eec70b6e040f511261adf7dbd77978ce0366a8a63e38763191e68948b6" origin="Generated by Gradle"/>
</artifact>
<artifact name="prometheus-metrics-instrumentation-jvm-1.3.4.pom">
<sha256 value="fbb5e2b323602005ce4234fad9ec2e7a9e222403aec3afd98353816ce692f806" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="prometheus-metrics-model" version="1.3.4">
<artifact name="prometheus-metrics-model-1.3.4.jar">
<sha256 value="5e29e9700e95ec4d69c48b411513362d665e1200a222d601bfa6f72800fac7d4" origin="Generated by Gradle"/>
</artifact>
<artifact name="prometheus-metrics-model-1.3.4.pom">
<sha256 value="385beb4a2b4c2829945e165ce4b0a1a476a3860036823771cabcc1efdc3799f5" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="prometheus-metrics-tracer" version="1.3.4">
<artifact name="prometheus-metrics-tracer-1.3.4.pom">
<sha256 value="1472bac7e86837ffddfc6f2032f345aa95c5108e3fd34d79d67bd1a64f6e3662" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="prometheus-metrics-tracer-common" version="1.3.4">
<artifact name="prometheus-metrics-tracer-common-1.3.4.jar">
<sha256 value="66a0563d42633a42933c1e54e7e927661e882df8ee35b21024ef081c45763611" origin="Generated by Gradle"/>
</artifact>
<artifact name="prometheus-metrics-tracer-common-1.3.4.pom">
<sha256 value="3cbd56e48a813a3ff5d54fb0f474e44014f92eefc5191efbe08600bf2bba5e8e" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="prometheus-metrics-tracer-initializer" version="1.3.4">
<artifact name="prometheus-metrics-tracer-initializer-1.3.4.jar">
<sha256 value="f5d06533d60b27a54b050cebb9ffaf7f3028c059b306f279090101cc4c944aa9" origin="Generated by Gradle"/>
</artifact>
<artifact name="prometheus-metrics-tracer-initializer-1.3.4.pom">
<sha256 value="51187447c1eb2e5ea2623a8451e379af42e27f4c0b5079a75af86b75748c5da0" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="prometheus-metrics-tracer-otel" version="1.3.4">
<artifact name="prometheus-metrics-tracer-otel-1.3.4.jar">
<sha256 value="e1a22acb39f09dd96af8d330074f7d685840663ecbda83fde5903c62c15ecb21" origin="Generated by Gradle"/>
</artifact>
<artifact name="prometheus-metrics-tracer-otel-1.3.4.pom">
<sha256 value="3388537f652d0b7f5feb16abee5a7c3a7002a1d4dc52ac5fc8d8463190fd3654" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="prometheus-metrics-tracer-otel-agent" version="1.3.4">
<artifact name="prometheus-metrics-tracer-otel-agent-1.3.4.jar">
<sha256 value="e7e191623273dfd5e6a6e9806e951a4facdc82e2fe426b6c3b27720677b41774" origin="Generated by Gradle"/>
</artifact>
<artifact name="prometheus-metrics-tracer-otel-agent-1.3.4.pom">
<sha256 value="13b9bbab7d6a2ca4425ea9886a8a00067bf184a5d2170d84a0f2c19f9d93be6e" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="simpleclient" version="0.16.0">
<artifact name="simpleclient-0.16.0.jar">
<sha256 value="22c374f237f7bc4fdb1f0ec2da379c0ba00e69ad2ffe8b6ade543d73062d377f" origin="Generated by Gradle"/>
@ -2936,11 +2769,6 @@
<sha256 value="fec080d07ab15875d971c4ae81f951da61b5cff9991cf50e530ba8fca4770973" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="simpleclient_bom" version="0.16.0">
<artifact name="simpleclient_bom-0.16.0.pom">
<sha256 value="af441d3295de11a70e35febeb3d919ba2ed17552753163325b954d53594d55c3" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="simpleclient_common" version="0.16.0">
<artifact name="simpleclient_common-0.16.0.jar">
<sha256 value="eba6ec26ce7e40cbb8725e05fb83247a9f4f44945b9e7522e3375dde67b9f059" origin="Generated by Gradle"/>
@ -2949,22 +2777,6 @@
<sha256 value="77f01109ce1507bd7443e2272737674b27524faacb0dcb96e3b8ede0b6ab9eb6" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="simpleclient_guava" version="0.16.0">
<artifact name="simpleclient_guava-0.16.0.jar">
<sha256 value="49959fe20423803b8958fe35ce6cdcc47e58e2251b191ad53eb7ef6fc46c4ae1" origin="Generated by Gradle"/>
</artifact>
<artifact name="simpleclient_guava-0.16.0.pom">
<sha256 value="1d42aa72798870f20e997ae6c4d14ec3b26b3ef715602b2a8f32404e3657bdb1" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="simpleclient_hotspot" version="0.16.0">
<artifact name="simpleclient_hotspot-0.16.0.jar">
<sha256 value="134f156cfeb44cbd3864065806ef5db5543c68af578d1d7ee59283e2e9853f73" origin="Generated by Gradle"/>
</artifact>
<artifact name="simpleclient_hotspot-0.16.0.pom">
<sha256 value="d216937de723120fb7a4c88b92c5bea1911afb88ba76d0d48f1769ad85b675e9" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="simpleclient_httpserver" version="0.16.0">
<artifact name="simpleclient_httpserver-0.16.0.jar">
<sha256 value="cab87de10b6a474151cceebc3b634329a973ffb602ce6efef2c0fd97a90365c8" origin="Generated by Gradle"/>
@ -2973,14 +2785,6 @@
<sha256 value="3c647fd6f561a21b19ed971d0419fd462d9f83f9347bc0a105a1c289eeaaaac4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="simpleclient_pushgateway" version="0.16.0">
<artifact name="simpleclient_pushgateway-0.16.0.jar">
<sha256 value="3277cf527702f21760073d4528fa247493a6de3116994dfd290d6bb677849d47" origin="Generated by Gradle"/>
</artifact>
<artifact name="simpleclient_pushgateway-0.16.0.pom">
<sha256 value="c6fa51afc562d31016c388efde34862d2e6c0f19db44de835f4e03f4579e6541" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="simpleclient_tracer" version="0.16.0">
<artifact name="simpleclient_tracer-0.16.0.pom">
<sha256 value="3812bb22b95f81b4c3460e9e4e75c3ba72fa45e443a9fce5320842aabd0e99c2" origin="Generated by Gradle"/>
@ -5576,14 +5380,6 @@
<sha256 value="965aeb2bedff369819bdde1bf7a0b3b89b8247dd69c88b86375d76163bb8c397" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains" name="annotations" version="17.0.0">
<artifact name="annotations-17.0.0.jar">
<sha256 value="195fb0da046d55bb042e91543484cf1da68b02bb7afbfe031f229e45ac84b3f2" origin="Generated by Gradle"/>
</artifact>
<artifact name="annotations-17.0.0.pom">
<sha256 value="8390ee0b4dff7f568b1336e061f694e77d761a505169aed8964e75482c990eed" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-gradle-plugin-api" version="1.8.10">
<artifact name="kotlin-gradle-plugin-api-1.8.10-gradle76.jar">
<sha256 value="fc453e5efb4a644879a54e51d69df4874d0d2b77fce509fe030728119c43ecff" origin="Generated by Gradle"/>
@ -6108,14 +5904,6 @@
<sha256 value="fb42d7d5bf585894076a6ce16caff7e690c184ed49e756a703620d5ac42fa2f3" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.junit.vintage" name="junit-vintage-engine" version="5.11.2">
<artifact name="junit-vintage-engine-5.11.2.jar">
<sha256 value="b98cb692e669b3e90e826d2ede40fce1a8b1c8e0b58262935a609f0545b42ef9" origin="Generated by Gradle"/>
</artifact>
<artifact name="junit-vintage-engine-5.11.2.module">
<sha256 value="4c45322e2f5f0bfe5a56a7a64a0f1b0aa3c94f797b32e16b63b3cc0a7bf83561" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.junit.vintage" name="junit-vintage-engine" version="5.5.2">
<artifact name="junit-vintage-engine-5.5.2.jar">
<sha256 value="350c19f11a1112c0f0cae7701f7e2cbbd2247d42460da096fa515a25b4a9754d" origin="Generated by Gradle"/>
@ -6525,14 +6313,6 @@
<sha256 value="54ba23d87a2d438540c99ef8794a0856fc573a256b498678283c3c67ef18ada8" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.rnorth.duct-tape" name="duct-tape" version="1.0.8">
<artifact name="duct-tape-1.0.8.jar">
<sha256 value="31cef12ddec979d1f86d7cf708c41a17da523d05c685fd6642e9d0b2addb7240" origin="Generated by Gradle"/>
</artifact>
<artifact name="duct-tape-1.0.8.pom">
<sha256 value="2636f66debe6e12437632f23a67b9bc961b03633009e52527a3bbc871f4069b4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.rocksdb" name="rocksdbjni" version="9.7.3">
<artifact name="rocksdbjni-9.7.3.jar">
<sha256 value="5ca63e0e955f101f7af1c1fb8ce260b5d3a5701cea7d8c2852b9d56031a57221" origin="Generated by Gradle"/>
@ -6673,14 +6453,6 @@
<sha256 value="b88a2db288863565727ee7cf80272aef5d90b33ed13a187bad4439d01f9144d7" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.testcontainers" name="testcontainers" version="1.20.2">
<artifact name="testcontainers-1.20.2.jar">
<sha256 value="2a3ec0d7001c27a68ab87d4992e21d8fcacde9f3d0bb4c15fc41dc899cbb3b64" origin="Generated by Gradle"/>
</artifact>
<artifact name="testcontainers-1.20.2.pom">
<sha256 value="017e32e23584dc0e201a0e728d4aa89864fa4e6b75d14332950bdf79e6f06125" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.testcontainers" name="testcontainers-bom" version="1.19.4">
<artifact name="testcontainers-bom-1.19.4.pom">
<sha256 value="fc7f42a3c2a3d7a89ac7e01fa64c8a8e08294fd5abaa6009fead3d60d7628fa3" origin="Generated by 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'

@ -18,12 +18,12 @@ import org.hyperledger.besu.metrics.opentelemetry.MetricsOtelPushService;
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<MetricsService> 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 {

@ -17,99 +17,14 @@ package org.hyperledger.besu.metrics;
import org.hyperledger.besu.plugin.services.metrics.MetricCategory;
import java.util.List;
import java.util.Objects;
import com.google.common.base.MoreObjects;
/** The Observation. */
public class Observation {
private final MetricCategory category;
private final String metricName;
private final List<String> labels;
private final Object value;
/**
* Instantiates a new Observation.
/**
* The Observation.
*
* @param category the category
* @param metricName the metric name
* @param value the value
* @param labels the labels
*/
public Observation(
final MetricCategory category,
final String metricName,
final Object value,
final List<String> labels) {
this.category = category;
this.metricName = metricName;
this.value = value;
this.labels = labels;
}
/**
* Gets category.
*
* @return the category
*/
public MetricCategory getCategory() {
return category;
}
/**
* Gets metric name.
*
* @return the metric name
*/
public String getMetricName() {
return metricName;
}
/**
* Gets labels.
*
* @return the labels
*/
public List<String> getLabels() {
return labels;
}
/**
* Gets value.
*
* @return the value
*/
public Object getValue() {
return value;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Observation that = (Observation) o;
return Objects.equals(category, that.category)
&& Objects.equals(metricName, that.metricName)
&& Objects.equals(labels, that.labels)
&& Objects.equals(value, that.value);
}
@Override
public int hashCode() {
return Objects.hash(category, metricName, labels, value);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("category", category)
.add("metricName", metricName)
.add("labels", labels)
.add("value", value)
.toString();
}
}
public record Observation(
MetricCategory category, String metricName, Object value, List<String> labels) {}

@ -21,6 +21,7 @@ import org.hyperledger.besu.plugin.services.metrics.ExternalSummary;
import org.hyperledger.besu.plugin.services.metrics.LabelledGauge;
import org.hyperledger.besu.plugin.services.metrics.LabelledMetric;
import org.hyperledger.besu.plugin.services.metrics.LabelledSuppliedMetric;
import org.hyperledger.besu.plugin.services.metrics.LabelledSuppliedSummary;
import org.hyperledger.besu.plugin.services.metrics.MetricCategory;
import org.hyperledger.besu.plugin.services.metrics.OperationTimer;
@ -41,9 +42,6 @@ public class NoOpMetricsSystem implements ObservableMetricsSystem {
/** The constant NO_OP_COUNTER. */
public static final Counter NO_OP_COUNTER = new NoOpCounter();
/** The constant NO_OP_GAUGE. */
public static final LabelledSuppliedMetric NO_OP_GAUGE = new NoOpValueCollector();
private static final OperationTimer.TimingContext NO_OP_TIMING_CONTEXT = () -> 0;
/** The constant NO_OP_OPERATION_TIMER. */
@ -65,18 +63,6 @@ public class NoOpMetricsSystem implements ObservableMetricsSystem {
public static final LabelledMetric<OperationTimer> NO_OP_LABELLED_1_OPERATION_TIMER =
new LabelCountingNoOpMetric<>(1, NO_OP_OPERATION_TIMER);
/** The constant NO_OP_LABELLED_1_GAUGE. */
public static final LabelledSuppliedMetric NO_OP_LABELLED_1_GAUGE =
new LabelledSuppliedNoOpMetric(1, NO_OP_GAUGE);
/** The constant NO_OP_LABELLED_2_GAUGE. */
public static final LabelledSuppliedMetric NO_OP_LABELLED_2_GAUGE =
new LabelledSuppliedNoOpMetric(2, NO_OP_GAUGE);
/** The constant NO_OP_LABELLED_3_GAUGE. */
public static final LabelledSuppliedMetric NO_OP_LABELLED_3_GAUGE =
new LabelledSuppliedNoOpMetric(3, NO_OP_GAUGE);
/** Default constructor */
public NoOpMetricsSystem() {}
@ -96,17 +82,8 @@ public class NoOpMetricsSystem implements ObservableMetricsSystem {
* @return the counter labelled metric
*/
public static LabelledMetric<Counter> getCounterLabelledMetric(final int labelCount) {
switch (labelCount) {
case 1:
return NO_OP_LABELLED_1_COUNTER;
case 2:
return NO_OP_LABELLED_2_COUNTER;
case 3:
return NO_OP_LABELLED_3_COUNTER;
default:
return new LabelCountingNoOpMetric<>(labelCount, NO_OP_COUNTER);
}
}
@Override
public LabelledMetric<OperationTimer> createSimpleLabelledTimer(
@ -118,11 +95,13 @@ public class NoOpMetricsSystem implements ObservableMetricsSystem {
}
@Override
public void trackExternalSummary(
public LabelledSuppliedSummary createLabelledSuppliedSummary(
final MetricCategory category,
final String name,
final String help,
final Supplier<ExternalSummary> summarySupplier) {}
final String... labelNames) {
return getLabelledSuppliedSummary(labelNames.length);
}
@Override
public LabelledMetric<OperationTimer> createLabelledTimer(
@ -141,12 +120,8 @@ public class NoOpMetricsSystem implements ObservableMetricsSystem {
*/
public static LabelledMetric<OperationTimer> getOperationTimerLabelledMetric(
final int labelCount) {
if (labelCount == 1) {
return NO_OP_LABELLED_1_OPERATION_TIMER;
} else {
return new LabelCountingNoOpMetric<>(labelCount, NO_OP_OPERATION_TIMER);
}
}
@Override
public void createGauge(
@ -184,16 +159,17 @@ public class NoOpMetricsSystem implements ObservableMetricsSystem {
* @return the labelled gauge
*/
public static LabelledSuppliedMetric getLabelledSuppliedMetric(final int labelCount) {
switch (labelCount) {
case 1:
return NO_OP_LABELLED_1_GAUGE;
case 2:
return NO_OP_LABELLED_2_GAUGE;
case 3:
return NO_OP_LABELLED_3_GAUGE;
default:
return new LabelledSuppliedNoOpMetric(labelCount, NO_OP_GAUGE);
return new LabelledSuppliedNoOpMetric(labelCount);
}
/**
* Gets labelled supplied histogram.
*
* @param labelCount the label count
* @return the labelled gauge
*/
public static LabelledSuppliedSummary getLabelledSuppliedSummary(final int labelCount) {
return new LabelledSuppliedNoOpMetric(labelCount);
}
@Override
@ -249,7 +225,8 @@ public class NoOpMetricsSystem implements ObservableMetricsSystem {
/** The Labelled supplied NoOp metric. */
@SuppressWarnings("removal") // remove when deprecated LabelledGauge is removed
public static class LabelledSuppliedNoOpMetric implements LabelledSuppliedMetric, LabelledGauge {
public static class LabelledSuppliedNoOpMetric
implements LabelledSuppliedMetric, LabelledGauge, LabelledSuppliedSummary {
/** The Label count. */
final int labelCount;
@ -257,22 +234,26 @@ public class NoOpMetricsSystem implements ObservableMetricsSystem {
final List<String> labelValuesCache = new ArrayList<>();
/**
* Instantiates a new Labelled gauge NoOp metric.
* Instantiates a new Labelled supplied NoOp metric.
*
* @param labelCount the label count
* @param fakeMetric the fake metric
*/
public LabelledSuppliedNoOpMetric(
final int labelCount, final LabelledSuppliedMetric fakeMetric) {
public LabelledSuppliedNoOpMetric(final int labelCount) {
this.labelCount = labelCount;
this.fakeMetric = fakeMetric;
}
/** The Fake metric. */
final LabelledSuppliedMetric fakeMetric;
@Override
public void labels(final DoubleSupplier valueSupplier, final String... labelValues) {
internalLabels(valueSupplier, labelValues);
}
@Override
public void labels(
final Supplier<ExternalSummary> summarySupplier, final String... labelValues) {
internalLabels(summarySupplier, labelValues);
}
private void internalLabels(final Object valueSupplier, final String... labelValues) {
final String labelValuesString = String.join(",", labelValues);
Preconditions.checkArgument(
!labelValuesCache.contains(labelValuesString),
@ -281,6 +262,7 @@ public class NoOpMetricsSystem implements ObservableMetricsSystem {
labelValues.length == labelCount,
"The count of labels used must match the count of labels expected.");
Preconditions.checkNotNull(valueSupplier, "No valueSupplier specified");
labelValuesCache.add(labelValuesString);
}
}
}

@ -20,9 +20,9 @@ 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.ExternalSummary;
import org.hyperledger.besu.plugin.services.metrics.LabelledMetric;
import org.hyperledger.besu.plugin.services.metrics.LabelledSuppliedMetric;
import org.hyperledger.besu.plugin.services.metrics.LabelledSuppliedSummary;
import org.hyperledger.besu.plugin.services.metrics.MetricCategory;
import org.hyperledger.besu.plugin.services.metrics.OperationTimer;
@ -41,7 +41,6 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.DoubleSupplier;
import java.util.function.Supplier;
import java.util.stream.Stream;
import javax.inject.Singleton;
@ -134,7 +133,7 @@ public class OpenTelemetrySystem implements ObservableMetricsSystem {
@Override
public Stream<Observation> streamObservations(final MetricCategory category) {
return streamObservations().filter(metricData -> metricData.getCategory().equals(category));
return streamObservations().filter(metricData -> metricData.category().equals(category));
}
@Override
@ -246,11 +245,14 @@ public class OpenTelemetrySystem implements ObservableMetricsSystem {
}
@Override
public void trackExternalSummary(
public LabelledSuppliedSummary createLabelledSuppliedSummary(
final MetricCategory category,
final String name,
final String help,
final Supplier<ExternalSummary> summarySupplier) {}
final String... labelNames) {
// not yet supported
return (LabelledSuppliedSummary) NoOpMetricsSystem.getLabelledSuppliedMetric(labelNames.length);
}
@Override
public LabelledMetric<OperationTimer> createLabelledTimer(

@ -0,0 +1,132 @@
/*
* 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 static org.hyperledger.besu.metrics.prometheus.PrometheusCollector.addLabelValues;
import static org.hyperledger.besu.metrics.prometheus.PrometheusCollector.getLabelValues;
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.registry.Collector;
import io.prometheus.metrics.model.registry.PrometheusRegistry;
import io.prometheus.metrics.model.snapshots.SummarySnapshot;
/**
* Abstract base class for Prometheus summary collectors. A summary provides a total count of
* observations and a sum of all observed values, it calculates configurable quantiles over a
* sliding time window.
*/
abstract class AbstractPrometheusSummary extends CategorizedPrometheusCollector {
/** The Prometheus collector */
protected Collector collector;
/**
* Constructs a new AbstractPrometheusSummary.
*
* @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);
}
/**
* Gets the identifier for this collector.
*
* @return The Prometheus name of the collector
*/
@Override
public String getIdentifier() {
return collector.getPrometheusName();
}
/**
* Registers this collector with the given Prometheus registry.
*
* @param registry The Prometheus registry to register this collector with
*/
@Override
public void register(final PrometheusRegistry registry) {
registry.register(collector);
}
/**
* Unregisters this collector from the given Prometheus registry.
*
* @param registry The Prometheus registry to unregister this collector from
*/
@Override
public void unregister(final PrometheusRegistry registry) {
registry.unregister(collector);
}
/**
* Collects the summary snapshot from the Prometheus collector.
*
* @return The collected summary snapshot
*/
private SummarySnapshot collect() {
return (SummarySnapshot) collector.collect();
}
/**
* Streams the observations from the collected summary snapshot.
*
* @return A stream of observations
*/
@Override
public Stream<Observation> streamObservations() {
return collect().getDataPoints().stream()
.flatMap(
dataPoint -> {
final var labelValues = getLabelValues(dataPoint.getLabels());
final var quantiles = dataPoint.getQuantiles();
final var observations = new ArrayList<Observation>(quantiles.size() + 2);
if (dataPoint.hasSum()) {
observations.add(
new Observation(
category, name, dataPoint.getSum(), addLabelValues(labelValues, "sum")));
}
if (dataPoint.hasCount()) {
observations.add(
new Observation(
category,
name,
dataPoint.getCount(),
addLabelValues(labelValues, "count")));
}
quantiles.forEach(
quantile ->
observations.add(
new Observation(
category,
name,
quantile.getValue(),
addLabelValues(
labelValues,
"quantile",
Double.toString(quantile.getQuantile())))));
return observations.stream();
});
}
}

@ -0,0 +1,96 @@
/*
* 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.LabelledSuppliedMetric;
import org.hyperledger.besu.plugin.services.metrics.MetricCategory;
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.metrics.model.registry.Collector;
import io.prometheus.metrics.model.registry.PrometheusRegistry;
import io.prometheus.metrics.model.snapshots.DataPointSnapshot;
/**
* Abstract base class for Prometheus supplied value collectors. A supplied value collector is one
* which actual value is kept outside the metric system, for example in an external library or to
* calculate the value only on demand when a metric scrape occurs. This class provides common
* functionality for Prometheus supplied value collectors.
*/
abstract class AbstractPrometheusSuppliedValueCollector extends CategorizedPrometheusCollector
implements LabelledSuppliedMetric {
/** The collector */
protected final Collector collector;
/** Map label values with the collector callback data */
protected final Map<List<String>, CallbackData> labelledCallbackData = new ConcurrentHashMap<>();
protected AbstractPrometheusSuppliedValueCollector(
final MetricCategory category,
final String name,
final String help,
final String... labelNames) {
super(category, name);
this.collector = createCollector(help, labelNames);
}
protected abstract Collector createCollector(final String help, final String... labelNames);
@Override
public void labels(final DoubleSupplier valueSupplier, final String... labelValues) {
final var valueList = List.of(labelValues);
if (labelledCallbackData.putIfAbsent(valueList, new CallbackData(valueSupplier, labelValues))
!= null) {
throw new IllegalArgumentException(
String.format("A collector has already been created for label values %s", valueList));
}
}
@Override
public String getIdentifier() {
return collector.getPrometheusName();
}
@Override
public void register(final PrometheusRegistry registry) {
registry.register(collector);
}
@Override
public void unregister(final PrometheusRegistry registry) {
registry.unregister(collector);
}
@Override
public Stream<Observation> streamObservations() {
final var snapshot = collector.collect();
return snapshot.getDataPoints().stream().map(this::convertToObservation);
}
/**
* Convert the collected sample to an observation
*
* @param sample the collected sample
* @return an observation
*/
protected abstract Observation convertToObservation(final DataPointSnapshot sample);
protected record CallbackData(DoubleSupplier valueSupplier, String[] labelValues) {}
}

@ -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;
}
}

@ -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<String> labelNames;
private final List<String> 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<String> labelNames,
final List<String> labelValues,
final DoubleSupplier valueSupplier) {
this.metricName = metricName;
this.help = help;
this.valueSupplier = valueSupplier;
this.labelNames = labelNames;
this.labelValues = labelValues;
}
@Override
public List<MetricFamilySamples> collect() {
final Sample sample =
new Sample(metricName, labelNames, labelValues, valueSupplier.getAsDouble());
return singletonList(
new MetricFamilySamples(metricName, Type.GAUGE, help, singletonList(sample)));
}
}

@ -15,61 +15,46 @@
package org.hyperledger.besu.metrics.prometheus;
import static com.google.common.base.Preconditions.checkArgument;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
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 +70,39 @@ public class MetricsHttpService implements MetricsService {
@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);
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;
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();
private Handler<RoutingContext> checkAllowlistHostHeader() {
return event -> {
final Optional<String> 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.\"}");
return CompletableFuture.completedFuture(null);
} catch (final Throwable e) {
return CompletableFuture.failedFuture(e);
}
}
};
private Authenticator.Result checkAllowlistHostHeader(final HttpExchange exch) {
if (config.getHostsAllowlist().contains("*")) {
return AUTHORIZED;
}
private Optional<String> 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 +125,12 @@ public class MetricsHttpService implements MetricsService {
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<String> names = new TreeSet<>(routingContext.queryParam("name[]"));
final HttpServerResponse response = routingContext.response();
vertx.<String>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());
httpServer.stop();
return CompletableFuture.completedFuture(null);
} catch (final Throwable e) {
return CompletableFuture.failedFuture(e);
}
});
}
/**
@ -240,7 +142,7 @@ public class MetricsHttpService implements MetricsService {
if (httpServer == null) {
return EMPTY_SOCKET_ADDRESS;
}
return new InetSocketAddress(config.getHost(), httpServer.actualPort());
return new InetSocketAddress(config.getHost(), httpServer.getPort());
}
@Override
@ -248,11 +150,21 @@ public class MetricsHttpService implements MetricsService {
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(HTTP_NOT_FOUND, -1);
} finally {
exchange.close();
}
} else {
super.handle(exchange);
}
}
}
}

@ -26,7 +26,7 @@ import java.util.concurrent.Executors;
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 class MetricsPushGatewayService implements MetricsService {
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 class MetricsPushGatewayService implements MetricsService {
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 class MetricsPushGatewayService implements MetricsService {
private void pushMetrics() {
try {
pushGateway.pushAdd(
((PrometheusMetricsSystem) metricsSystem).getRegistry(), config.getPrometheusJob());
pushGateway.pushAdd();
} catch (final IOException e) {
LOG.warn("Could not push metrics", e);
}

@ -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 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 identifier of the collector
*
* @return the identifier of the collector
*/
String getIdentifier();
/**
* Register this collector to the specified registry
*
* @param registry the registry
*/
void register(final PrometheusRegistry registry);
/**
* Unregister this collector from the specified registry
*
* @param registry the registry
*/
void unregister(final PrometheusRegistry registry);
/**
* Stream the data points of this collector
*
* @return a stream of the data points of this collector
*/
Stream<Observation> 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<String> getLabelValues(final Labels labels) {
return labels.stream().map(Label::getValue).toList();
}
/**
* Add new values to an existing list of label values
*
* @param labelValues existing list of label values
* @param values the values to add
* @return a new list with new values appended to the original list
*/
static List<String> addLabelValues(final List<String> labelValues, final String... values) {
final var newList = new ArrayList<String>(labelValues.size() + values.length);
newList.addAll(labelValues);
Collections.addAll(newList, values);
return newList;
}
}

@ -14,34 +14,88 @@
*/
package org.hyperledger.besu.metrics.prometheus;
import static org.hyperledger.besu.metrics.prometheus.PrometheusCollector.getLabelValues;
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;
import java.util.stream.Stream;
class PrometheusCounter implements LabelledMetric<Counter> {
import io.prometheus.metrics.core.datapoints.CounterDataPoint;
import io.prometheus.metrics.model.registry.PrometheusRegistry;
private final io.prometheus.client.Counter counter;
/**
* A Prometheus counter implementation for Besu metrics. This class provides a Prometheus counter
* where the actual value is kept internally by the collector and methods are provided to increase
* the value when needed.
*/
class PrometheusCounter extends CategorizedPrometheusCollector implements LabelledMetric<Counter> {
private final io.prometheus.metrics.core.metrics.Counter counter;
public PrometheusCounter(final io.prometheus.client.Counter counter) {
this.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 getIdentifier() {
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);
}
/**
* Streams the observations from the collected counter data points.
*
* @return A stream of observations
*/
@Override
public Stream<Observation> streamObservations() {
return counter.collect().getDataPoints().stream()
.map(
sample ->
new Observation(
category, name, sample.getValue(), getLabelValues(sample.getLabels())));
}
/** A private record class representing an unlabelled counter. */
private record UnlabelledCounter(CounterDataPoint counter) implements Counter {
/** Increments the counter by one. */
@Override
public void inc() {
counter.inc();
}
/**
* Increments the counter by the specified amount.
*
* @param amount The amount to increment the counter by
*/
@Override
public void inc(final long amount) {
counter.inc((double) amount);

@ -0,0 +1,171 @@
/*
* 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 static org.hyperledger.besu.metrics.prometheus.PrometheusCollector.getLabelValues;
import org.hyperledger.besu.metrics.Observation;
import org.hyperledger.besu.plugin.services.metrics.MetricCategory;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.ToDoubleFunction;
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.prometheus.metrics.model.snapshots.CounterSnapshot;
import io.prometheus.metrics.model.snapshots.DataPointSnapshot;
import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
import io.prometheus.metrics.model.snapshots.MetricSnapshot;
import io.vertx.core.impl.ConcurrentHashSet;
/**
* A Prometheus Guava cache collector implementation for Besu metrics. This class provides a way to
* expose metrics from Guava caches, it behaves differently from other collectors, since instead of
* having one collector per cache, Prometheus provides only one collector for all caches, so we need
* a Context that wraps the Prometheus single collector and handles its registration, while here we
* keep the abstraction of one Prometheus collector for one Guava cache, and we also verify that
* there is no collector name clash.
*/
class PrometheusGuavaCache extends CategorizedPrometheusCollector {
/** Use to reduce the possibility of a name clash with other collectors */
private static final String NAME_PREFIX = "__guavaCacheMetricsCollector__";
private final Cache<?, ?> cache;
private final Context context;
public PrometheusGuavaCache(
final MetricCategory category,
final Context context,
final String name,
final Cache<?, ?> cache) {
super(category, name);
if (context.alreadyExists(name)) {
throw new IllegalStateException("Cache already registered: " + name);
}
this.cache = cache;
this.context = context;
}
@Override
public String getIdentifier() {
return category.getName() + "." + NAME_PREFIX + "." + name;
}
@Override
public void register(final PrometheusRegistry registry) {
context.registerCache(registry, name, cache);
}
@Override
public void unregister(final PrometheusRegistry registry) {
context.unregisterCache(registry, name);
}
@Override
public Stream<Observation> streamObservations() {
return context.streamObservations(category, name);
}
/**
* Since Prometheus provides only one collector for all Guava caches, we only need to register
* that collector once when the first Besu Guava cache collector is created, and unregister it
* when the last is unregistered, so we have this context to keep track of that and also manage
* the observations stream.
*/
static class Context {
private static final Map<String, ToDoubleFunction<DataPointSnapshot>>
COLLECTOR_VALUE_EXTRACTORS =
Map.of(
"guava_cache_eviction", Context::counterValueExtractor,
"guava_cache_hit", Context::counterValueExtractor,
"guava_cache_miss", Context::counterValueExtractor,
"guava_cache_requests", Context::counterValueExtractor,
"guava_cache_size", Context::gaugeValueExtractor);
private final CacheMetricsCollector cacheMetricsCollector = new CacheMetricsCollector();
private final Set<String> cacheNames = new ConcurrentHashSet<>();
private final AtomicBoolean collectorRegistered = new AtomicBoolean(false);
boolean alreadyExists(final String name) {
return cacheNames.contains(name);
}
void registerCache(
final PrometheusRegistry registry, final String name, final Cache<?, ?> cache) {
cacheMetricsCollector.addCache(name, cache);
cacheNames.add(name);
if (collectorRegistered.compareAndSet(false, true)) {
registry.register(cacheMetricsCollector);
}
}
void unregisterCache(final PrometheusRegistry registry, final String name) {
cacheMetricsCollector.removeCache(name);
cacheNames.remove(name);
if (cacheNames.isEmpty() && collectorRegistered.compareAndSet(true, false)) {
registry.unregister(cacheMetricsCollector);
}
}
void clear() {
cacheNames.forEach(cacheMetricsCollector::removeCache);
cacheNames.clear();
collectorRegistered.set(false);
}
private Stream<Observation> streamObservations(
final MetricCategory category, final String cacheName) {
return cacheMetricsCollector.collect().stream()
.flatMap(ms -> convertToObservations(category, cacheName, ms));
}
private static Stream<Observation> convertToObservations(
final MetricCategory category, final String cacheName, final MetricSnapshot snapshot) {
final var prometheusName = snapshot.getMetadata().getPrometheusName();
if (COLLECTOR_VALUE_EXTRACTORS.containsKey(prometheusName)) {
return snapshotToObservations(category, cacheName, prometheusName, snapshot);
}
return Stream.empty();
}
private static Stream<Observation> snapshotToObservations(
final MetricCategory category,
final String cacheName,
final String prometheusName,
final MetricSnapshot snapshot) {
return snapshot.getDataPoints().stream()
.filter(gdps -> gdps.getLabels().get("cache").equals(cacheName))
.map(
gdps ->
new Observation(
category,
prometheusName,
COLLECTOR_VALUE_EXTRACTORS.get(prometheusName).applyAsDouble(gdps),
getLabelValues(gdps.getLabels())));
}
private static double gaugeValueExtractor(final DataPointSnapshot snapshot) {
return ((GaugeSnapshot.GaugeDataPointSnapshot) snapshot).getValue();
}
private static double counterValueExtractor(final DataPointSnapshot snapshot) {
return ((CounterSnapshot.CounterDataPointSnapshot) snapshot).getValue();
}
}
}

@ -14,59 +14,61 @@
*/
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;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import org.hyperledger.besu.plugin.services.metrics.ExternalSummary;
import org.hyperledger.besu.plugin.services.metrics.Counter;
import org.hyperledger.besu.plugin.services.metrics.LabelledMetric;
import org.hyperledger.besu.plugin.services.metrics.LabelledSuppliedMetric;
import org.hyperledger.besu.plugin.services.metrics.LabelledSuppliedSummary;
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;
import java.util.function.DoubleSupplier;
import java.util.function.Supplier;
import java.util.stream.Stream;
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.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<String> EXTERNAL_SUMMARY_LABELS = List.of("quantile");
private final Map<MetricCategory, Collection<Collector>> collectors = new ConcurrentHashMap<>();
private final CollectorRegistry registry = new CollectorRegistry(true);
private final Map<String, LabelledMetric<org.hyperledger.besu.plugin.services.metrics.Counter>>
cachedCounters = new ConcurrentHashMap<>();
private final Map<String, LabelledMetric<OperationTimer>> cachedTimers =
private static final Map<Double, Double> 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<MetricCategory, Collection<PrometheusCollector>> collectors =
new ConcurrentHashMap<>();
private final PrometheusRegistry registry = PrometheusRegistry.defaultRegistry;
private final Map<CachedMetricKey, LabelledMetric<Counter>> cachedCounters =
new ConcurrentHashMap<>();
private final Set<String> totalSuffixedCounters = new ConcurrentHashSet<>();
private final Map<MetricCategory, CacheMetricsCollector> guavaCacheCollectors =
private final Map<CachedMetricKey, LabelledMetric<OperationTimer>> cachedTimers =
new ConcurrentHashMap<>();
private final Set<String> guavaCacheNames = new ConcurrentHashSet<>();
private final PrometheusGuavaCache.Context guavaCacheCollectorContext =
new PrometheusGuavaCache.Context();
private final Set<MetricCategory> enabledCategories;
private final boolean timersEnabled;
@ -85,15 +87,19 @@ public class PrometheusMetricsSystem implements ObservableMetricsSystem {
/** 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);
}
}
@ -103,22 +109,20 @@ public class PrometheusMetricsSystem implements ObservableMetricsSystem {
}
@Override
public LabelledMetric<org.hyperledger.besu.plugin.services.metrics.Counter> createLabelledCounter(
public LabelledMetric<Counter> createLabelledCounter(
final MetricCategory category,
final String name,
final String help,
final String... labelNames) {
final String metricName = convertToPrometheusCounterName(category, name);
return cachedCounters.computeIfAbsent(
metricName,
(k) -> {
CachedMetricKey.of(category, 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);
} else {
return NoOpMetricsSystem.getCounterLabelledMetric(labelNames.length);
return counter;
}
return NoOpMetricsSystem.getCounterLabelledMetric(labelNames.length);
});
}
@ -128,26 +132,16 @@ public class PrometheusMetricsSystem implements ObservableMetricsSystem {
final String name,
final String help,
final String... labelNames) {
final String metricName = convertToPrometheusName(category, name);
return cachedTimers.computeIfAbsent(
metricName,
(k) -> {
CachedMetricKey.of(category, 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);
} else {
return NoOpMetricsSystem.getOperationTimerLabelledMetric(labelNames.length);
return summary;
}
return NoOpMetricsSystem.getOperationTimerLabelledMetric(labelNames.length);
});
}
@ -157,85 +151,41 @@ public class PrometheusMetricsSystem implements ObservableMetricsSystem {
final String name,
final String help,
final String... labelNames) {
final String metricName = convertToPrometheusName(category, name);
return cachedTimers.computeIfAbsent(
metricName,
(k) -> {
CachedMetricKey.of(category, 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);
} else {
return NoOpMetricsSystem.getOperationTimerLabelledMetric(labelNames.length);
return histogram;
}
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(
public LabelledSuppliedSummary createLabelledSuppliedSummary(
final MetricCategory category,
final String name,
final String help,
final Supplier<ExternalSummary> summarySupplier) {
final String... labelNames) {
if (isCategoryEnabled(category)) {
final var externalSummaryCollector =
new Collector() {
@Override
public List<MetricFamilySamples> 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));
}
};
registerCollector(category, externalSummaryCollector);
final PrometheusSuppliedSummary summary =
new PrometheusSuppliedSummary(category, name, help, labelNames);
registerCollector(category, summary);
return summary;
}
return NoOpMetricsSystem.getLabelledSuppliedSummary(labelNames.length);
}
@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 guavaCacheCollector =
guavaCacheCollectors.computeIfAbsent(
category,
unused -> {
final var cmc = new CacheMetricsCollector();
registerCollector(category, cmc);
return cmc;
});
guavaCacheCollector.addCache(name, cache);
final var cacheCollector =
new PrometheusGuavaCache(category, guavaCacheCollectorContext, name, cache);
registerCollector(category, cacheCollector);
}
}
@ -245,7 +195,13 @@ public class PrometheusMetricsSystem implements ObservableMetricsSystem {
final String name,
final String help,
final String... labelNames) {
return createLabelledSuppliedMetric(category, Collector.Type.COUNTER, name, help, labelNames);
if (isCategoryEnabled(category)) {
final PrometheusSuppliedCounter counter =
new PrometheusSuppliedCounter(category, name, help, labelNames);
registerCollector(category, counter);
return counter;
}
return NoOpMetricsSystem.getLabelledSuppliedMetric(labelNames.length);
}
@Override
@ -254,53 +210,38 @@ public class PrometheusMetricsSystem implements ObservableMetricsSystem {
final String name,
final String help,
final String... labelNames) {
return createLabelledSuppliedMetric(category, Collector.Type.GAUGE, name, help, labelNames);
}
private LabelledSuppliedMetric createLabelledSuppliedMetric(
final MetricCategory category,
final Collector.Type type,
final String name,
final String help,
final String... labelNames) {
final String metricName = convertToPrometheusName(category, name);
if (isCategoryEnabled(category)) {
final PrometheusSuppliedValueCollector suppliedValueCollector =
new PrometheusSuppliedValueCollector(type, metricName, help, List.of(labelNames));
registerCollector(category, suppliedValueCollector);
return suppliedValueCollector;
final PrometheusSuppliedGauge gauge =
new PrometheusSuppliedGauge(category, name, help, labelNames);
registerCollector(category, gauge);
return gauge;
}
return NoOpMetricsSystem.getLabelledSuppliedMetric(labelNames.length);
}
private void registerCollector(final MetricCategory category, final Collector collector) {
final Collection<Collector> categoryCollectors =
this.collectors.computeIfAbsent(
category, key -> Collections.newSetFromMap(new ConcurrentHashMap<>()));
final List<String> newSamples =
collector.collect().stream().map(metricFamilySamples -> metricFamilySamples.name).toList();
private void registerCollector(
final MetricCategory category, final PrometheusCollector collector) {
final Collection<PrometheusCollector> 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.getIdentifier().equals(collector.getIdentifier()))
.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<Observation> 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
@ -308,137 +249,22 @@ public class PrometheusMetricsSystem implements ObservableMetricsSystem {
return collectors.keySet().stream().flatMap(this::streamObservations);
}
PrometheusRegistry getRegistry() {
return registry;
}
@Override
public void shutdown() {
registry.clear();
collectors.clear();
cachedCounters.clear();
cachedTimers.clear();
guavaCacheCollectors.clear();
guavaCacheNames.clear();
}
private Stream<Observation> 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<String> 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<String> 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<String> 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);
guavaCacheCollectorContext.clear();
}
/**
* 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() + "_";
private record CachedMetricKey(MetricCategory category, String name) {
static CachedMetricKey of(final MetricCategory category, final String name) {
return new CachedMetricKey(category, name);
}
/**
* Gets registry.
*
* @return the registry
*/
CollectorRegistry getRegistry() {
return registry;
}
}

@ -14,22 +14,86 @@
*/
package org.hyperledger.besu.metrics.prometheus;
import static org.hyperledger.besu.metrics.prometheus.PrometheusCollector.addLabelValues;
import static org.hyperledger.besu.metrics.prometheus.PrometheusCollector.getLabelValues;
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;
import io.prometheus.metrics.core.metrics.Histogram;
import io.prometheus.metrics.model.registry.PrometheusRegistry;
class PrometheusSimpleTimer implements LabelledMetric<OperationTimer> {
/**
* An implementation of Besu simple timer backed by a Prometheus histogram. The histogram samples
* durations and counts them in configurable buckets. It also provides a sum of all observed values.
*/
class PrometheusSimpleTimer extends CategorizedPrometheusCollector
implements LabelledMetric<OperationTimer> {
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 getIdentifier() {
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<Observation> streamObservations() {
final var snapshot = histogram.collect();
return snapshot.getDataPoints().stream()
.flatMap(
dataPoint -> {
final var labelValues = 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(),
addLabelValues(
labelValues, Double.toString(bucket.getUpperBound()))));
});
}
}

@ -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 static org.hyperledger.besu.metrics.prometheus.PrometheusCollector.getLabelValues;
import org.hyperledger.besu.metrics.Observation;
import org.hyperledger.besu.plugin.services.metrics.MetricCategory;
import java.util.List;
import io.prometheus.metrics.core.metrics.CounterWithCallback;
import io.prometheus.metrics.model.registry.Collector;
import io.prometheus.metrics.model.snapshots.CounterSnapshot;
import io.prometheus.metrics.model.snapshots.DataPointSnapshot;
/**
* A Prometheus supplied counter collector. A supplied counter collector is one which actual value
* is kept outside the metric system, for example in an external library or to calculate the value
* only on demand when a metric scrape occurs.
*/
class PrometheusSuppliedCounter extends AbstractPrometheusSuppliedValueCollector {
public PrometheusSuppliedCounter(
final MetricCategory category,
final String name,
final String help,
final String... labelNames) {
super(category, name, help, labelNames);
}
@Override
protected Collector createCollector(final String help, final String... labelNames) {
return CounterWithCallback.builder()
.name(this.prefixedName)
.help(help)
.labelNames(labelNames)
.callback(this::callback)
.build();
}
private void callback(final CounterWithCallback.Callback callback) {
labelledCallbackData
.values()
.forEach(
callbackData ->
callback.call(
callbackData.valueSupplier().getAsDouble(), callbackData.labelValues()));
}
@Override
protected Observation convertToObservation(final DataPointSnapshot sample) {
final List<String> labelValues = getLabelValues(sample.getLabels());
return new Observation(
category,
name,
((CounterSnapshot.CounterDataPointSnapshot) sample).getValue(),
labelValues);
}
}

@ -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 static org.hyperledger.besu.metrics.prometheus.PrometheusCollector.getLabelValues;
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.List;
import io.prometheus.metrics.core.metrics.GaugeWithCallback;
import io.prometheus.metrics.model.registry.Collector;
import io.prometheus.metrics.model.snapshots.DataPointSnapshot;
import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
/**
* A Prometheus supplied gauge collector. A supplied gauge collector is one which actual value is
* kept outside the metric system, for example in an external library or to calculate the value only
* on demand when a metric scrape occurs.
*/
@SuppressWarnings("removal")
class PrometheusSuppliedGauge extends AbstractPrometheusSuppliedValueCollector
implements LabelledGauge {
public PrometheusSuppliedGauge(
final MetricCategory category,
final String name,
final String help,
final String... labelNames) {
super(category, name, help, labelNames);
}
@Override
protected Collector createCollector(final String help, final String... labelNames) {
return GaugeWithCallback.builder()
.name(this.prefixedName)
.help(help)
.labelNames(labelNames)
.callback(this::callback)
.build();
}
private void callback(final GaugeWithCallback.Callback callback) {
labelledCallbackData
.values()
.forEach(
callbackData ->
callback.call(
callbackData.valueSupplier().getAsDouble(), callbackData.labelValues()));
}
@Override
protected Observation convertToObservation(final DataPointSnapshot sample) {
final List<String> labelValues = getLabelValues(sample.getLabels());
return new Observation(
category, name, ((GaugeSnapshot.GaugeDataPointSnapshot) sample).getValue(), labelValues);
}
}

@ -0,0 +1,83 @@
/*
* 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.LabelledSuppliedSummary;
import org.hyperledger.besu.plugin.services.metrics.MetricCategory;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import io.prometheus.metrics.core.metrics.SummaryWithCallback;
import io.prometheus.metrics.model.snapshots.Quantile;
import io.prometheus.metrics.model.snapshots.Quantiles;
/**
* A Prometheus supplied summary collector. A supplied summary collector is one which actual value
* is kept outside the metric system, for example in an external library or to calculate the value
* only on demand when a metric scrape occurs.
*/
class PrometheusSuppliedSummary extends AbstractPrometheusSummary
implements LabelledSuppliedSummary {
/** Map label values with the collector callback data */
protected final Map<List<String>, CallbackData> labelledCallbackData = new ConcurrentHashMap<>();
public PrometheusSuppliedSummary(
final MetricCategory category,
final String name,
final String help,
final String... labelNames) {
super(category, name);
this.collector =
SummaryWithCallback.builder()
.name(name)
.help(help)
.labelNames(labelNames)
.callback(this::callback)
.build();
}
private void callback(final SummaryWithCallback.Callback callback) {
labelledCallbackData
.values()
.forEach(
callbackData -> {
final var externalSummary = callbackData.summarySupplier().get();
final var quantilesBuilder = Quantiles.builder();
externalSummary.quantiles().stream()
.map(pq -> new Quantile(pq.quantile(), pq.value()))
.forEach(quantilesBuilder::quantile);
callback.call(
externalSummary.count(), externalSummary.sum(), quantilesBuilder.build());
});
}
@Override
public synchronized void labels(
final Supplier<ExternalSummary> summarySupplier, final String... labelValues) {
final var valueList = List.of(labelValues);
if (labelledCallbackData.containsKey(valueList)) {
throw new IllegalArgumentException(
String.format("A collector has already been created for label values %s", valueList));
}
labelledCallbackData.put(valueList, new CallbackData(summarySupplier, labelValues));
}
protected record CallbackData(Supplier<ExternalSummary> summarySupplier, String[] labelValues) {}
}

@ -1,81 +0,0 @@
/*
* 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.LabelledGauge;
import org.hyperledger.besu.plugin.services.metrics.LabelledSuppliedMetric;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.DoubleSupplier;
import io.prometheus.client.Collector;
/** The Prometheus supplied value collector. */
@SuppressWarnings("removal") // remove when deprecated LabelledGauge is removed
public class PrometheusSuppliedValueCollector extends Collector
implements LabelledSuppliedMetric, LabelledGauge {
private final Type type;
private final String metricName;
private final String help;
private final List<String> labelNames;
private final Map<List<String>, DoubleSupplier> observationsMap = new ConcurrentHashMap<>();
/**
* Instantiates a new Prometheus supplied value collector.
*
* @param type the type of the collector
* @param metricName the metric name
* @param help the help
* @param labelNames the label names
*/
public PrometheusSuppliedValueCollector(
final Type type, final String metricName, final String help, final List<String> labelNames) {
this.type = type;
this.metricName = metricName;
this.help = help;
this.labelNames = labelNames;
}
@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);
throw new IllegalArgumentException(
String.format("A gauge has already been created for label values %s", labelValuesString));
}
}
@Override
public List<MetricFamilySamples> collect() {
final List<MetricFamilySamples.Sample> samples = new ArrayList<>();
observationsMap.forEach(
(labels, valueSupplier) ->
samples.add(
new MetricFamilySamples.Sample(
metricName, labelNames, labels, valueSupplier.getAsDouble())));
return List.of(new MetricFamilySamples(metricName, type, help, samples));
}
private void validateLabelsCardinality(final String... labelValues) {
if (labelValues.length != labelNames.size()) {
throw new IllegalArgumentException(
"Label values and label names must be the same cardinality");
}
}
}

@ -15,21 +15,37 @@
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<OperationTimer> {
import io.prometheus.metrics.core.datapoints.DistributionDataPoint;
import io.prometheus.metrics.core.metrics.Summary;
private final Summary summary;
/**
* An implementation of Besu timer backed by a Prometheus summary. The summary provides a total
* count of durations and a sum of all observed durations, it calculates configurable quantiles over
* a sliding time window.
*/
class PrometheusTimer extends AbstractPrometheusSummary implements LabelledMetric<OperationTimer> {
public PrometheusTimer(final Summary summary) {
this.summary = summary;
public PrometheusTimer(
final MetricCategory category,
final String name,
final String help,
final Map<Double, Double> quantiles,
final String... labelNames) {
super(category, name);
final var summaryBuilder =
Summary.builder().name(this.prefixedName).help(help).labelNames(labelNames);
quantiles.forEach(summaryBuilder::quantile);
this.collector = summaryBuilder.build();
}
@Override
public OperationTimer labels(final String... labels) {
final Summary.Child metric = summary.labels(labels);
final DistributionDataPoint metric = ((Summary) collector).labelValues(labels);
return () -> metric.startTimer()::observeDuration;
}
}

@ -18,9 +18,9 @@ import static java.util.Arrays.asList;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import org.hyperledger.besu.plugin.services.metrics.Counter;
import org.hyperledger.besu.plugin.services.metrics.ExternalSummary;
import org.hyperledger.besu.plugin.services.metrics.LabelledMetric;
import org.hyperledger.besu.plugin.services.metrics.LabelledSuppliedMetric;
import org.hyperledger.besu.plugin.services.metrics.LabelledSuppliedSummary;
import org.hyperledger.besu.plugin.services.metrics.MetricCategory;
import org.hyperledger.besu.plugin.services.metrics.OperationTimer;
@ -30,7 +30,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.DoubleSupplier;
import java.util.function.Supplier;
import java.util.stream.Stream;
import com.google.common.cache.Cache;
@ -98,11 +97,13 @@ public class StubMetricsSystem implements ObservableMetricsSystem {
}
@Override
public void trackExternalSummary(
public LabelledSuppliedSummary createLabelledSuppliedSummary(
final MetricCategory category,
final String name,
final String help,
final Supplier<ExternalSummary> summarySupplier) {}
final String... labelNames) {
return NoOpMetricsSystem.getLabelledSuppliedSummary(labelNames.length);
}
@Override
public void createGauge(

@ -50,9 +50,9 @@ import org.junit.jupiter.api.Test;
public class OpenTelemetryMetricsSystemTest {
private static final Comparator<Observation> IGNORE_VALUES =
Comparator.<Observation, String>comparing(observation -> observation.getCategory().getName())
.thenComparing(Observation::getMetricName)
.thenComparing((o1, o2) -> o1.getLabels().equals(o2.getLabels()) ? 0 : 1);
Comparator.<Observation, String>comparing(observation -> observation.category().getName())
.thenComparing(Observation::metricName)
.thenComparing((o1, o2) -> o1.labels().equals(o2.labels()) ? 0 : 1);
@BeforeEach
public void resetGlobalOpenTelemetry() {
@ -266,7 +266,7 @@ public class OpenTelemetryMetricsSystemTest {
final LabelledMetric<Counter> counterN =
localMetricSystem.createLabelledCounter(
NETWORK, "ABC", "Not that kind of network", "show");
assertThat(counterN).isSameAs(NoOpMetricsSystem.NO_OP_LABELLED_1_COUNTER);
assertThat(counterN).isInstanceOf(NoOpMetricsSystem.LabelCountingNoOpMetric.class);
counterN.labels("show").inc();
assertThat(localMetricSystem.streamObservations()).isEmpty();
@ -274,7 +274,7 @@ public class OpenTelemetryMetricsSystemTest {
// do a category we are watching
final LabelledMetric<Counter> counterR =
localMetricSystem.createLabelledCounter(RPC, "name", "Not useful", "method");
assertThat(counterR).isNotSameAs(NoOpMetricsSystem.NO_OP_LABELLED_1_COUNTER);
assertThat(counterR).isNotInstanceOf(NoOpMetricsSystem.LabelCountingNoOpMetric.class);
counterR.labels("op").inc();
assertThat(getObservation(localMetricSystem))

@ -24,43 +24,50 @@ import java.net.InetSocketAddress;
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 PrometheusMetricsSystem metricsSystem;
private MetricsHttpService service;
private OkHttpClient client;
private String baseUrl;
private static MetricsHttpService service;
private static OkHttpClient client;
private static String baseUrl;
@BeforeAll
public static void initServerAndClient() {
service = createMetricsHttpService();
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);
}
@AfterEach
public void stopServer() {
metricsSystem.shutdown();
service.stop();
}
private static MetricsHttpService createMetricsHttpService() {
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 @@ public class MetricsHttpServiceTest {
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 class MetricsHttpServiceTest {
@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 class MetricsHttpServiceTest {
@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 class MetricsHttpServiceTest {
@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 class MetricsHttpServiceTest {
@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 class MetricsHttpServiceTest {
@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 class MetricsHttpServiceTest {
@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 class MetricsHttpServiceTest {
@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 class MetricsHttpServiceTest {
@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 class MetricsHttpServiceTest {
// 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);
}
}

@ -20,6 +20,7 @@ import static java.util.Collections.singletonList;
import static java.util.function.Predicate.not;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.hyperledger.besu.metrics.BesuMetricCategory.BLOCKCHAIN;
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;
@ -38,33 +39,31 @@ import org.hyperledger.besu.plugin.services.metrics.LabelledSuppliedMetric;
import org.hyperledger.besu.plugin.services.metrics.OperationTimer;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
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<Observation> IGNORE_VALUES =
Comparator.<Observation, String>comparing(observation -> observation.getCategory().getName())
.thenComparing(Observation::getMetricName)
.thenComparing((o1, o2) -> o1.getLabels().equals(o2.getLabels()) ? 0 : 1);
private static final Comparator<Observation> WITH_VALUES =
Comparator.<Observation, String>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 +71,11 @@ public class PrometheusMetricsSystemTest {
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 +88,11 @@ public class PrometheusMetricsSystemTest {
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 +106,9 @@ public class PrometheusMetricsSystemTest {
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 +144,12 @@ public class PrometheusMetricsSystemTest {
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 +157,18 @@ public class PrometheusMetricsSystemTest {
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 +185,19 @@ public class PrometheusMetricsSystemTest {
final LabelledMetric<OperationTimer> 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
@ -270,7 +244,7 @@ public class PrometheusMetricsSystemTest {
// do a category we are not watching
final LabelledMetric<Counter> counterN =
localMetricSystem.createLabelledCounter(NETWORK, "ABC", "Not that kind of network", "show");
assertThat(counterN).isSameAs(NoOpMetricsSystem.NO_OP_LABELLED_1_COUNTER);
assertThat(counterN).isInstanceOf(NoOpMetricsSystem.LabelCountingNoOpMetric.class);
counterN.labels("show").inc();
assertThat(localMetricSystem.streamObservations()).isEmpty();
@ -278,7 +252,7 @@ public class PrometheusMetricsSystemTest {
// do a category we are watching
final LabelledMetric<Counter> counterR =
localMetricSystem.createLabelledCounter(RPC, "name", "Not useful", "method");
assertThat(counterR).isNotSameAs(NoOpMetricsSystem.NO_OP_LABELLED_1_COUNTER);
assertThat(counterR).isNotInstanceOf(NoOpMetricsSystem.LabelCountingNoOpMetric.class);
counterR.labels("op").inc();
assertThat(localMetricSystem.streamObservations())
@ -314,17 +288,28 @@ public class PrometheusMetricsSystemTest {
assertThat(localMetricSystem).isInstanceOf(PrometheusMetricsSystem.class);
}
@Test
public void shouldCreateObservationFromGuavaCache() throws ExecutionException {
final Cache<String, String> guavaCache =
CacheBuilder.newBuilder().maximumSize(1).recordStats().build();
metricsSystem.createGuavaCacheCollector(BLOCKCHAIN, "test", guavaCache);
guavaCache.put("a", "b");
guavaCache.get("a", () -> "b");
guavaCache.get("z", () -> "x");
assertThat(metricsSystem.streamObservations())
.containsExactlyInAnyOrder(
new Observation(BLOCKCHAIN, "guava_cache_size", 1.0, List.of("test")),
new Observation(BLOCKCHAIN, "guava_cache_requests", 2.0, List.of("test")),
new Observation(BLOCKCHAIN, "guava_cache_hit", 1.0, List.of("test")),
new Observation(BLOCKCHAIN, "guava_cache_miss", 1.0, List.of("test")),
new Observation(BLOCKCHAIN, "guava_cache_eviction", 1.0, List.of("test")));
}
private boolean isCreatedSample(final Observation obs) {
// Simple client 0.10.0 add a _created sample to every counter, histogram and summary, that we
// 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);
return obs.labels().contains("created");
}
}

@ -171,7 +171,7 @@ public class RocksDBStats {
for (final var histogramType : HISTOGRAM_TYPES) {
metricsSystem.trackExternalSummary(
metricsSystem.createSummary(
KVSTORE_ROCKSDB_STATS,
KVSTORE_ROCKSDB_STATS.getName() + "_" + histogramType.name().toLowerCase(Locale.ROOT),
"RocksDB histogram for " + histogramType.name(),

@ -30,7 +30,7 @@ dependencies {
api platform('io.grpc:grpc-bom:1.68.0')
api platform('io.netty:netty-bom:4.1.115.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.4')
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')

@ -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 = 'PKvPlngg7BfdZ4Jinh0IUsyFOLvNaQU72VD4BHia/WM='
knownHash = '0suP4G0+vTbIvbBfaH+pOpNTEDaf2Hq+byXDyHc2i2E='
}
check.dependsOn('checkAPIChanges')

@ -19,6 +19,7 @@ import org.hyperledger.besu.plugin.services.metrics.ExternalSummary;
import org.hyperledger.besu.plugin.services.metrics.LabelledGauge;
import org.hyperledger.besu.plugin.services.metrics.LabelledMetric;
import org.hyperledger.besu.plugin.services.metrics.LabelledSuppliedMetric;
import org.hyperledger.besu.plugin.services.metrics.LabelledSuppliedSummary;
import org.hyperledger.besu.plugin.services.metrics.MetricCategory;
import org.hyperledger.besu.plugin.services.metrics.OperationTimer;
@ -184,7 +185,13 @@ public interface MetricsSystem extends BesuService {
* @param help A human readable description of the metric.
* @param valueSupplier A supplier for the double value to be presented.
*/
void createGauge(MetricCategory category, String name, String help, DoubleSupplier valueSupplier);
default void createGauge(
final MetricCategory category,
final String name,
final String help,
final DoubleSupplier valueSupplier) {
createLabelledSuppliedGauge(category, name, help).labels(valueSupplier);
}
/**
* Creates a gauge for displaying integer values.
@ -219,7 +226,21 @@ public interface MetricsSystem extends BesuService {
}
/**
* Track a summary that is computed externally to this metric system. Useful when existing
* Create a summary with assigned labels, that is computed externally to this metric system.
* Useful when existing libraries calculate the summary data on their own, and we want to export
* that summary via the configured metric system. A notable example are RocksDB statistics.
*
* @param category The {@link MetricCategory} this external summary is assigned to.
* @param name A name for the metric.
* @param help A human readable description of the metric.
* @param labelNames An array of labels to assign to the supplier summary.
* @return The created labelled supplied summary
*/
LabelledSuppliedSummary createLabelledSuppliedSummary(
MetricCategory category, String name, String help, String... labelNames);
/**
* Create a summary that is computed externally to this metric system. Useful when existing
* libraries calculate the summary data on their own, and we want to export that summary via the
* configured metric system. A notable example are RocksDB statistics.
*
@ -228,8 +249,13 @@ public interface MetricsSystem extends BesuService {
* @param help A human readable description of the metric.
* @param summarySupplier A supplier to retrieve the summary data when needed.
*/
void trackExternalSummary(
MetricCategory category, String name, String help, Supplier<ExternalSummary> summarySupplier);
default void createSummary(
final MetricCategory category,
final String name,
final String help,
final Supplier<ExternalSummary> summarySupplier) {
createLabelledSuppliedSummary(category, name, help).labels(summarySupplier);
}
/**
* Collect metrics from Guava cache.

@ -0,0 +1,28 @@
/*
* 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.plugin.services.metrics;
import java.util.function.Supplier;
/** The interface Labelled supplied summary. */
public interface LabelledSuppliedSummary {
/**
* Labels.
*
* @param summarySupplier the summary supplier
* @param labelValues the label values
*/
void labels(final Supplier<ExternalSummary> summarySupplier, final String... labelValues);
}
Loading…
Cancel
Save