77
88import static io .prometheus .metrics .model .snapshots .PrometheusNaming .prometheusName ;
99import static io .prometheus .metrics .model .snapshots .PrometheusNaming .sanitizeLabelName ;
10- import static io .prometheus .metrics .model .snapshots .PrometheusNaming .sanitizeMetricName ;
1110import static java .util .Objects .requireNonNull ;
1211
1312import io .opentelemetry .api .common .AttributeKey ;
@@ -84,6 +83,7 @@ final class Otel2PrometheusConverter {
8483
8584 private final boolean otelScopeLabelsEnabled ;
8685 private final boolean targetInfoMetricEnabled ;
86+ private final TranslationStrategy translationStrategy ;
8787 @ Nullable private final Predicate <String > allowedResourceAttributesFilter ;
8888
8989 /**
@@ -104,9 +104,11 @@ final class Otel2PrometheusConverter {
104104 Otel2PrometheusConverter (
105105 boolean otelScopeLabelsEnabled ,
106106 boolean targetInfoMetricEnabled ,
107+ TranslationStrategy translationStrategy ,
107108 @ Nullable Predicate <String > allowedResourceAttributesFilter ) {
108109 this .otelScopeLabelsEnabled = otelScopeLabelsEnabled ;
109110 this .targetInfoMetricEnabled = targetInfoMetricEnabled ;
111+ this .translationStrategy = translationStrategy ;
110112 this .allowedResourceAttributesFilter = allowedResourceAttributesFilter ;
111113 this .resourceAttributesToAllowedKeysCache =
112114 allowedResourceAttributesFilter != null
@@ -122,6 +124,10 @@ boolean isTargetInfoMetricEnabled() {
122124 return targetInfoMetricEnabled ;
123125 }
124126
127+ TranslationStrategy getTranslationStrategy () {
128+ return translationStrategy ;
129+ }
130+
125131 @ Nullable
126132 Predicate <String > getAllowedResourceAttributesFilter () {
127133 return allowedResourceAttributesFilter ;
@@ -155,7 +161,8 @@ private MetricSnapshot convert(MetricData metricData) {
155161 // Note that AggregationTemporality.DELTA should never happen
156162 // because PrometheusMetricReader#getAggregationTemporality returns CUMULATIVE.
157163
158- MetricMetadata metadata = convertMetadata (metricData );
164+ boolean isCounter = isMonotonicSum (metricData );
165+ MetricMetadata metadata = convertMetadata (metricData , isCounter );
159166 InstrumentationScopeInfo scope = metricData .getInstrumentationScopeInfo ();
160167 switch (metricData .getType ()) {
161168 case LONG_GAUGE :
@@ -210,6 +217,17 @@ private MetricSnapshot convert(MetricData metricData) {
210217 return null ;
211218 }
212219
220+ private static boolean isMonotonicSum (MetricData metricData ) {
221+ switch (metricData .getType ()) {
222+ case LONG_SUM :
223+ return metricData .getLongSumData ().isMonotonic ();
224+ case DOUBLE_SUM :
225+ return metricData .getDoubleSumData ().isMonotonic ();
226+ default :
227+ return false ;
228+ }
229+ }
230+
213231 private GaugeSnapshot convertLongGauge (
214232 MetricMetadata metadata ,
215233 InstrumentationScopeInfo scope ,
@@ -550,29 +568,91 @@ private List<AttributeKey<?>> filterAllowedResourceAttributeKeys(@Nullable Resou
550568 * non-standard characters (dots, dashes, etc.) to underscores, and {@code sanitizeLabelName}
551569 * strips invalid leading prefixes.
552570 */
553- private static String convertLabelName (String key ) {
554- return sanitizeLabelName (prometheusName (key ));
571+ private String convertLabelName (String key ) {
572+ if (translationStrategy .shouldEscape ()) {
573+ return sanitizeLabelName (prometheusName (key ));
574+ }
575+ return key ;
576+ }
577+
578+ private MetricMetadata convertMetadata (MetricData metricData , boolean isCounter ) {
579+ switch (translationStrategy ) {
580+ case UNDERSCORE_ESCAPING_WITH_SUFFIXES :
581+ return convertMetadataEscapedWithSuffixes (metricData );
582+ case UNDERSCORE_ESCAPING_WITHOUT_SUFFIXES :
583+ return convertMetadataEscapedWithoutSuffixes (metricData );
584+ case NO_UTF8_ESCAPING_WITH_SUFFIXES :
585+ return convertMetadataUtf8WithSuffixes (metricData , isCounter );
586+ case NO_TRANSLATION :
587+ return convertMetadataNoTranslation (metricData );
588+ }
589+ throw new IllegalStateException ("Unknown strategy: " + translationStrategy );
590+ }
591+
592+ private static MetricMetadata convertMetadataEscapedWithSuffixes (MetricData metricData ) {
593+ String name = prometheusName (metricData .getName ());
594+ String help = metricData .getDescription ();
595+ Unit unit = PrometheusUnitsHelper .convertUnit (metricData .getUnit ());
596+ name = stripRepeatedUnderscores (stripReservedMetricSuffixes (name ));
597+ if (unit != null && !name .endsWith (unit .toString ())) {
598+ name = name + "_" + unit ;
599+ }
600+ return new MetricMetadata (stripRepeatedUnderscores (name ), help , unit );
555601 }
556602
557- private static MetricMetadata convertMetadata (MetricData metricData ) {
558- String name = sanitizeMetricName (prometheusName (metricData .getName ()));
603+ private static MetricMetadata convertMetadataEscapedWithoutSuffixes (MetricData metricData ) {
604+ String rawName = stripRepeatedUnderscores (prometheusName (metricData .getName ()));
605+ String name = stripReservedMetricSuffixes (rawName );
606+ return new MetricMetadata (name , rawName , rawName , metricData .getDescription (), null );
607+ }
608+
609+ private static MetricMetadata convertMetadataUtf8WithSuffixes (
610+ MetricData metricData , boolean isCounter ) {
611+ String name = metricData .getName ();
559612 String help = metricData .getDescription ();
560613 Unit unit = PrometheusUnitsHelper .convertUnit (metricData .getUnit ());
561614 if (unit != null && !name .endsWith (unit .toString ())) {
562615 name = name + "_" + unit ;
563616 }
564- // Repeated __ are discouraged according to spec, although this is allowed in prometheus, see
565- // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/compatibility/prometheus_and_openmetrics.md#metric-metadata-1
617+ String expositionBaseName = name ;
618+ if (isCounter && !expositionBaseName .endsWith ("_total" )) {
619+ expositionBaseName = expositionBaseName + "_total" ;
620+ }
621+ return new MetricMetadata (stripReservedMetricSuffixes (name ), expositionBaseName , help , unit );
622+ }
623+
624+ private static MetricMetadata convertMetadataNoTranslation (MetricData metricData ) {
625+ String rawName = metricData .getName ();
626+ String name = stripReservedMetricSuffixes (rawName );
627+ return new MetricMetadata (name , rawName , rawName , metricData .getDescription (), null );
628+ }
629+
630+ private static String stripReservedMetricSuffixes (String name ) {
631+ boolean modified = true ;
632+ while (modified ) {
633+ modified = false ;
634+ for (String suffix : PrometheusUnitsHelper .RESERVED_SUFFIXES ) {
635+ if (name .equals (suffix )) {
636+ return name .substring (1 );
637+ }
638+ if (name .endsWith (suffix )) {
639+ name = name .substring (0 , name .length () - suffix .length ());
640+ modified = true ;
641+ }
642+ }
643+ }
644+ return name ;
645+ }
646+
647+ private static String stripRepeatedUnderscores (String name ) {
566648 while (name .contains ("__" )) {
567649 name = name .replace ("__" , "_" );
568650 }
569-
570- return new MetricMetadata (name , help , unit );
651+ return name ;
571652 }
572653
573- private static void putOrMerge (
574- Map <String , MetricSnapshot > snapshotsByName , MetricSnapshot snapshot ) {
575- String name = snapshot .getMetadata ().getPrometheusName ();
654+ private void putOrMerge (Map <String , MetricSnapshot > snapshotsByName , MetricSnapshot snapshot ) {
655+ String name = getMergeKey (snapshot .getMetadata ());
576656 if (snapshotsByName .containsKey (name )) {
577657 MetricSnapshot merged = merge (snapshotsByName .get (name ), snapshot );
578658 if (merged != null ) {
@@ -583,6 +663,13 @@ private static void putOrMerge(
583663 }
584664 }
585665
666+ private String getMergeKey (MetricMetadata metadata ) {
667+ if (translationStrategy .shouldEscape ()) {
668+ return metadata .getPrometheusName ();
669+ }
670+ return metadata .getName ();
671+ }
672+
586673 /**
587674 * OpenTelemetry may use the same metric name multiple times but in different instrumentation
588675 * scopes. In that case, we try to merge the metrics. They will have different {@code
0 commit comments