1919
2020import java .util .ArrayList ;
2121import java .util .List ;
22+ import java .util .concurrent .ExecutionException ;
2223import java .util .regex .Matcher ;
24+
25+ import org .apache .hadoop .thirdparty .com .google .common .cache .CacheBuilder ;
26+ import org .apache .hadoop .thirdparty .com .google .common .cache .CacheLoader ;
27+ import org .apache .hadoop .thirdparty .com .google .common .cache .LoadingCache ;
28+ import org .apache .hadoop .thirdparty .com .google .common .util .concurrent .UncheckedExecutionException ;
2329import org .apache .commons .configuration2 .SubsetConfiguration ;
2430import org .apache .hadoop .metrics2 .AbstractMetric ;
2531import org .apache .hadoop .metrics2 .MetricType ;
3541import java .util .regex .Pattern ;
3642
3743import org .apache .commons .lang3 .StringUtils ;
44+ import org .slf4j .Logger ;
45+ import org .slf4j .LoggerFactory ;
3846
3947/**
4048 * Metrics sink for prometheus exporter.
4149 * <p>
4250 * Stores the metric data in-memory and return with it on request.
4351 */
4452public class PrometheusMetricsSink implements MetricsSink {
53+ private static final Logger LOG = LoggerFactory .getLogger (PrometheusMetricsSink .class );
4554
4655 /**
4756 * Cached output lines for each metrics.
@@ -62,6 +71,17 @@ public class PrometheusMetricsSink implements MetricsSink {
6271 Pattern
6372 .compile ("^op=(?<op>\\ w+)(.user=(?<user>.*)|)\\ .(TotalCount|count)$" );
6473
74+ /**
75+ * A fixed cache for Hadoop metric to Prometheus metric name conversion.
76+ */
77+ private static final int NORMALIZED_NAME_CACHE_MAX_SIZE = 100_000 ;
78+ private static final CacheLoader <String , String > NORMALIZED_NAME_CACHE_LOADER =
79+ CacheLoader .from (PrometheusMetricsSink ::normalizeImpl );
80+ private static final LoadingCache <String , String > NORMALIZED_NAME_CACHE =
81+ CacheBuilder .newBuilder ()
82+ .maximumSize (NORMALIZED_NAME_CACHE_MAX_SIZE )
83+ .build (NORMALIZED_NAME_CACHE_LOADER );
84+
6585 public PrometheusMetricsSink () {
6686 }
6787
@@ -83,7 +103,21 @@ public void putMetrics(MetricsRecord metricsRecord) {
83103
84104 /**
85105 * Convert CamelCase based names to lower-case names where the separator
86- * is the underscore, to follow prometheus naming conventions.
106+ * is the underscore, to follow prometheus naming conventions. This method
107+ * utilizes a cache to improve performance.
108+ *
109+ * <p>
110+ * Reference:
111+ * <ul>
112+ * <li>
113+ * <a href="https://prometheus.io/docs/practices/naming/">
114+ * Metrics and Label Naming</a>
115+ * </li>
116+ * <li>
117+ * <a href="https://prometheus.io/docs/instrumenting/exposition_formats/">
118+ * Exposition formats</a>
119+ * </li>
120+ * </ul>
87121 *
88122 * @param metricName metricName.
89123 * @param recordName recordName.
@@ -93,6 +127,22 @@ public String prometheusName(String recordName,
93127 String metricName ) {
94128 String baseName = StringUtils .capitalize (recordName )
95129 + StringUtils .capitalize (metricName );
130+ try {
131+ return NORMALIZED_NAME_CACHE .get (baseName );
132+ } catch (ExecutionException | UncheckedExecutionException e ) {
133+ // This should not happen since normalization function do not throw any exception
134+ // Nevertheless, we can fall back to uncached implementation if it somehow happens.
135+ LOG .warn ("Exception encountered when loading metric with base name {} from cache, " +
136+ "fall back to uncached normalization implementation" , baseName , e );
137+ return normalizeImpl (baseName );
138+ }
139+ }
140+
141+ /**
142+ * Underlying Prometheus normalization implementation.
143+ * See {@link PrometheusMetricsSink#prometheusName(String, String)} for more information.
144+ */
145+ private static String normalizeImpl (String baseName ) {
96146 String [] parts = SPLIT_PATTERN .split (baseName );
97147 String joined = String .join ("_" , parts ).toLowerCase ();
98148 return DELIMITERS .matcher (joined ).replaceAll ("_" );
0 commit comments