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 <paul.harris@consensys.net>

Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>
pull/2661/head
Paul Harris 3 years ago committed by GitHub
parent 4a22b41c2f
commit 92955d2894
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 56
      metrics/core/src/main/java/org/hyperledger/besu/metrics/noop/NoOpMetricsSystem.java
  3. 35
      metrics/core/src/main/java/org/hyperledger/besu/metrics/noop/NoOpValueCollector.java
  4. 73
      metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetryGauge.java
  5. 17
      metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetrySystem.java
  6. 15
      metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/CurrentValueCollector.java
  7. 63
      metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusGauge.java
  8. 16
      metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystem.java
  9. 10
      metrics/core/src/test-support/java/org/hyperledger/besu/metrics/StubMetricsSystem.java
  10. 17
      metrics/core/src/test/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetryMetricsSystemTest.java
  11. 29
      metrics/core/src/test/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystemTest.java
  12. 2
      plugin-api/build.gradle
  13. 13
      plugin-api/src/main/java/org/hyperledger/besu/plugin/services/MetricsSystem.java
  14. 21
      plugin-api/src/main/java/org/hyperledger/besu/plugin/services/metrics/LabelledGauge.java

@ -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)

@ -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<OperationTimer> 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<Counter> 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<Observation> 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<String> 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");
}
}
}

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

@ -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<String> labelNames;
private final Map<Labels, DoubleSupplier> observationsMap = new ConcurrentHashMap<>();
public OpenTelemetryGauge(
final String metricName,
final String help,
final Meter meter,
final List<String> 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));
}
}

@ -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<MetricCategory> getEnabledCategories() {
return enabledCategories;

@ -28,18 +28,31 @@ 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, emptyList(), emptyList(), valueSupplier.getAsDouble());
new Sample(metricName, labelNames, labelValues, valueSupplier.getAsDouble());
return singletonList(
new MetricFamilySamples(metricName, Type.GAUGE, help, singletonList(sample)));
}

@ -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<String> labelNames;
private final Map<List<String>, DoubleSupplier> observationsMap = new ConcurrentHashMap<>();
public PrometheusGauge(
final String metricName, final String help, final List<String> 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<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.GAUGE, help, samples));
}
}

@ -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<Collector> metricSupplier) {
if (isCategoryEnabled(category)) {

@ -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) {

@ -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 =

@ -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");

@ -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')

@ -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<Counter> 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.
*

@ -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);
}
Loading…
Cancel
Save