From 92955d28943b383798bcfd147d779ac5ff63b1db Mon Sep 17 00:00:00 2001 From: Paul Harris Date: Fri, 20 Aug 2021 12:58:41 +1000 Subject: [PATCH] Add a labelled gauge to metrics (#2646) The labelled gauge functionality will allow monitoring of gauge type data without creating many gauges, which can be particularly handy in tracking related data. Signed-off-by: Paul Harris Signed-off-by: Adrian Sutton --- CHANGELOG.md | 1 + .../besu/metrics/noop/NoOpMetricsSystem.java | 56 ++++++++++++++ .../besu/metrics/noop/NoOpValueCollector.java | 35 +++++++++ .../opentelemetry/OpenTelemetryGauge.java | 73 +++++++++++++++++++ .../opentelemetry/OpenTelemetrySystem.java | 17 +++++ .../prometheus/CurrentValueCollector.java | 15 +++- .../metrics/prometheus/PrometheusGauge.java | 63 ++++++++++++++++ .../prometheus/PrometheusMetricsSystem.java | 16 ++++ .../besu/metrics/StubMetricsSystem.java | 10 +++ .../OpenTelemetryMetricsSystemTest.java | 17 +++++ .../PrometheusMetricsSystemTest.java | 29 ++++++++ plugin-api/build.gradle | 2 +- .../besu/plugin/services/MetricsSystem.java | 13 ++++ .../services/metrics/LabelledGauge.java | 21 ++++++ 14 files changed, 366 insertions(+), 2 deletions(-) create mode 100644 metrics/core/src/main/java/org/hyperledger/besu/metrics/noop/NoOpValueCollector.java create mode 100644 metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetryGauge.java create mode 100644 metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusGauge.java create mode 100644 plugin-api/src/main/java/org/hyperledger/besu/plugin/services/metrics/LabelledGauge.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 964c881544..1a8df7146c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Removed code supporting EIP-1702. [#2657](https://github.com/hyperledger/besu/pull/2657) - A native library was added for the alternative signature algorithm secp256r1, which will be used by default [#2630](https://github.com/hyperledger/besu/pull/2630) - The command line option --Xsecp-native-enabled was added as an alias for --Xsecp256k1-native-enabled [#2630](https://github.com/hyperledger/besu/pull/2630) +- Added Labelled gauges for metrics [#2646](https://github.com/hyperledger/besu/pull/2646) ### Bug Fixes - Consider effective price and effective priority fee in transaction replacement rules [\#2529](https://github.com/hyperledger/besu/issues/2529) diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/noop/NoOpMetricsSystem.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/noop/NoOpMetricsSystem.java index c037fc744c..b97de986ae 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/noop/NoOpMetricsSystem.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/noop/NoOpMetricsSystem.java @@ -17,11 +17,14 @@ package org.hyperledger.besu.metrics.noop; import org.hyperledger.besu.metrics.ObservableMetricsSystem; import org.hyperledger.besu.metrics.Observation; import org.hyperledger.besu.plugin.services.metrics.Counter; +import org.hyperledger.besu.plugin.services.metrics.LabelledGauge; import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; import org.hyperledger.besu.plugin.services.metrics.MetricCategory; import org.hyperledger.besu.plugin.services.metrics.OperationTimer; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; import java.util.Set; import java.util.function.DoubleSupplier; import java.util.stream.Stream; @@ -31,6 +34,7 @@ import com.google.common.base.Preconditions; public class NoOpMetricsSystem implements ObservableMetricsSystem { public static final Counter NO_OP_COUNTER = new NoOpCounter(); + public static final LabelledGauge NO_OP_GAUGE = new NoOpValueCollector(); private static final OperationTimer.TimingContext NO_OP_TIMING_CONTEXT = () -> 0; public static final OperationTimer NO_OP_OPERATION_TIMER = () -> NO_OP_TIMING_CONTEXT; @@ -42,6 +46,12 @@ public class NoOpMetricsSystem implements ObservableMetricsSystem { new LabelCountingNoOpMetric<>(3, NO_OP_COUNTER); public static final LabelledMetric NO_OP_LABELLED_1_OPERATION_TIMER = new LabelCountingNoOpMetric<>(1, NO_OP_OPERATION_TIMER); + public static final LabelledGauge NO_OP_LABELLED_1_GAUGE = + new LabelledGaugeNoOpMetric(1, NO_OP_GAUGE); + public static final LabelledGauge NO_OP_LABELLED_2_GAUGE = + new LabelledGaugeNoOpMetric(2, NO_OP_GAUGE); + public static final LabelledGauge NO_OP_LABELLED_3_GAUGE = + new LabelledGaugeNoOpMetric(3, NO_OP_GAUGE); @Override public LabelledMetric createLabelledCounter( @@ -90,6 +100,28 @@ public class NoOpMetricsSystem implements ObservableMetricsSystem { final String help, final DoubleSupplier valueSupplier) {} + @Override + public LabelledGauge createLabelledGauge( + final MetricCategory category, + final String name, + final String help, + final String... labelNames) { + return getLabelledGauge(labelNames.length); + } + + public static LabelledGauge getLabelledGauge(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 LabelledGaugeNoOpMetric(labelCount, NO_OP_GAUGE); + } + } + @Override public Stream streamObservations(final MetricCategory category) { return Stream.empty(); @@ -123,4 +155,28 @@ public class NoOpMetricsSystem implements ObservableMetricsSystem { return fakeMetric; } } + + public static class LabelledGaugeNoOpMetric implements LabelledGauge { + final int labelCount; + final List labelValuesCache = new ArrayList<>(); + + public LabelledGaugeNoOpMetric(final int labelCount, final LabelledGauge fakeMetric) { + this.labelCount = labelCount; + this.fakeMetric = fakeMetric; + } + + final LabelledGauge fakeMetric; + + @Override + public void labels(final DoubleSupplier valueSupplier, final String... labelValues) { + final String labelValuesString = String.join(",", labelValues); + Preconditions.checkArgument( + !labelValuesCache.contains(labelValuesString), + "Received label values that were already in use " + labelValuesString); + Preconditions.checkArgument( + labelValues.length == labelCount, + "The count of labels used must match the count of labels expected."); + Preconditions.checkNotNull(valueSupplier, "No valueSupplier specified"); + } + } } diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/noop/NoOpValueCollector.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/noop/NoOpValueCollector.java new file mode 100644 index 0000000000..43342ba9b4 --- /dev/null +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/noop/NoOpValueCollector.java @@ -0,0 +1,35 @@ +/* + * 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.noop; + +import org.hyperledger.besu.plugin.services.metrics.LabelledGauge; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.DoubleSupplier; + +public class NoOpValueCollector implements LabelledGauge { + private final List labelValuesCreated = new ArrayList<>(); + + @Override + public synchronized void labels(final DoubleSupplier valueSupplier, final String... labelValues) { + final String labelValuesString = String.join(",", labelValues); + if (labelValuesCreated.contains(labelValuesString)) { + throw new IllegalArgumentException( + String.format("A gauge has already been created for label values %s", labelValuesString)); + } + labelValuesCreated.add(labelValuesString); + } +} diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetryGauge.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetryGauge.java new file mode 100644 index 0000000000..f82c480fbc --- /dev/null +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetryGauge.java @@ -0,0 +1,73 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.metrics.opentelemetry; + +import org.hyperledger.besu.plugin.services.metrics.LabelledGauge; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.DoubleSupplier; + +import com.google.common.base.Preconditions; +import io.opentelemetry.api.metrics.AsynchronousInstrument; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.common.Labels; +import io.opentelemetry.api.metrics.common.LabelsBuilder; + +public class OpenTelemetryGauge implements LabelledGauge { + private final List labelNames; + private final Map observationsMap = new ConcurrentHashMap<>(); + + public OpenTelemetryGauge( + final String metricName, + final String help, + final Meter meter, + final List labelNames) { + this.labelNames = labelNames; + + meter + .doubleValueObserverBuilder(metricName) + .setDescription(help) + .setUpdater(this::updater) + .build(); + } + + @Override + public void labels(final DoubleSupplier valueSupplier, final String... labelValues) { + Preconditions.checkArgument( + labelValues.length == labelNames.size(), + "label values and label names need the same number of elements"); + final Labels labels = getLabels(labelValues); + if (observationsMap.putIfAbsent(labels, valueSupplier) != null) { + throw new IllegalStateException( + "Already registered a gauge with labels " + Arrays.toString(labelValues)); + } + } + + private Labels getLabels(final String... labelValues) { + final LabelsBuilder labelsBuilder = Labels.builder(); + for (int i = 0; i < labelNames.size(); i++) { + labelsBuilder.put(labelNames.get(i), labelValues[i]); + } + return labelsBuilder.build(); + } + + private void updater(final AsynchronousInstrument.DoubleResult doubleResult) { + observationsMap.forEach( + (labels, valueSupplier) -> doubleResult.observe(valueSupplier.getAsDouble(), labels)); + } +} diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetrySystem.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetrySystem.java index d4e9fd9c40..acfcbef652 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetrySystem.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetrySystem.java @@ -20,6 +20,7 @@ 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.LabelledGauge; import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; import org.hyperledger.besu.plugin.services.metrics.MetricCategory; import org.hyperledger.besu.plugin.services.metrics.OperationTimer; @@ -241,6 +242,22 @@ public class OpenTelemetrySystem implements ObservableMetricsSystem { } } + @Override + public LabelledGauge createLabelledGauge( + final MetricCategory category, + final String name, + final String help, + final String... labelNames) { + LOG.trace("Creating a labelled gauge {}", name); + if (isCategoryEnabled(category)) { + final OpenTelemetryGauge gauge = + new OpenTelemetryGauge( + name, help, meterSdkProvider.get(category.getName()), List.of(labelNames)); + return gauge; + } + return NoOpMetricsSystem.getLabelledGauge(labelNames.length); + } + @Override public Set getEnabledCategories() { return enabledCategories; diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/CurrentValueCollector.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/CurrentValueCollector.java index 30b2606e74..fdcc32bafd 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/CurrentValueCollector.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/CurrentValueCollector.java @@ -28,18 +28,31 @@ class CurrentValueCollector extends Collector { private final String metricName; private final String help; private final DoubleSupplier valueSupplier; + private final List labelNames; + private final List labelValues; public CurrentValueCollector( final String metricName, final String help, final DoubleSupplier valueSupplier) { + this(metricName, help, emptyList(), emptyList(), valueSupplier); + } + + public CurrentValueCollector( + final String metricName, + final String help, + final List labelNames, + final List labelValues, + final DoubleSupplier valueSupplier) { this.metricName = metricName; this.help = help; this.valueSupplier = valueSupplier; + this.labelNames = labelNames; + this.labelValues = labelValues; } @Override public List collect() { final Sample sample = - new Sample(metricName, emptyList(), emptyList(), valueSupplier.getAsDouble()); + new Sample(metricName, labelNames, labelValues, valueSupplier.getAsDouble()); return singletonList( new MetricFamilySamples(metricName, Type.GAUGE, help, singletonList(sample))); } diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusGauge.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusGauge.java new file mode 100644 index 0000000000..b46398ef90 --- /dev/null +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusGauge.java @@ -0,0 +1,63 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.metrics.prometheus; + +import org.hyperledger.besu.plugin.services.metrics.LabelledGauge; + +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; + +public class PrometheusGauge extends Collector implements LabelledGauge { + private final String metricName; + private final String help; + private final List labelNames; + private final Map, DoubleSupplier> observationsMap = new ConcurrentHashMap<>(); + + public PrometheusGauge( + final String metricName, final String help, final List labelNames) { + this.metricName = metricName; + this.help = help; + this.labelNames = labelNames; + } + + @Override + public synchronized void labels(final DoubleSupplier valueSupplier, final String... labelValues) { + if (labelValues.length != labelNames.size()) { + throw new IllegalArgumentException( + "Label values and label names must be the same cardinality"); + } + 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 collect() { + final List samples = new ArrayList<>(); + observationsMap.forEach( + (labels, valueSupplier) -> + samples.add( + new MetricFamilySamples.Sample( + metricName, labelNames, labels, valueSupplier.getAsDouble()))); + return List.of(new MetricFamilySamples(metricName, Type.GAUGE, help, samples)); + } +} diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystem.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystem.java index b4be6eba0b..58afbd259a 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystem.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystem.java @@ -18,6 +18,7 @@ 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.LabelledGauge; import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; import org.hyperledger.besu.plugin.services.metrics.MetricCategory; import org.hyperledger.besu.plugin.services.metrics.OperationTimer; @@ -141,6 +142,21 @@ public class PrometheusMetricsSystem implements ObservableMetricsSystem { } } + @Override + public LabelledGauge createLabelledGauge( + final MetricCategory category, + final String name, + final String help, + final String... labelNames) { + final String metricName = convertToPrometheusName(category, name); + if (isCategoryEnabled(category)) { + final PrometheusGauge gauge = new PrometheusGauge(metricName, help, List.of(labelNames)); + addCollectorUnchecked(category, gauge); + return gauge; + } + return NoOpMetricsSystem.getLabelledGauge(labelNames.length); + } + public void addCollector( final MetricCategory category, final Supplier metricSupplier) { if (isCategoryEnabled(category)) { diff --git a/metrics/core/src/test-support/java/org/hyperledger/besu/metrics/StubMetricsSystem.java b/metrics/core/src/test-support/java/org/hyperledger/besu/metrics/StubMetricsSystem.java index 492ebd3dd8..9e4b9d1ef9 100644 --- a/metrics/core/src/test-support/java/org/hyperledger/besu/metrics/StubMetricsSystem.java +++ b/metrics/core/src/test-support/java/org/hyperledger/besu/metrics/StubMetricsSystem.java @@ -18,6 +18,7 @@ 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.LabelledGauge; import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; import org.hyperledger.besu.plugin.services.metrics.MetricCategory; import org.hyperledger.besu.plugin.services.metrics.OperationTimer; @@ -44,6 +45,15 @@ public class StubMetricsSystem implements ObservableMetricsSystem { return counters.computeIfAbsent(name, key -> new StubLabelledCounter()); } + @Override + public LabelledGauge createLabelledGauge( + final MetricCategory category, + final String name, + final String help, + final String... labelNames) { + return NoOpMetricsSystem.getLabelledGauge(labelNames.length); + } + public long getCounterValue(final String name, final String... labels) { final StubLabelledCounter labelledCounter = counters.get(name); if (labelledCounter == null) { diff --git a/metrics/core/src/test/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetryMetricsSystemTest.java b/metrics/core/src/test/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetryMetricsSystemTest.java index 93323116de..3226ce859f 100644 --- a/metrics/core/src/test/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetryMetricsSystemTest.java +++ b/metrics/core/src/test/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetryMetricsSystemTest.java @@ -31,11 +31,13 @@ import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.plugin.services.metrics.Counter; +import org.hyperledger.besu.plugin.services.metrics.LabelledGauge; import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; import org.hyperledger.besu.plugin.services.metrics.OperationTimer; import java.util.Collections; import java.util.Comparator; +import java.util.List; import com.google.common.collect.ImmutableSet; import org.junit.Test; @@ -177,6 +179,21 @@ public class OpenTelemetryMetricsSystemTest { .containsExactlyInAnyOrder(new Observation(RPC, "myValue", 7.0, emptyList())); } + @Test + public void shouldCreateLabelledGauge() { + LabelledGauge labelledGauge = + metricsSystem.createLabelledGauge(RPC, "gaugeName", "help", "a", "b"); + labelledGauge.labels(() -> 1.0, "a1", "b1"); + labelledGauge.labels(() -> 11.0, "a2", "b2"); + labelledGauge.labels(() -> 21.0, "a3", "b3"); + + assertThat(metricsSystem.streamObservations()) + .containsExactlyInAnyOrder( + new Observation(RPC, "gaugeName", 1.0, List.of("a1", "b1")), + new Observation(RPC, "gaugeName", 11.0, List.of("a2", "b2")), + new Observation(RPC, "gaugeName", 21.0, List.of("a3", "b3"))); + } + @Test public void shouldOnlyObserveEnabledMetrics() { final MetricsConfiguration metricsConfiguration = diff --git a/metrics/core/src/test/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystemTest.java b/metrics/core/src/test/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystemTest.java index 8d9aa601bf..f6c6218267 100644 --- a/metrics/core/src/test/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystemTest.java +++ b/metrics/core/src/test/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystemTest.java @@ -32,11 +32,13 @@ import org.hyperledger.besu.metrics.Observation; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.plugin.services.metrics.Counter; +import org.hyperledger.besu.plugin.services.metrics.LabelledGauge; import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; import org.hyperledger.besu.plugin.services.metrics.OperationTimer; import java.util.Collections; import java.util.Comparator; +import java.util.List; import com.google.common.collect.ImmutableSet; import org.junit.Test; @@ -96,6 +98,33 @@ public class PrometheusMetricsSystemTest { new Observation(PEERS, "connected", 1.0, singletonList("value2"))); } + @Test + public void shouldCreateSeparateObservationsForEachLabelledGaugeValue() { + final LabelledGauge gauge = + metricsSystem.createLabelledGauge(PEERS, "test", "test help", "a", "b", "c"); + final double value1 = 1.0; + final double value2 = 11.0; + + gauge.labels(() -> value1, "a1", "b1", "c1"); + gauge.labels(() -> value2, "a2", "b2", "c2"); + + assertThat(metricsSystem.streamObservations()) + .containsExactlyInAnyOrder( + new Observation(PEERS, "test", 1.0, List.of("a1", "b1", "c1")), + new Observation(PEERS, "test", 11.0, List.of("a2", "b2", "c2"))); + } + + @Test + public void shouldNotUseSameLabelsTwiceOnSameGauge() { + final LabelledGauge gauge = + metricsSystem.createLabelledGauge(PEERS, "test", "test help", "a", "b", "c"); + final double value1 = 1.0; + + gauge.labels(() -> value1, "a1", "b1", "c1"); + assertThatThrownBy(() -> gauge.labels(() -> value1, "a1", "b1", "c1")) + .isInstanceOf(IllegalArgumentException.class); + } + @Test public void shouldIncrementCounterBySpecifiedAmount() { final Counter counter = metricsSystem.createCounter(PEERS, "connected", "Some help string"); diff --git a/plugin-api/build.gradle b/plugin-api/build.gradle index 7576cf7cb1..c3df33a012 100644 --- a/plugin-api/build.gradle +++ b/plugin-api/build.gradle @@ -64,7 +64,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 = 'dIKkR29FARmuawnqoO0BVlVqyrDWb6WRgQu9cfAb+o8=' + knownHash = 'zpv92JZmlU6gPifGcPG5ATE6Lv/ruWdJtjLk3EVXO4g=' } check.dependsOn('checkAPIChanges') diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/MetricsSystem.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/MetricsSystem.java index 83427a323c..a72eb182b0 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/MetricsSystem.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/MetricsSystem.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.plugin.services; import org.hyperledger.besu.plugin.services.metrics.Counter; +import org.hyperledger.besu.plugin.services.metrics.LabelledGauge; import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; import org.hyperledger.besu.plugin.services.metrics.MetricCategory; import org.hyperledger.besu.plugin.services.metrics.OperationTimer; @@ -51,6 +52,18 @@ public interface MetricsSystem extends BesuService { LabelledMetric createLabelledCounter( MetricCategory category, String name, String help, String... labelNames); + /** + * Creates a Gauge with assigned labels. + * + * @param category The {@link MetricCategory} this gauge is assigned to. + * @param name A name for this metric. + * @param help A human readable description of the metric. + * @param labelNames An array of labels to assign to the Gauge. + * @return The created LabelledGauge instance. + */ + LabelledGauge createLabelledGauge( + MetricCategory category, String name, String help, String... labelNames); + /** * Creates a Timer. * diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/metrics/LabelledGauge.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/metrics/LabelledGauge.java new file mode 100644 index 0000000000..886340f7b9 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/metrics/LabelledGauge.java @@ -0,0 +1,21 @@ +/* + * 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.plugin.services.metrics; + +import java.util.function.DoubleSupplier; + +public interface LabelledGauge { + void labels(final DoubleSupplier valueSupplier, final String... labelValues); +}