Skip to content

Commit

Permalink
Add a labelled gauge to metrics (hyperledger#2646)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
rolfyone authored Aug 20, 2021
1 parent 4a22b41 commit 92955d2
Show file tree
Hide file tree
Showing 14 changed files with 366 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
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;
Expand All @@ -31,6 +34,7 @@
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;

Expand All @@ -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(
Expand Down Expand Up @@ -90,6 +100,28 @@ public void createGauge(
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();
Expand Down Expand Up @@ -123,4 +155,28 @@ public T labels(final String... labels) {
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");
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
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;
Expand Down Expand Up @@ -241,6 +242,22 @@ public void createGauge(
}
}

@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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,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.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;
Expand Down Expand Up @@ -141,6 +142,21 @@ public void createGauge(
}
}

@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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

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;
Expand All @@ -44,6 +45,15 @@ public LabelledMetric<Counter> createLabelledCounter(
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) {
Expand Down
Loading

0 comments on commit 92955d2

Please sign in to comment.