Browse Source

HADOOP-19571. Improve PrometheusMetricsSink#normalizeName performance (#7692) Contributed by Ivan Andika.

* HADOOP-19571. Improve PrometheusMetricsSink#normalizeName performance

Reviewed-by: Akira Ajisaka <aajisaka@apache.org>
Signed-off-by: Shilun Fan <slfan1989@apache.org>
Ivan Andika 6 days ago
parent
commit
949292eac6

+ 51 - 1
hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/metrics2/sink/PrometheusMetricsSink.java

@@ -19,7 +19,13 @@ package org.apache.hadoop.metrics2.sink;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.ExecutionException;
 import java.util.regex.Matcher;
+
+import org.apache.hadoop.thirdparty.com.google.common.cache.CacheBuilder;
+import org.apache.hadoop.thirdparty.com.google.common.cache.CacheLoader;
+import org.apache.hadoop.thirdparty.com.google.common.cache.LoadingCache;
+import org.apache.hadoop.thirdparty.com.google.common.util.concurrent.UncheckedExecutionException;
 import org.apache.commons.configuration2.SubsetConfiguration;
 import org.apache.hadoop.metrics2.AbstractMetric;
 import org.apache.hadoop.metrics2.MetricType;
@@ -35,6 +41,8 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.regex.Pattern;
 
 import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Metrics sink for prometheus exporter.
@@ -42,6 +50,7 @@ import org.apache.commons.lang3.StringUtils;
  * Stores the metric data in-memory and return with it on request.
  */
 public class PrometheusMetricsSink implements MetricsSink {
+  private static final Logger LOG = LoggerFactory.getLogger(PrometheusMetricsSink.class);
 
   /**
    * Cached output lines for each metrics.
@@ -62,6 +71,17 @@ public class PrometheusMetricsSink implements MetricsSink {
       Pattern
           .compile("^op=(?<op>\\w+)(.user=(?<user>.*)|)\\.(TotalCount|count)$");
 
+  /**
+   * A fixed cache for Hadoop metric to Prometheus metric name conversion.
+   */
+  private static final int NORMALIZED_NAME_CACHE_MAX_SIZE = 100_000;
+  private static final CacheLoader<String, String> NORMALIZED_NAME_CACHE_LOADER =
+      CacheLoader.from(PrometheusMetricsSink::normalizeImpl);
+  private static final LoadingCache<String, String> NORMALIZED_NAME_CACHE =
+      CacheBuilder.newBuilder()
+          .maximumSize(NORMALIZED_NAME_CACHE_MAX_SIZE)
+          .build(NORMALIZED_NAME_CACHE_LOADER);
+
   public PrometheusMetricsSink() {
   }
 
@@ -83,7 +103,21 @@ public class PrometheusMetricsSink implements MetricsSink {
 
   /**
    * Convert CamelCase based names to lower-case names where the separator
-   * is the underscore, to follow prometheus naming conventions.
+   * is the underscore, to follow prometheus naming conventions. This method
+   * utilizes a cache to improve performance.
+   *
+   * <p>
+   *  Reference:
+   *  <ul>
+   *    <li>
+   *      <a href="https://prometheus.io/docs/practices/naming/">
+   *        Metrics and Label Naming</a>
+   *    </li>
+   *    <li>
+   *      <a href="https://prometheus.io/docs/instrumenting/exposition_formats/">
+   *        Exposition formats</a>
+   *    </li>
+   *  </ul>
    *
    * @param metricName metricName.
    * @param recordName recordName.
@@ -93,6 +127,22 @@ public class PrometheusMetricsSink implements MetricsSink {
                                String metricName) {
     String baseName = StringUtils.capitalize(recordName)
         + StringUtils.capitalize(metricName);
+    try {
+      return NORMALIZED_NAME_CACHE.get(baseName);
+    } catch (ExecutionException | UncheckedExecutionException e) {
+      // This should not happen since normalization function do not throw any exception
+      // Nevertheless, we can fall back to uncached implementation if it somehow happens.
+      LOG.warn("Exception encountered when loading metric with base name {} from cache, " +
+          "fall back to uncached normalization implementation", baseName, e);
+      return normalizeImpl(baseName);
+    }
+  }
+
+  /**
+   * Underlying Prometheus normalization implementation.
+   * See {@link PrometheusMetricsSink#prometheusName(String, String)} for more information.
+   */
+  private static String normalizeImpl(String baseName) {
     String[] parts = SPLIT_PATTERN.split(baseName);
     String joined =  String.join("_", parts).toLowerCase();
     return DELIMITERS.matcher(joined).replaceAll("_");