This is an automated email from the ASF dual-hosted git repository.
HTHou pushed a commit to branch codex/prometheus
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/codex/prometheus by this push:
new d38c50e90d9 Fix metric scrape parser for query labels
d38c50e90d9 is described below
commit d38c50e90d988a27a3aac70a7b50734406e14765
Author: HTHou <[email protected]>
AuthorDate: Fri May 15 11:17:31 2026 +0800
Fix metric scrape parser for query labels
---
.../iotdb/metricscrape/PrometheusTextParser.java | 52 +++++++++++++++++++++-
.../metricscrape/PrometheusTextParserTest.java | 22 +++++++++
2 files changed, 73 insertions(+), 1 deletion(-)
diff --git
a/external-service-impl/metric-scrape/src/main/java/org/apache/iotdb/metricscrape/PrometheusTextParser.java
b/external-service-impl/metric-scrape/src/main/java/org/apache/iotdb/metricscrape/PrometheusTextParser.java
index 8b553a7ff25..334f07a3c7c 100644
---
a/external-service-impl/metric-scrape/src/main/java/org/apache/iotdb/metricscrape/PrometheusTextParser.java
+++
b/external-service-impl/metric-scrape/src/main/java/org/apache/iotdb/metricscrape/PrometheusTextParser.java
@@ -217,7 +217,11 @@ public class PrometheusTextParser {
while (position < line.length()) {
char c = line.charAt(position++);
if (c == '"') {
- return builder.toString();
+ if (isLabelValueTerminator()) {
+ return builder.toString();
+ }
+ builder.append(c);
+ continue;
}
if (c == '\\') {
if (position >= line.length()) {
@@ -244,6 +248,45 @@ public class PrometheusTextParser {
throw new IllegalArgumentException("Unclosed Prometheus label value at
line " + lineNumber);
}
+ private boolean isLabelValueTerminator() {
+ int next = skipWhitespace(position);
+ if (next >= line.length()) {
+ return true;
+ }
+ if (line.charAt(next) == '}') {
+ return next + 1 >= line.length() ||
Character.isWhitespace(line.charAt(next + 1));
+ }
+ if (line.charAt(next) != ',') {
+ return false;
+ }
+ int afterComma = skipWhitespace(next + 1);
+ return afterComma >= line.length()
+ || line.charAt(afterComma) == '}'
+ || isLabelAssignmentStart(afterComma);
+ }
+
+ private boolean isLabelAssignmentStart(int start) {
+ if (start >= line.length() || line.charAt(start) == '"') {
+ return false;
+ }
+ int position = start;
+ while (position < line.length()) {
+ char c = line.charAt(position);
+ if (c == '=') {
+ String labelName = line.substring(start, position).trim();
+ int valueStart = skipWhitespace(position + 1);
+ return !labelName.isEmpty()
+ && valueStart < line.length()
+ && line.charAt(valueStart) == '"';
+ }
+ if (c == ',' || c == '}') {
+ return false;
+ }
+ position++;
+ }
+ return false;
+ }
+
private double parseValue() {
String token = parseToken("sample value");
return parsePrometheusDouble(token, lineNumber);
@@ -279,6 +322,13 @@ public class PrometheusTextParser {
}
}
+ private int skipWhitespace(int position) {
+ while (position < line.length() &&
Character.isWhitespace(line.charAt(position))) {
+ position++;
+ }
+ return position;
+ }
+
private void expect(char expected) {
if (position >= line.length() || line.charAt(position) != expected) {
throw new IllegalArgumentException(
diff --git
a/external-service-impl/metric-scrape/src/test/java/org/apache/iotdb/metricscrape/PrometheusTextParserTest.java
b/external-service-impl/metric-scrape/src/test/java/org/apache/iotdb/metricscrape/PrometheusTextParserTest.java
index 237d2d6495d..9a114584956 100644
---
a/external-service-impl/metric-scrape/src/test/java/org/apache/iotdb/metricscrape/PrometheusTextParserTest.java
+++
b/external-service-impl/metric-scrape/src/test/java/org/apache/iotdb/metricscrape/PrometheusTextParserTest.java
@@ -105,6 +105,28 @@ public class PrometheusTextParserTest {
assertEquals("request_duration_seconds_count",
samples.get(1).getMetricName());
}
+ @Test
+ public void testParseQueryLabelValueWithUnescapedQuote() {
+ String text =
+ "# HELP performance_overview_seconds\n"
+ + "# TYPE performance_overview_seconds summary\n"
+ +
"performance_overview_seconds{interface=\"executeQueryStatement\","
+ + "type=\"Query{selectItems=[\"s1\", \"s2\"],
from=Optional[Table{d1}]}\","
+ + "quantile=\"0.5\",} 1\n";
+
+ List<PrometheusSample> samples = new PrometheusTextParser().parse(text,
100);
+
+ assertEquals(1, samples.size());
+ assertEquals("performance_overview_seconds",
samples.get(0).getMetricFamilyName());
+ assertEquals("performance_overview_seconds",
samples.get(0).getMetricName());
+ assertEquals("executeQueryStatement",
samples.get(0).getLabels().get("interface"));
+ assertEquals(
+ "Query{selectItems=[\"s1\", \"s2\"], from=Optional[Table{d1}]}",
+ samples.get(0).getLabels().get("type"));
+ assertEquals("0.5", samples.get(0).getLabels().get("quantile"));
+ assertEquals(1.0, samples.get(0).getValue(), 0.0);
+ }
+
@Test(expected = IllegalArgumentException.class)
public void testRejectDuplicateLabel() {
new PrometheusTextParser().parse("up{job=\"a\",job=\"b\"} 1", 100);