GERONIMO-6654 simple way to override prometheus export
diff --git a/README.adoc b/README.adoc
index b001d51..d3a7030 100644
--- a/README.adoc
+++ b/README.adoc
@@ -10,3 +10,23 @@
<version>${metrics.version}</version>
</parent>
----
+
+
+== Renaming Prometheus keys
+
+Prometheus exporter can rename metrics keys providing
+a properties file in the classpath as resource `META-INF/geronimo-metrics/prometheus-mapping.properties`
+or using system properties (with the prefix `geronimo.metrics.prometheus.mapping.`).
+
+The key is the metric key (with the registry type as prefix) and the value the replacement key:
+
+[source]
+----
+base\:my_metric = app:metric
+----
+
+TIP: since it is a properties format the `:` must be escaped.
+
+The specific key `geronimo.metrics.filter.prefix` can take
+a list (comma separated values) of metrics prefixes to filter (whitelist)
+exported metrics.
diff --git a/geronimo-metrics-common/src/main/java/org/apache/geronimo/microprofile/metrics/common/jaxrs/MetricsEndpoints.java b/geronimo-metrics-common/src/main/java/org/apache/geronimo/microprofile/metrics/common/jaxrs/MetricsEndpoints.java
index 32f0315..c2fdec4 100644
--- a/geronimo-metrics-common/src/main/java/org/apache/geronimo/microprofile/metrics/common/jaxrs/MetricsEndpoints.java
+++ b/geronimo-metrics-common/src/main/java/org/apache/geronimo/microprofile/metrics/common/jaxrs/MetricsEndpoints.java
@@ -48,7 +48,7 @@
private MetricRegistry vendorRegistry;
private MetricRegistry applicationRegistry;
- private final PrometheusFormatter prometheus = new PrometheusFormatter();
+ private final PrometheusFormatter prometheus = new PrometheusFormatter().enableOverriding();
public void setBaseRegistry(final MetricRegistry baseRegistry) {
this.baseRegistry = baseRegistry;
diff --git a/geronimo-metrics-common/src/main/java/org/apache/geronimo/microprofile/metrics/common/prometheus/PrometheusFormatter.java b/geronimo-metrics-common/src/main/java/org/apache/geronimo/microprofile/metrics/common/prometheus/PrometheusFormatter.java
index afd1e96..f9f0b3f 100644
--- a/geronimo-metrics-common/src/main/java/org/apache/geronimo/microprofile/metrics/common/prometheus/PrometheusFormatter.java
+++ b/geronimo-metrics-common/src/main/java/org/apache/geronimo/microprofile/metrics/common/prometheus/PrometheusFormatter.java
@@ -21,9 +21,16 @@
import static java.util.Locale.ROOT;
import static java.util.Optional.of;
import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toList;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.Properties;
import java.util.function.Function;
+import java.util.function.Predicate;
import java.util.stream.Stream;
import org.eclipse.microprofile.metrics.Counter;
@@ -40,62 +47,104 @@
// this is so weird to have this format built-in but not mainstream ones,
// todo: pby make it dropped from the spec
// note: this is a simplified serialization flavor and it can need some more love
+// todo: cache all the keys, can easily be done decorating the registry and enriching metadata (ExtendedMetadata)
public class PrometheusFormatter {
+ private final Map<String, String> keyMapping = new HashMap<>();
+ private Predicate<String> prefixFilter = null;
+
+ public PrometheusFormatter enableOverriding(final Properties properties) {
+ properties.stringPropertyNames().forEach(k -> keyMapping.put(k, properties.getProperty(k)));
+ afterOverride();
+ return this;
+ }
+
+ public PrometheusFormatter enableOverriding() {
+ try (final InputStream source = Thread.currentThread().getContextClassLoader()
+ .getResourceAsStream("META-INF/geronimo-metrics/prometheus-mapping.properties")) {
+ if (source != null) {
+ final Properties properties = new Properties();
+ properties.load(source);
+ enableOverriding(properties);
+ }
+ } catch (final IOException e) {
+ // no-op
+ }
+ System.getProperties().stringPropertyNames().stream()
+ .filter(it -> it.startsWith("geronimo.metrics.prometheus.mapping."))
+ .forEach(k -> keyMapping.put(k.substring("geronimo.metrics.prometheus.mapping.".length()), System.getProperty(k)));
+ afterOverride();
+ return this;
+ }
+
+ private void afterOverride() {
+ final String prefix = keyMapping.get("geronimo.metrics.filter.prefix");
+ if (prefix == null) {
+ prefixFilter = null;
+ } else {
+ final List<String> prefixes = Stream.of(prefix.split(","))
+ .map(String::trim)
+ .filter(it -> !it.isEmpty())
+ .collect(toList());
+ final Predicate<String> directPredicate = name -> prefixes.stream().anyMatch(name::startsWith);
+ prefixFilter = name -> directPredicate.test(name) || directPredicate.test(keyMapping.getOrDefault(name, name));
+ }
+ }
+
public StringBuilder toText(final MetricRegistry registry,
final String registryKey,
final Map<String, Metric> entries) {
final Map<String, Metadata> metadatas = registry.getMetadata();
return entries.entrySet().stream()
+ .map(it -> new Entry(metadatas.get(it.getKey()), registryKey + ':' + toPrometheusKey(metadatas.get(it.getKey())), it.getValue()))
+ .filter(it -> prefixFilter == null || prefixFilter.test(it.prometheusKey))
.map(entry -> {
- final Metadata metadata = metadatas.get(entry.getKey());
- final Metric value = entry.getValue();
- switch (metadata.getTypeRaw()) {
+ switch (entry.metadata.getTypeRaw()) {
case COUNTER: {
- final String key = toPrometheusKey(metadata);
+ final String key = toPrometheusKey(entry.metadata);
return new StringBuilder()
- .append(value(registryKey, key, Counter.class.cast(value).getCount(), metadata));
+ .append(value(registryKey, key, Counter.class.cast(entry.metric).getCount(), entry.metadata));
}
case GAUGE: {
- final Object val = Gauge.class.cast(value).getValue();
+ final Object val = Gauge.class.cast(entry.metric).getValue();
if (Number.class.isInstance(val)) {
- final String key = toPrometheusKey(metadata);
+ final String key = toPrometheusKey(entry.metadata);
return new StringBuilder()
- .append(value(registryKey, key, Number.class.cast(val).doubleValue(), metadata));
+ .append(value(registryKey, key, Number.class.cast(val).doubleValue(), entry.metadata));
}
return new StringBuilder();
}
case METERED: {
- final String keyBase = toPrometheus(metadata);
- final String key = keyBase + toUnitSuffix(metadata);
- final Meter meter = Meter.class.cast(value);
+ final String keyBase = toPrometheus(entry.metadata);
+ final String key = keyBase + toUnitSuffix(entry.metadata);
+ final Meter meter = Meter.class.cast(entry.metric);
return new StringBuilder()
- .append(value(registryKey, key + "_count", meter.getCount(), metadata))
- .append(value(registryKey, keyBase + "_rate_per_second", meter.getMeanRate(), metadata))
- .append(value(registryKey, keyBase + "_one_min_rate_per_second", meter.getOneMinuteRate(), metadata))
- .append(value(registryKey, keyBase + "_five_min_rate_per_second", meter.getFiveMinuteRate(), metadata))
- .append(value(registryKey, keyBase + "_fifteen_min_rate_per_second", meter.getFifteenMinuteRate(), metadata));
+ .append(value(registryKey, key + "_count", meter.getCount(), entry.metadata))
+ .append(value(registryKey, keyBase + "_rate_per_second", meter.getMeanRate(), entry.metadata))
+ .append(value(registryKey, keyBase + "_one_min_rate_per_second", meter.getOneMinuteRate(), entry.metadata))
+ .append(value(registryKey, keyBase + "_five_min_rate_per_second", meter.getFiveMinuteRate(), entry.metadata))
+ .append(value(registryKey, keyBase + "_fifteen_min_rate_per_second", meter.getFifteenMinuteRate(), entry.metadata));
}
case TIMER: {
- final String keyBase = toPrometheus(metadata);
- final String keyUnit = toUnitSuffix(metadata);
- final Timer timer = Timer.class.cast(value);
+ final String keyBase = toPrometheus(entry.metadata);
+ final String keyUnit = toUnitSuffix(entry.metadata);
+ final Timer timer = Timer.class.cast(entry.metric);
return new StringBuilder()
- .append(type(registryKey, keyBase + keyUnit + " summary", metadata))
- .append(value(registryKey, keyBase + keyUnit + "_count", timer.getCount(), metadata))
- .append(value(registryKey, keyBase + "_rate_per_second", timer.getMeanRate(), metadata))
- .append(value(registryKey, keyBase + "_one_min_rate_per_second", timer.getOneMinuteRate(), metadata))
- .append(value(registryKey, keyBase + "_five_min_rate_per_second", timer.getFiveMinuteRate(), metadata))
- .append(value(registryKey, keyBase + "_fifteen_min_rate_per_second", timer.getFifteenMinuteRate(), metadata))
- .append(toPrometheus(registryKey, keyBase, keyUnit, timer.getSnapshot(), metadata));
+ .append(type(registryKey, keyBase + keyUnit + " summary", entry.metadata))
+ .append(value(registryKey, keyBase + keyUnit + "_count", timer.getCount(), entry.metadata))
+ .append(value(registryKey, keyBase + "_rate_per_second", timer.getMeanRate(), entry.metadata))
+ .append(value(registryKey, keyBase + "_one_min_rate_per_second", timer.getOneMinuteRate(), entry.metadata))
+ .append(value(registryKey, keyBase + "_five_min_rate_per_second", timer.getFiveMinuteRate(), entry.metadata))
+ .append(value(registryKey, keyBase + "_fifteen_min_rate_per_second", timer.getFifteenMinuteRate(), entry.metadata))
+ .append(toPrometheus(registryKey, keyBase, keyUnit, timer.getSnapshot(), entry.metadata));
}
case HISTOGRAM:
- final String keyBase = toPrometheus(metadata);
- final String keyUnit = toUnitSuffix(metadata);
- final Histogram histogram = Histogram.class.cast(value);
+ final String keyBase = toPrometheus(entry.metadata);
+ final String keyUnit = toUnitSuffix(entry.metadata);
+ final Histogram histogram = Histogram.class.cast(entry.metric);
return new StringBuilder()
- .append(type(registryKey, keyBase + keyUnit + " summary", metadata))
- .append(value(registryKey, keyBase + keyUnit + "_count", histogram.getCount(), metadata))
- .append(toPrometheus(registryKey, keyBase, keyUnit, histogram.getSnapshot(), metadata));
+ .append(type(registryKey, keyBase + keyUnit + " summary", entry.metadata))
+ .append(value(registryKey, keyBase + keyUnit + "_count", histogram.getCount(), entry.metadata))
+ .append(toPrometheus(registryKey, keyBase, keyUnit, histogram.getSnapshot(), entry.metadata));
default:
return new StringBuilder();
}
@@ -133,9 +182,10 @@
private StringBuilder value(final String registryKey, final String key, final double value,
final Metadata metadata) {
+ final String builtKey = registryKey + ':' + key;
return new StringBuilder()
.append(type(registryKey, key, metadata))
- .append(registryKey).append(':').append(key)
+ .append(keyMapping.getOrDefault(builtKey, builtKey))
.append(of(metadata.getTags())
.filter(t -> !t.isEmpty())
.map(t -> t.entrySet().stream()
@@ -146,8 +196,9 @@
}
private StringBuilder type(final String registryKey, final String key, final Metadata metadata) {
+ final String builtKey = registryKey + ':' + key;
final StringBuilder builder = new StringBuilder()
- .append("# TYPE ").append(registryKey).append(':').append(key);
+ .append("# TYPE ").append(keyMapping.getOrDefault(builtKey, builtKey));
if (metadata != null) {
builder.append(' ').append(metadata.getType());
}
@@ -240,4 +291,16 @@
.replace(":_", ":")
.toLowerCase(ROOT);
}
+
+ private static class Entry {
+ private final Metadata metadata;
+ private final String prometheusKey;
+ private final Metric metric;
+
+ private Entry(final Metadata metadata, final String prometheusKey, final Metric metric) {
+ this.metadata = metadata;
+ this.prometheusKey = prometheusKey;
+ this.metric = metric;
+ }
+ }
}
diff --git a/geronimo-metrics-common/src/test/java/org/apache/geronimo/microprofile/metrics/common/prometheus/PrometheusFormatterTest.java b/geronimo-metrics-common/src/test/java/org/apache/geronimo/microprofile/metrics/common/prometheus/PrometheusFormatterTest.java
new file mode 100644
index 0000000..88d3595
--- /dev/null
+++ b/geronimo-metrics-common/src/test/java/org/apache/geronimo/microprofile/metrics/common/prometheus/PrometheusFormatterTest.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.microprofile.metrics.common.prometheus;
+
+import static java.util.Collections.singletonMap;
+import static org.junit.Assert.assertEquals;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.geronimo.microprofile.metrics.common.RegistryImpl;
+import org.eclipse.microprofile.metrics.Gauge;
+import org.eclipse.microprofile.metrics.Metric;
+import org.junit.Test;
+
+public class PrometheusFormatterTest {
+ @Test
+ public void rename() {
+ final PrometheusFormatter prometheusFormatter = new PrometheusFormatter().enableOverriding();
+ final RegistryImpl registry = new RegistryImpl();
+ final Map<String, Metric> metrics = singletonMap("myMetric", (Gauge<Long>) () -> 1234L);
+ metrics.forEach(registry::register);
+ assertEquals(
+ "# TYPE sample:my_metric gauge\nsample:my_metric 1234.0\n",
+ prometheusFormatter.toText(registry, "sample", metrics).toString());
+ System.setProperty("geronimo.metrics.prometheus.mapping.sample:my_metric", "renamed");
+ prometheusFormatter.enableOverriding();
+ assertEquals(
+ "# TYPE renamed gauge\nrenamed 1234.0\n",
+ prometheusFormatter.toText(registry, "sample", metrics).toString());
+ System.clearProperty("sample:my_metric");
+ System.setProperty("geronimo.metrics.prometheus.mapping.sample:my_metric", "renamed");
+ prometheusFormatter.enableOverriding(new Properties() {{
+ setProperty("sample:my_metric", "again");
+ }});
+ assertEquals(
+ "# TYPE again gauge\nagain 1234.0\n",
+ prometheusFormatter.toText(registry, "sample", metrics).toString());
+ }
+
+ @Test
+ public void filter() {
+ final PrometheusFormatter prometheusFormatter = new PrometheusFormatter().enableOverriding();
+ final RegistryImpl registry = new RegistryImpl();
+ final Map<String, Metric> metrics = new LinkedHashMap<>();
+ metrics.put("myMetric1", (Gauge<Long>) () -> 1234L);
+ metrics.put("myMetric2", (Gauge<Long>) () -> 1235L);
+ metrics.forEach(registry::register);
+ assertEquals(
+ "# TYPE sample:my_metric1 gauge\nsample:my_metric1 1234.0\n" +
+ "# TYPE sample:my_metric2 gauge\nsample:my_metric2 1235.0\n",
+ prometheusFormatter.toText(registry, "sample", metrics).toString());
+ prometheusFormatter.enableOverriding(new Properties() {{
+ setProperty("geronimo.metrics.filter.prefix", "sample:my_metric2");
+ }});
+ assertEquals(
+ "# TYPE sample:my_metric2 gauge\nsample:my_metric2 1235.0\n",
+ prometheusFormatter.toText(registry, "sample", metrics).toString());
+ }
+}