You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hugegraph.apache.org by ji...@apache.org on 2026/10/14 18:10:48 UTC
(hugegraph) branch master updated: fix(server): sanitize Prometheus metric names (#3344)
This is an automated email from the ASF dual-hosted git repository.
imbajin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hugegraph.git
The following commit(s) were added to refs/heads/master by this push:
new e1d9c696c fix(server): sanitize Prometheus metric names (#3344)
e1d9c696c is described below
commit e1d9c696c1611925f904d9d05cf39d0e058a4865
Author: Arshil <ar...@gmail.com>
AuthorDate: Wed Oct 14 23:40:43 2026 +0530
fix(server): sanitize Prometheus metric names (#3344)
Cache metric keys such as "...Cache.schema~DEFAULT-hugegraph.hits" kept
the '~' when written to /metrics, and Prometheus rejected the whole
scrape. Each of the two name helpers stripped a fixed set of
characters, so any character outside that set needed its own patch.
---
.../apache/hugegraph/api/metrics/MetricsAPI.java | 13 ++-
.../org/apache/hugegraph/metrics/MetricsUtil.java | 35 +++++--
.../org/apache/hugegraph/unit/UnitTestSuite.java | 2 +
.../hugegraph/unit/util/MetricsUtilTest.java | 104 +++++++++++++++++++++
4 files changed, 137 insertions(+), 17 deletions(-)
diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/metrics/MetricsAPI.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/metrics/MetricsAPI.java
index b457b66bf..1357a8558 100644
--- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/metrics/MetricsAPI.java
+++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/metrics/MetricsAPI.java
@@ -40,8 +40,7 @@ import static org.apache.hugegraph.metrics.MetricsUtil.STR_TYPE;
import static org.apache.hugegraph.metrics.MetricsUtil.UNTYPED;
import static org.apache.hugegraph.metrics.MetricsUtil.VERSION_STR;
import static org.apache.hugegraph.metrics.MetricsUtil.exportSnapshot;
-import static org.apache.hugegraph.metrics.MetricsUtil.replaceDotDashInKey;
-import static org.apache.hugegraph.metrics.MetricsUtil.replaceSlashInKey;
+import static org.apache.hugegraph.metrics.MetricsUtil.sanitizePrometheusMetricName;
import java.util.HashMap;
import java.util.LinkedHashMap;
@@ -252,7 +251,7 @@ public class MetricsAPI extends API {
final Gauge<?> gauge
= reporter.gauges().get(key);
if (gauge != null) {
- helpName = replaceDotDashInKey(key);
+ helpName = sanitizePrometheusMetricName(key);
promMetric.append(STR_HELP)
.append(helpName).append(END_LSTR);
promMetric.append(STR_TYPE)
@@ -266,7 +265,7 @@ public class MetricsAPI extends API {
for (String histogramkey : reporter.histograms().keySet()) {
final Histogram histogram = reporter.histograms().get(histogramkey);
if (histogram != null) {
- helpName = replaceDotDashInKey(histogramkey);
+ helpName = sanitizePrometheusMetricName(histogramkey);
promMetric.append(STR_HELP)
.append(helpName).append(END_LSTR);
promMetric.append(STR_TYPE)
@@ -285,7 +284,7 @@ public class MetricsAPI extends API {
for (String meterkey : reporter.meters().keySet()) {
final Meter metric = reporter.meters().get(meterkey);
if (metric != null) {
- helpName = replaceDotDashInKey(meterkey);
+ helpName = sanitizePrometheusMetricName(meterkey);
promMetric.append(STR_HELP)
.append(helpName).append(END_LSTR);
promMetric.append(STR_TYPE)
@@ -315,7 +314,7 @@ public class MetricsAPI extends API {
final com.codahale.metrics.Timer timer = reporter.timers()
.get(timerkey);
if (timer != null) {
- helpName = replaceDotDashInKey(timerkey);
+ helpName = sanitizePrometheusMetricName(timerkey);
promMetric.append(STR_HELP)
.append(helpName).append(END_LSTR);
promMetric.append(STR_TYPE)
@@ -412,7 +411,7 @@ public class MetricsAPI extends API {
.append(SPACE_STR + "1.0" + END_LSTR);
for (String methodKey : metricMap.keySet()) {
- String metricName = replaceSlashInKey(methodKey);
+ String metricName = sanitizePrometheusMetricName(methodKey);
promMetric.append(STR_HELP)
.append(metricName).append(END_LSTR);
promMetric.append(STR_TYPE)
diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/metrics/MetricsUtil.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/metrics/MetricsUtil.java
index 3721cb1e1..de32a7dba 100644
--- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/metrics/MetricsUtil.java
+++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/metrics/MetricsUtil.java
@@ -17,6 +17,8 @@
package org.apache.hugegraph.metrics;
+import java.util.regex.Pattern;
+
import org.apache.tinkerpop.gremlin.server.util.MetricManager;
import com.codahale.metrics.Counter;
@@ -63,6 +65,9 @@ public class MetricsUtil {
public static final String RIGHT_NAME_STR = ",} ";
public static final String PROM_HELP_NAME = "hugegraph_info";
+ private static final Pattern INVALID_PROMETHEUS_NAME_CHAR =
+ Pattern.compile("[^a-zA-Z0-9_:]");
+
public static <T> Gauge<T> registerGauge(Class<?> clazz, String name,
Gauge<T> gauge) {
return REGISTRY.register(MetricRegistry.name(clazz, name), gauge);
@@ -92,19 +97,29 @@ public class MetricsUtil {
return REGISTRY.timer(MetricRegistry.name(clazz, name));
}
- public static String replaceDotDashInKey(String orgKey) {
- return orgKey.replace(".", "_").replace("-", "_").replace("/", "_").replace("$", "_");
- }
-
- public static String replaceSlashInKey(String orgKey) {
- return orgKey.replace("/", "_");
+ /**
+ * Convert a metric key into a valid Prometheus metric name, which must
+ * match [a-zA-Z_:][a-zA-Z0-9_:]*. Every other character becomes '_',
+ * and a leading digit gets a '_' prefix. Only call this when writing
+ * Prometheus output; registry keys stay unchanged.
+ */
+ public static String sanitizePrometheusMetricName(String originalName) {
+ String sanitized = INVALID_PROMETHEUS_NAME_CHAR.matcher(originalName)
+ .replaceAll("_");
+ if (sanitized.isEmpty()) {
+ return "_";
+ }
+ if (Character.isDigit(sanitized.charAt(0))) {
+ return "_" + sanitized;
+ }
+ return sanitized;
}
public static void writePrometheusFormat(StringBuilder promeMetrics, MetricRegistry registry) {
// gauges
registry.getGauges().forEach((key, gauge) -> {
if (gauge != null) {
- String helpName = replaceDotDashInKey(key);
+ String helpName = sanitizePrometheusMetricName(key);
promeMetrics.append(STR_HELP)
.append(helpName).append(END_LSTR);
promeMetrics.append(STR_TYPE)
@@ -117,7 +132,7 @@ public class MetricsUtil {
// histograms
registry.getHistograms().forEach((key, histogram) -> {
if (histogram != null) {
- String helpName = replaceDotDashInKey(key);
+ String helpName = sanitizePrometheusMetricName(key);
promeMetrics.append(STR_HELP)
.append(helpName).append(END_LSTR);
promeMetrics.append(STR_TYPE)
@@ -134,7 +149,7 @@ public class MetricsUtil {
// meters
registry.getMeters().forEach((key, metric) -> {
if (metric != null) {
- String helpName = replaceDotDashInKey(key);
+ String helpName = sanitizePrometheusMetricName(key);
promeMetrics.append(STR_HELP)
.append(helpName).append(END_LSTR);
promeMetrics.append(STR_TYPE)
@@ -160,7 +175,7 @@ public class MetricsUtil {
// timer
registry.getTimers().forEach((key, timer) -> {
if (timer != null) {
- String helpName = replaceDotDashInKey(key);
+ String helpName = sanitizePrometheusMetricName(key);
promeMetrics.append(STR_HELP)
.append(helpName).append(END_LSTR);
promeMetrics.append(STR_TYPE)
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java
index 3cd65b0d8..59b025c66 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java
@@ -123,6 +123,7 @@ import org.apache.hugegraph.unit.traversal.ShortestPathTraverserTest;
import org.apache.hugegraph.unit.util.CompressUtilTest;
import org.apache.hugegraph.unit.util.ConsumersTest;
import org.apache.hugegraph.unit.util.JsonUtilTest;
+import org.apache.hugegraph.unit.util.MetricsUtilTest;
import org.apache.hugegraph.unit.util.RateLimiterTest;
import org.apache.hugegraph.unit.util.StringEncodingTest;
import org.apache.hugegraph.unit.util.VersionTest;
@@ -270,6 +271,7 @@ import org.junit.runners.Suite;
JsonUtilTest.class,
StringEncodingTest.class,
CompressUtilTest.class,
+ MetricsUtilTest.class,
RateLimiterTest.FixedTimerWindowRateLimiterTest.class,
RateLimiterTest.FixedWatchWindowRateLimiterTest.class,
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/util/MetricsUtilTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/util/MetricsUtilTest.java
new file mode 100644
index 000000000..dfb515910
--- /dev/null
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/util/MetricsUtilTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.hugegraph.unit.util;
+
+import static org.apache.hugegraph.metrics.MetricsUtil.sanitizePrometheusMetricName;
+
+import java.util.regex.Pattern;
+
+import org.apache.hugegraph.metrics.MetricsUtil;
+import org.apache.hugegraph.testutil.Assert;
+import org.junit.Test;
+
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.MetricRegistry;
+
+public class MetricsUtilTest {
+
+ private static final Pattern VALID_NAME =
+ Pattern.compile("[a-zA-Z_:][a-zA-Z0-9_:]*");
+
+ @Test
+ public void testSanitizeReplacesInvalidChars() {
+ Assert.assertEquals("a_b_c_d_e_f",
+ sanitizePrometheusMetricName("a.b-c/d$e~f"));
+ Assert.assertEquals("org_apache_Cache_schema_DEFAULT_hugegraph_hits",
+ sanitizePrometheusMetricName(
+ "org.apache.Cache.schema~DEFAULT-hugegraph.hits"));
+ Assert.assertEquals("_graphs_1_x",
+ sanitizePrometheusMetricName("/graphs/1~x"));
+ }
+
+ @Test
+ public void testSanitizeKeepsValidName() {
+ Assert.assertEquals("valid_name:total",
+ sanitizePrometheusMetricName("valid_name:total"));
+ Assert.assertEquals("Mixed_Case_09",
+ sanitizePrometheusMetricName("Mixed_Case_09"));
+ }
+
+ @Test
+ public void testSanitizePrefixesLeadingDigit() {
+ Assert.assertEquals("_9cache_hits",
+ sanitizePrometheusMetricName("9cache.hits"));
+ }
+
+ @Test
+ public void testSanitizeEmptyName() {
+ Assert.assertEquals("_", sanitizePrometheusMetricName(""));
+ }
+
+ @Test
+ public void testWritePrometheusFormatWithTildeInGaugeName() {
+ MetricRegistry registry = new MetricRegistry();
+ String key = "org.apache.hugegraph.Cache.schema~DEFAULT-hugegraph.hits";
+ registry.register(key, (Gauge<Long>) () -> 42L);
+
+ StringBuilder output = new StringBuilder();
+ MetricsUtil.writePrometheusFormat(output, registry);
+ String text = output.toString();
+
+ String name = "org_apache_hugegraph_Cache_schema_DEFAULT_hugegraph_hits";
+ Assert.assertFalse(text, text.contains("~"));
+ Assert.assertEquals("# HELP " + name + "\n" +
+ "# TYPE " + name + " gauge\n" +
+ name + " 42\n", text);
+ }
+
+ @Test
+ public void testWritePrometheusFormatProducesValidNames() {
+ MetricRegistry registry = new MetricRegistry();
+ registry.register("cache~a.size", (Gauge<Integer>) () -> 1);
+ registry.histogram("api/graphs/1~x");
+ registry.meter("9meter-rate");
+ registry.timer("timer$latency~p");
+
+ StringBuilder output = new StringBuilder();
+ MetricsUtil.writePrometheusFormat(output, registry);
+
+ for (String line : output.toString().split("\n")) {
+ String name;
+ if (line.startsWith("# HELP ") || line.startsWith("# TYPE ")) {
+ name = line.substring(7).split(" ")[0];
+ } else {
+ name = line.split("[{ ]")[0];
+ }
+ Assert.assertTrue(line, VALID_NAME.matcher(name).matches());
+ }
+ }
+}