Skip to content

Commit 1ad3600

Browse files
committed
Own Prometheus name translation
Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
1 parent 7dfbab2 commit 1ad3600

3 files changed

Lines changed: 66 additions & 6 deletions

File tree

exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
package io.opentelemetry.exporter.prometheus;
77

8-
import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName;
98
import static java.util.Objects.requireNonNull;
109

1110
import io.opentelemetry.api.common.AttributeKey;
@@ -624,6 +623,32 @@ private static boolean containsOnlyUnderscores(String value) {
624623
return true;
625624
}
626625

626+
private static String convertLegacyMetricName(String name) {
627+
if (name.isEmpty()) {
628+
return name;
629+
}
630+
631+
StringBuilder result = new StringBuilder(name.length());
632+
for (int i = 0; i < name.length(); ) {
633+
int codePoint = name.codePointAt(i);
634+
if (isValidLegacyMetricChar(codePoint, i)) {
635+
result.appendCodePoint(codePoint);
636+
} else {
637+
result.append('_');
638+
}
639+
i += Character.charCount(codePoint);
640+
}
641+
return result.toString();
642+
}
643+
644+
private static boolean isValidLegacyMetricChar(int codePoint, int index) {
645+
return (codePoint >= 'a' && codePoint <= 'z')
646+
|| (codePoint >= 'A' && codePoint <= 'Z')
647+
|| codePoint == '_'
648+
|| codePoint == ':'
649+
|| (codePoint >= '0' && codePoint <= '9' && index > 0);
650+
}
651+
627652
private MetricMetadata convertMetadata(MetricData metricData, boolean isCounter) {
628653
switch (translationStrategy) {
629654
case UNDERSCORE_ESCAPING_WITH_SUFFIXES:
@@ -639,7 +664,7 @@ private MetricMetadata convertMetadata(MetricData metricData, boolean isCounter)
639664
}
640665

641666
private static MetricMetadata convertMetadataEscapedWithSuffixes(MetricData metricData) {
642-
String name = prometheusName(metricData.getName());
667+
String name = convertLegacyMetricName(metricData.getName());
643668
String help = metricData.getDescription();
644669
Unit unit = PrometheusUnitsHelper.convertUnit(metricData.getUnit());
645670
name = stripRepeatedUnderscores(stripReservedMetricSuffixes(name));
@@ -650,7 +675,7 @@ private static MetricMetadata convertMetadataEscapedWithSuffixes(MetricData metr
650675
}
651676

652677
private static MetricMetadata convertMetadataEscapedWithoutSuffixes(MetricData metricData) {
653-
String rawName = stripRepeatedUnderscores(prometheusName(metricData.getName()));
678+
String rawName = stripRepeatedUnderscores(convertLegacyMetricName(metricData.getName()));
654679
String name = stripReservedMetricSuffixes(rawName);
655680
return new MetricMetadata(name, rawName, rawName, metricData.getDescription(), null);
656681
}

exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
package io.opentelemetry.exporter.prometheus;
77

8-
import io.prometheus.metrics.model.snapshots.PrometheusNaming;
98
import io.prometheus.metrics.model.snapshots.Unit;
109
import java.util.Map;
1110
import java.util.concurrent.ConcurrentHashMap;
@@ -99,7 +98,7 @@ static Unit convertUnit(String otelUnit) {
9998
@Nullable
10099
private static Unit unitOrNull(String name) {
101100
try {
102-
String sanitized = PrometheusNaming.sanitizeUnitName(name);
101+
String sanitized = sanitizeUnitName(name);
103102
sanitized = stripReservedUnitSuffixes(sanitized);
104103
if (sanitized.isEmpty()) {
105104
return null;
@@ -110,6 +109,42 @@ private static Unit unitOrNull(String name) {
110109
}
111110
}
112111

112+
private static String sanitizeUnitName(String unitName) {
113+
if (unitName.isEmpty()) {
114+
throw new IllegalArgumentException("Cannot convert an empty string to a valid unit name.");
115+
}
116+
String sanitizedName = replaceIllegalCharsInUnitName(unitName);
117+
while (sanitizedName.startsWith("_") || sanitizedName.startsWith(".")) {
118+
sanitizedName = sanitizedName.substring(1);
119+
}
120+
while (sanitizedName.endsWith(".") || sanitizedName.endsWith("_")) {
121+
sanitizedName = sanitizedName.substring(0, sanitizedName.length() - 1);
122+
}
123+
if (sanitizedName.isEmpty()) {
124+
throw new IllegalArgumentException(
125+
"Cannot convert '" + unitName + "' into a valid unit name.");
126+
}
127+
return sanitizedName;
128+
}
129+
130+
private static String replaceIllegalCharsInUnitName(String name) {
131+
int length = name.length();
132+
char[] sanitized = new char[length];
133+
for (int i = 0; i < length; i++) {
134+
char ch = name.charAt(i);
135+
if (ch == ':'
136+
|| ch == '.'
137+
|| (ch >= 'a' && ch <= 'z')
138+
|| (ch >= 'A' && ch <= 'Z')
139+
|| (ch >= '0' && ch <= '9')) {
140+
sanitized[i] = ch;
141+
} else {
142+
sanitized[i] = '_';
143+
}
144+
}
145+
return new String(sanitized);
146+
}
147+
113148
private static String stripReservedUnitSuffixes(String name) {
114149
boolean modified = true;
115150
while (modified) {

exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverterTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ private static Stream<Arguments> resourceAttributesAdditionArgs() {
421421
}
422422

423423
@Test
424-
void prometheusNameCollisionTest_Issue6277() {
424+
void metricNameCollisionTest_Issue6277() {
425425
// NOTE: Metrics with the same resolved prometheus name should merge. However,
426426
// Otel2PrometheusConverter is not responsible for merging individual series, so the merge will
427427
// fail if the two different metrics contain overlapping series. Users should deal with this by

0 commit comments

Comments
 (0)