22
33import static io .prometheus .metrics .model .snapshots .PrometheusNaming .escapeName ;
44import static io .prometheus .metrics .model .snapshots .PrometheusNaming .isValidLabelName ;
5+ import static io .prometheus .metrics .model .snapshots .PrometheusNaming .normalizeMetricName ;
56import static io .prometheus .metrics .model .snapshots .PrometheusNaming .prometheusName ;
67import static io .prometheus .metrics .model .snapshots .PrometheusNaming .sanitizeLabelName ;
78import static io .prometheus .metrics .model .snapshots .PrometheusNaming .sanitizeMetricName ;
@@ -22,27 +23,79 @@ class PrometheusNamingTest {
2223
2324 @ Test
2425 void testSanitizeMetricName () {
25- assertThat (sanitizeMetricName ("my_counter_total" )).isEqualTo ("my_counter_total" );
26- assertThat (sanitizeMetricName ("jvm.info" )).isEqualTo ("jvm.info" );
27- assertThat (sanitizeMetricName ("jvm_info" )).isEqualTo ("jvm_info" );
26+ // Reserved suffixes are stripped to avoid confusion with Prometheus type conventions.
27+ assertThat (sanitizeMetricName ("my_counter_total" )).isEqualTo ("my_counter" );
28+ assertThat (sanitizeMetricName ("jvm.info" )).isEqualTo ("jvm" );
29+ assertThat (sanitizeMetricName ("jvm_info" )).isEqualTo ("jvm" );
2830 assertThat (sanitizeMetricName ("a.b" )).isEqualTo ("a.b" );
29- assertThat (sanitizeMetricName ("_total" )).isEqualTo ("_total" );
31+ // "_total" / ".total" corner cases: the suffix is the entire name, so the separator
32+ // character is dropped to avoid returning an empty string.
33+ assertThat (sanitizeMetricName ("_total" )).isEqualTo ("total" );
34+ assertThat (sanitizeMetricName (".total" )).isEqualTo ("total" );
3035 assertThat (sanitizeMetricName ("total" )).isEqualTo ("total" );
31- assertThat (sanitizeMetricName ("my_events_created" )).isEqualTo ("my_events_created" );
32- assertThat (sanitizeMetricName ("my_histogram_bucket" )).isEqualTo ("my_histogram_bucket" );
36+ assertThat (sanitizeMetricName ("my_events_created" )).isEqualTo ("my_events" );
37+ assertThat (sanitizeMetricName ("my_histogram_bucket" )).isEqualTo ("my_histogram" );
38+ }
39+
40+ /**
41+ * Regression test: reserved suffixes must be stripped even when the raw name comes from an
42+ * external system (e.g. JMX Exporter converting a JMX attribute named {@code "Total"} into a
43+ * Prometheus name {@code kafka_consumer_request_total}).
44+ *
45+ * <p>Without stripping, an UNKNOWN metric would be stored under {@code
46+ * kafka_consumer_request_total} instead of {@code kafka_consumer_request}, breaking registry
47+ * lookups by the expected base name and potentially triggering unintended counter-type inference
48+ * in tools that check for the {@code _total} suffix.
49+ */
50+ @ Test
51+ void testSanitizeMetricNameStripsReservedSuffixForDownstreamTools () {
52+ // A JMX attribute "Total" produces "kafka_consumer_request_total" as the raw name.
53+ // sanitizeMetricName must strip "_total" so that the metric is stored and looked up under
54+ // "kafka_consumer_request", not "kafka_consumer_request_total".
55+ assertThat (sanitizeMetricName ("kafka_consumer_request_total" ))
56+ .isEqualTo ("kafka_consumer_request" );
57+ // Dot variant is stripped too.
58+ assertThat (sanitizeMetricName ("kafka_consumer_request.total" ))
59+ .isEqualTo ("kafka_consumer_request" );
60+ // Multiple chained reserved suffixes are stripped iteratively.
61+ assertThat (sanitizeMetricName ("events_total_created" )).isEqualTo ("events" );
3362 }
3463
3564 @ Test
3665 void testSanitizeMetricNameWithUnit () {
3766 assertThat (prometheusName (sanitizeMetricName ("def" , Unit .RATIO )))
3867 .isEqualTo ("def_" + Unit .RATIO );
68+ // _total is stripped first, then the unit is appended.
3969 assertThat (prometheusName (sanitizeMetricName ("my_counter_total" , Unit .RATIO )))
40- .isEqualTo ("my_counter_total_ " + Unit .RATIO );
41- assertThat (sanitizeMetricName ("jvm.info" , Unit .RATIO )).isEqualTo ("jvm.info_ " + Unit .RATIO );
42- assertThat (sanitizeMetricName ("_total" , Unit .RATIO )).isEqualTo ("_total_ " + Unit .RATIO );
70+ .isEqualTo ("my_counter_ " + Unit .RATIO );
71+ assertThat (sanitizeMetricName ("jvm.info" , Unit .RATIO )).isEqualTo ("jvm_ " + Unit .RATIO );
72+ assertThat (sanitizeMetricName ("_total" , Unit .RATIO )).isEqualTo ("total_ " + Unit .RATIO );
4373 assertThat (sanitizeMetricName ("total" , Unit .RATIO )).isEqualTo ("total_" + Unit .RATIO );
4474 }
4575
76+ @ Test
77+ void testNormalizeMetricName () {
78+ assertThat (normalizeMetricName ("my_counter_total" )).isEqualTo ("my_counter_total" );
79+ assertThat (normalizeMetricName ("jvm.info" )).isEqualTo ("jvm.info" );
80+ assertThat (normalizeMetricName ("jvm_info" )).isEqualTo ("jvm_info" );
81+ assertThat (normalizeMetricName ("a.b" )).isEqualTo ("a.b" );
82+ assertThat (normalizeMetricName ("_total" )).isEqualTo ("_total" );
83+ assertThat (normalizeMetricName (".total" )).isEqualTo (".total" );
84+ assertThat (normalizeMetricName ("my_events_created" )).isEqualTo ("my_events_created" );
85+ assertThat (normalizeMetricName ("my_histogram_bucket" )).isEqualTo ("my_histogram_bucket" );
86+ }
87+
88+ @ Test
89+ void testNormalizeMetricNameWithUnit () {
90+ assertThat (prometheusName (normalizeMetricName ("def" , Unit .RATIO )))
91+ .isEqualTo ("def_" + Unit .RATIO );
92+ assertThat (prometheusName (normalizeMetricName ("my_counter_total" , Unit .RATIO )))
93+ .isEqualTo ("my_counter_total_" + Unit .RATIO );
94+ assertThat (normalizeMetricName ("jvm.info" , Unit .RATIO )).isEqualTo ("jvm.info_" + Unit .RATIO );
95+ assertThat (normalizeMetricName ("_total" , Unit .RATIO )).isEqualTo ("_total_" + Unit .RATIO );
96+ assertThat (normalizeMetricName ("total" , Unit .RATIO )).isEqualTo ("total_" + Unit .RATIO );
97+ }
98+
4699 @ Test
47100 void testSanitizeLabelName () {
48101 assertThat (prometheusName (sanitizeLabelName ("0abc.def" ))).isEqualTo ("_abc_def" );
0 commit comments