Skip to content

Commit 48fbef3

Browse files
committed
move name escaping to separate class
Signed-off-by: Gregor Zeitlinger <[email protected]>
1 parent 9413db6 commit 48fbef3

File tree

8 files changed

+159
-144
lines changed

8 files changed

+159
-144
lines changed

prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CallbackMetricTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package io.prometheus.metrics.core.metrics;
22

3-
import org.junit.jupiter.api.Test;
4-
53
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
64

5+
import org.junit.jupiter.api.Test;
6+
77
class CallbackMetricTest {
88

99
@Test

prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package io.prometheus.metrics.exporter.pushgateway;
22

33
import static io.prometheus.metrics.exporter.pushgateway.Scheme.HTTP;
4-
import static io.prometheus.metrics.model.snapshots.PrometheusNames.escapeName;
4+
import static io.prometheus.metrics.model.snapshots.NameEscaper.escapeName;
55
import static java.util.Objects.requireNonNull;
66

77
import io.prometheus.metrics.config.EscapingScheme;

prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,6 @@ private void validate() {
125125
}
126126

127127
MetricMetadata escape(EscapingScheme escapingScheme) {
128-
return new MetricMetadata(PrometheusNames.escapeName(name, escapingScheme), help, unit);
128+
return new MetricMetadata(NameEscaper.escapeName(name, escapingScheme), help, unit);
129129
}
130130
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package io.prometheus.metrics.model.snapshots;
2+
3+
import io.prometheus.metrics.config.EscapingScheme;
4+
5+
public class NameEscaper {
6+
/**
7+
* Escapes the incoming name according to the provided escaping scheme. Depending on the rules of
8+
* escaping, this may cause no change in the string that is returned (especially NO_ESCAPING,
9+
* which by definition is a noop). This method does not do any validation of the name.
10+
*/
11+
public static String escapeName(String name, EscapingScheme scheme) {
12+
if (name.isEmpty() || !needsEscaping(name, scheme)) {
13+
return name;
14+
}
15+
16+
StringBuilder escaped = new StringBuilder();
17+
switch (scheme) {
18+
case ALLOW_UTF8:
19+
return name;
20+
case UNDERSCORE_ESCAPING:
21+
for (int i = 0; i < name.length(); ) {
22+
int c = name.codePointAt(i);
23+
if (PrometheusNames.isValidLegacyChar(c, i)) {
24+
escaped.appendCodePoint(c);
25+
} else {
26+
escaped.append('_');
27+
}
28+
i += Character.charCount(c);
29+
}
30+
return escaped.toString();
31+
case DOTS_ESCAPING:
32+
// Do not early return for legacy valid names, we still escape underscores.
33+
for (int i = 0; i < name.length(); ) {
34+
int c = name.codePointAt(i);
35+
if (c == '_') {
36+
escaped.append("__");
37+
} else if (c == '.') {
38+
escaped.append("_dot_");
39+
} else if (PrometheusNames.isValidLegacyChar(c, i)) {
40+
escaped.appendCodePoint(c);
41+
} else {
42+
escaped.append("__");
43+
}
44+
i += Character.charCount(c);
45+
}
46+
return escaped.toString();
47+
case VALUE_ENCODING_ESCAPING:
48+
escaped.append("U__");
49+
for (int i = 0; i < name.length(); ) {
50+
int c = name.codePointAt(i);
51+
if (c == '_') {
52+
escaped.append("__");
53+
} else if (PrometheusNames.isValidLegacyChar(c, i)) {
54+
escaped.appendCodePoint(c);
55+
} else if (!PrometheusNames.isValidUtf8Char(c)) {
56+
escaped.append("_FFFD_");
57+
} else {
58+
escaped.append('_');
59+
escaped.append(Integer.toHexString(c));
60+
escaped.append('_');
61+
}
62+
i += Character.charCount(c);
63+
}
64+
return escaped.toString();
65+
default:
66+
throw new IllegalArgumentException("Invalid escaping scheme " + scheme);
67+
}
68+
}
69+
70+
static boolean needsEscaping(String name, EscapingScheme scheme) {
71+
return !PrometheusNames.isValidLegacyMetricName(name)
72+
|| (scheme == EscapingScheme.DOTS_ESCAPING && (name.contains(".") || name.contains("_")));
73+
}
74+
}

prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNames.java

Lines changed: 2 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public static String validateUnitName(String name) {
155155
* @return the name with dots replaced by underscores.
156156
*/
157157
public static String prometheusName(String name) {
158-
return escapeName(name, EscapingScheme.UNDERSCORE_ESCAPING);
158+
return NameEscaper.escapeName(name, EscapingScheme.UNDERSCORE_ESCAPING);
159159
}
160160

161161
/**
@@ -273,75 +273,6 @@ private static String replaceIllegalCharsInUnitName(String name) {
273273
return new String(sanitized);
274274
}
275275

276-
/**
277-
* Escapes the incoming name according to the provided escaping scheme. Depending on the rules of
278-
* escaping, this may cause no change in the string that is returned (especially NO_ESCAPING,
279-
* which by definition is a noop). This method does not do any validation of the name.
280-
*/
281-
public static String escapeName(String name, EscapingScheme scheme) {
282-
if (name.isEmpty() || !needsEscaping(name, scheme)) {
283-
return name;
284-
}
285-
286-
StringBuilder escaped = new StringBuilder();
287-
switch (scheme) {
288-
case ALLOW_UTF8:
289-
return name;
290-
case UNDERSCORE_ESCAPING:
291-
for (int i = 0; i < name.length(); ) {
292-
int c = name.codePointAt(i);
293-
if (isValidLegacyChar(c, i)) {
294-
escaped.appendCodePoint(c);
295-
} else {
296-
escaped.append('_');
297-
}
298-
i += Character.charCount(c);
299-
}
300-
return escaped.toString();
301-
case DOTS_ESCAPING:
302-
// Do not early return for legacy valid names, we still escape underscores.
303-
for (int i = 0; i < name.length(); ) {
304-
int c = name.codePointAt(i);
305-
if (c == '_') {
306-
escaped.append("__");
307-
} else if (c == '.') {
308-
escaped.append("_dot_");
309-
} else if (isValidLegacyChar(c, i)) {
310-
escaped.appendCodePoint(c);
311-
} else {
312-
escaped.append("__");
313-
}
314-
i += Character.charCount(c);
315-
}
316-
return escaped.toString();
317-
case VALUE_ENCODING_ESCAPING:
318-
escaped.append("U__");
319-
for (int i = 0; i < name.length(); ) {
320-
int c = name.codePointAt(i);
321-
if (c == '_') {
322-
escaped.append("__");
323-
} else if (isValidLegacyChar(c, i)) {
324-
escaped.appendCodePoint(c);
325-
} else if (!isValidUtf8Char(c)) {
326-
escaped.append("_FFFD_");
327-
} else {
328-
escaped.append('_');
329-
escaped.append(Integer.toHexString(c));
330-
escaped.append('_');
331-
}
332-
i += Character.charCount(c);
333-
}
334-
return escaped.toString();
335-
default:
336-
throw new IllegalArgumentException("Invalid escaping scheme " + scheme);
337-
}
338-
}
339-
340-
public static boolean needsEscaping(String name, EscapingScheme scheme) {
341-
return !isValidLegacyMetricName(name)
342-
|| (scheme == EscapingScheme.DOTS_ESCAPING && (name.contains(".") || name.contains("_")));
343-
}
344-
345276
static boolean isValidLegacyChar(int c, int i) {
346277
return (c >= 'a' && c <= 'z')
347278
|| (c >= 'A' && c <= 'Z')
@@ -350,7 +281,7 @@ static boolean isValidLegacyChar(int c, int i) {
350281
|| (c >= '0' && c <= '9' && i > 0);
351282
}
352283

353-
private static boolean isValidUtf8Char(int c) {
284+
static boolean isValidUtf8Char(int c) {
354285
return (0 <= c && c < MIN_HIGH_SURROGATE) || (MAX_LOW_SURROGATE < c && c <= MAX_CODE_POINT);
355286
}
356287
}

prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ static boolean snapshotNeedsEscaping(DataPointSnapshot d, EscapingScheme scheme)
6060

6161
private static boolean labelsNeedsEscaping(Labels labels, EscapingScheme scheme) {
6262
for (Label l : labels) {
63-
if (PrometheusNames.needsEscaping(l.getName(), scheme)) {
63+
if (NameEscaper.needsEscaping(l.getName(), scheme)) {
6464
return true;
6565
}
6666
}
@@ -100,7 +100,7 @@ public static Labels escapeLabels(Labels labels, EscapingScheme scheme) {
100100
Labels.Builder outLabelsBuilder = Labels.builder();
101101

102102
for (Label l : labels) {
103-
outLabelsBuilder.label(PrometheusNames.escapeName(l.getName(), scheme), l.getValue());
103+
outLabelsBuilder.label(NameEscaper.escapeName(l.getName(), scheme), l.getValue());
104104
}
105105

106106
return outLabelsBuilder.build();
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package io.prometheus.metrics.model.snapshots;
2+
3+
import static io.prometheus.metrics.model.snapshots.NameEscaper.escapeName;
4+
import static org.assertj.core.api.Assertions.assertThat;
5+
6+
import io.prometheus.metrics.config.EscapingScheme;
7+
import java.util.stream.Stream;
8+
import org.junit.jupiter.params.ParameterizedTest;
9+
import org.junit.jupiter.params.provider.Arguments;
10+
import org.junit.jupiter.params.provider.MethodSource;
11+
12+
class NameEscaperTest {
13+
@ParameterizedTest
14+
@MethodSource("escapeNameLegacyTestCases")
15+
public void testEscapeName(String input, EscapingScheme escapingScheme, String expected) {
16+
assertThat(escapeName(input, escapingScheme)).isEqualTo(expected);
17+
}
18+
19+
static Stream<Arguments> escapeNameLegacyTestCases() {
20+
return Stream.of(
21+
Arguments.of("", EscapingScheme.UNDERSCORE_ESCAPING, ""),
22+
Arguments.of("", EscapingScheme.DOTS_ESCAPING, ""),
23+
Arguments.of("", EscapingScheme.VALUE_ENCODING_ESCAPING, ""),
24+
Arguments.of(
25+
"no:escaping_required", EscapingScheme.UNDERSCORE_ESCAPING, "no:escaping_required"),
26+
// Dots escaping will escape underscores even though it's not strictly
27+
// necessary for compatibility.
28+
Arguments.of("no:escaping_required", EscapingScheme.DOTS_ESCAPING, "no:escaping__required"),
29+
Arguments.of(
30+
"no:escaping_required", EscapingScheme.VALUE_ENCODING_ESCAPING, "no:escaping_required"),
31+
Arguments.of(
32+
"no:escaping_required", EscapingScheme.UNDERSCORE_ESCAPING, "no:escaping_required"),
33+
Arguments.of(
34+
"mysystem.prod.west.cpu.load",
35+
EscapingScheme.DOTS_ESCAPING,
36+
"mysystem_dot_prod_dot_west_dot_cpu_dot_load"),
37+
Arguments.of(
38+
"mysystem.prod.west.cpu.load_total",
39+
EscapingScheme.DOTS_ESCAPING,
40+
"mysystem_dot_prod_dot_west_dot_cpu_dot_load__total"),
41+
Arguments.of("http.status:sum", EscapingScheme.DOTS_ESCAPING, "http_dot_status:sum"),
42+
Arguments.of("label with 😱", EscapingScheme.UNDERSCORE_ESCAPING, "label_with__"),
43+
Arguments.of("label with 😱", EscapingScheme.DOTS_ESCAPING, "label__with____"),
44+
Arguments.of(
45+
"label with 😱", EscapingScheme.VALUE_ENCODING_ESCAPING, "U__label_20_with_20__1f631_"),
46+
// name with unicode characters > 0x100
47+
Arguments.of("花火", EscapingScheme.UNDERSCORE_ESCAPING, "__"),
48+
// Dots-replacement does not know the difference between two replaced
49+
Arguments.of("花火", EscapingScheme.DOTS_ESCAPING, "____"),
50+
Arguments.of("花火", EscapingScheme.VALUE_ENCODING_ESCAPING, "U___82b1__706b_"),
51+
// name with spaces and edge-case value
52+
Arguments.of("label with Ā", EscapingScheme.UNDERSCORE_ESCAPING, "label_with__"),
53+
Arguments.of("label with Ā", EscapingScheme.DOTS_ESCAPING, "label__with____"),
54+
Arguments.of(
55+
"label with Ā", EscapingScheme.VALUE_ENCODING_ESCAPING, "U__label_20_with_20__100_"),
56+
// name with dots - needs UTF-8 validation for escaping to occur
57+
Arguments.of(
58+
"mysystem.prod.west.cpu.load",
59+
EscapingScheme.UNDERSCORE_ESCAPING,
60+
"mysystem_prod_west_cpu_load"),
61+
Arguments.of(
62+
"mysystem.prod.west.cpu.load",
63+
EscapingScheme.VALUE_ENCODING_ESCAPING,
64+
"U__mysystem_2e_prod_2e_west_2e_cpu_2e_load"),
65+
Arguments.of(
66+
"mysystem.prod.west.cpu.load_total",
67+
EscapingScheme.UNDERSCORE_ESCAPING,
68+
"mysystem_prod_west_cpu_load_total"),
69+
Arguments.of(
70+
"mysystem.prod.west.cpu.load_total",
71+
EscapingScheme.VALUE_ENCODING_ESCAPING,
72+
"U__mysystem_2e_prod_2e_west_2e_cpu_2e_load__total"),
73+
Arguments.of("http.status:sum", EscapingScheme.UNDERSCORE_ESCAPING, "http_status:sum"),
74+
Arguments.of(
75+
"http.status:sum", EscapingScheme.VALUE_ENCODING_ESCAPING, "U__http_2e_status:sum"));
76+
}
77+
}

prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamesTest.java

Lines changed: 0 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.prometheus.metrics.model.snapshots;
22

3-
import static io.prometheus.metrics.model.snapshots.PrometheusNames.escapeName;
43
import static io.prometheus.metrics.model.snapshots.PrometheusNames.isValidLabelName;
54
import static io.prometheus.metrics.model.snapshots.PrometheusNames.prometheusName;
65
import static io.prometheus.metrics.model.snapshots.PrometheusNames.sanitizeLabelName;
@@ -11,7 +10,6 @@
1110
import static org.assertj.core.api.Assertions.assertThat;
1211
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
1312

14-
import io.prometheus.metrics.config.EscapingScheme;
1513
import java.util.stream.Stream;
1614
import org.junit.jupiter.api.Test;
1715
import org.junit.jupiter.params.ParameterizedTest;
@@ -133,71 +131,6 @@ static Stream<Arguments> nameIsValid() {
133131
Arguments.of("a\ud800z", false));
134132
}
135133

136-
@ParameterizedTest
137-
@MethodSource("escapeNameLegacyTestCases")
138-
public void testEscapeName(String input, EscapingScheme escapingScheme, String expected) {
139-
assertThat(escapeName(input, escapingScheme)).isEqualTo(expected);
140-
}
141-
142-
static Stream<Arguments> escapeNameLegacyTestCases() {
143-
return Stream.of(
144-
Arguments.of("", EscapingScheme.UNDERSCORE_ESCAPING, ""),
145-
Arguments.of("", EscapingScheme.DOTS_ESCAPING, ""),
146-
Arguments.of("", EscapingScheme.VALUE_ENCODING_ESCAPING, ""),
147-
Arguments.of(
148-
"no:escaping_required", EscapingScheme.UNDERSCORE_ESCAPING, "no:escaping_required"),
149-
// Dots escaping will escape underscores even though it's not strictly
150-
// necessary for compatibility.
151-
Arguments.of("no:escaping_required", EscapingScheme.DOTS_ESCAPING, "no:escaping__required"),
152-
Arguments.of(
153-
"no:escaping_required", EscapingScheme.VALUE_ENCODING_ESCAPING, "no:escaping_required"),
154-
Arguments.of(
155-
"no:escaping_required", EscapingScheme.UNDERSCORE_ESCAPING, "no:escaping_required"),
156-
Arguments.of(
157-
"mysystem.prod.west.cpu.load",
158-
EscapingScheme.DOTS_ESCAPING,
159-
"mysystem_dot_prod_dot_west_dot_cpu_dot_load"),
160-
Arguments.of(
161-
"mysystem.prod.west.cpu.load_total",
162-
EscapingScheme.DOTS_ESCAPING,
163-
"mysystem_dot_prod_dot_west_dot_cpu_dot_load__total"),
164-
Arguments.of("http.status:sum", EscapingScheme.DOTS_ESCAPING, "http_dot_status:sum"),
165-
Arguments.of("label with 😱", EscapingScheme.UNDERSCORE_ESCAPING, "label_with__"),
166-
Arguments.of("label with 😱", EscapingScheme.DOTS_ESCAPING, "label__with____"),
167-
Arguments.of(
168-
"label with 😱", EscapingScheme.VALUE_ENCODING_ESCAPING, "U__label_20_with_20__1f631_"),
169-
// name with unicode characters > 0x100
170-
Arguments.of("花火", EscapingScheme.UNDERSCORE_ESCAPING, "__"),
171-
// Dots-replacement does not know the difference between two replaced
172-
Arguments.of("花火", EscapingScheme.DOTS_ESCAPING, "____"),
173-
Arguments.of("花火", EscapingScheme.VALUE_ENCODING_ESCAPING, "U___82b1__706b_"),
174-
// name with spaces and edge-case value
175-
Arguments.of("label with Ā", EscapingScheme.UNDERSCORE_ESCAPING, "label_with__"),
176-
Arguments.of("label with Ā", EscapingScheme.DOTS_ESCAPING, "label__with____"),
177-
Arguments.of(
178-
"label with Ā", EscapingScheme.VALUE_ENCODING_ESCAPING, "U__label_20_with_20__100_"),
179-
// name with dots - needs UTF-8 validation for escaping to occur
180-
Arguments.of(
181-
"mysystem.prod.west.cpu.load",
182-
EscapingScheme.UNDERSCORE_ESCAPING,
183-
"mysystem_prod_west_cpu_load"),
184-
Arguments.of(
185-
"mysystem.prod.west.cpu.load",
186-
EscapingScheme.VALUE_ENCODING_ESCAPING,
187-
"U__mysystem_2e_prod_2e_west_2e_cpu_2e_load"),
188-
Arguments.of(
189-
"mysystem.prod.west.cpu.load_total",
190-
EscapingScheme.UNDERSCORE_ESCAPING,
191-
"mysystem_prod_west_cpu_load_total"),
192-
Arguments.of(
193-
"mysystem.prod.west.cpu.load_total",
194-
EscapingScheme.VALUE_ENCODING_ESCAPING,
195-
"U__mysystem_2e_prod_2e_west_2e_cpu_2e_load__total"),
196-
Arguments.of("http.status:sum", EscapingScheme.UNDERSCORE_ESCAPING, "http_status:sum"),
197-
Arguments.of(
198-
"http.status:sum", EscapingScheme.VALUE_ENCODING_ESCAPING, "U__http_2e_status:sum"));
199-
}
200-
201134
@Test
202135
void testValidMetricName() {
203136
assertThat(PrometheusNames.isValidMetricName("valid_metric_name")).isTrue();

0 commit comments

Comments
 (0)