add support for publishing percentile time series for the histogram m… (#1689)

* add support for publishing percentile time series for the histogram metrics cql-requests, cql-messages and throttling delay.

Motivation:

Histogram metrics is generating too many metrics overloading the promethous servers. if application has 500 Vms
and 1000 cassandra nodes, The histogram metrics generates 100*500*1000 = 50,000,000 time series every 30 seconds.
This is just too much metrics.  Let us say we can generate percentile 95 timeseries for for every cassandra nodes,
then we only have 1*500 = 500 metrics and in applciation side, we can ignore the _bucket time series. This way there
will be very less metrics.

Modifications:
add configurable pre-defined percentiles to Micrometer Timer.Builder.publishPercentiles. This change is being added to
cql-requests, cql-messages and throttling delay.

Result:
Based on the configuration, we will see additonal quantile time series for cql-requests, cql-messages and throttling delay
histogram metrics.

* add support for publishing percentile time series for the histogram metrics cql-requests, cql-messages and throttling delay.

Motivation:

Histogram metrics is generating too many metrics overloading the promethous servers. if application has 500 Vms
and 1000 cassandra nodes, The histogram metrics generates 100*500*1000 = 50,000,000 time series every 30 seconds.
This is just too much metrics.  Let us say we can generate percentile 95 timeseries for for every cassandra nodes,
then we only have 1*500 = 500 metrics and in applciation side, we can ignore the _bucket time series. This way there
will be very less metrics.

Modifications:
add configurable pre-defined percentiles to Micrometer Timer.Builder.publishPercentiles. This change is being added to
cql-requests, cql-messages and throttling delay.

Result:
Based on the configuration, we will see additonal quantile time series for cql-requests, cql-messages and throttling delay
histogram metrics.

* using helper method as suggested in review

* fixes as per review comments

* add configuration option which switches aggregable histogram generation on/off for all metric flavors [default=on]

* updating java doc

* rename method to publishPercentilesIfDefined

* renmae method

---------

Co-authored-by: Nagappa Paraddi <nparaddi@walmartlabs.com>
diff --git a/core/src/main/java/com/datastax/dse/driver/api/core/config/DseDriverOption.java b/core/src/main/java/com/datastax/dse/driver/api/core/config/DseDriverOption.java
index 74907c1..3ad6ed6 100644
--- a/core/src/main/java/com/datastax/dse/driver/api/core/config/DseDriverOption.java
+++ b/core/src/main/java/com/datastax/dse/driver/api/core/config/DseDriverOption.java
@@ -288,6 +288,34 @@
    * <p>Value-type: {@link java.time.Duration Duration}
    */
   METRICS_NODE_GRAPH_MESSAGES_SLO("advanced.metrics.node.graph-messages.slo"),
+  /**
+   * Optional list of percentiles to publish for graph-requests metric. Produces an additional time
+   * series for each requested percentile. This percentile is computed locally, and so can't be
+   * aggregated with percentiles computed across other dimensions (e.g. in a different instance).
+   *
+   * <p>Value type: {@link java.util.List List}&#60;{@link Double}&#62;
+   */
+  METRICS_SESSION_GRAPH_REQUESTS_PUBLISH_PERCENTILES(
+      "advanced.metrics.session.graph-requests.publish-percentiles"),
+  /**
+   * Optional list of percentiles to publish for node graph-messages metric. Produces an additional
+   * time series for each requested percentile. This percentile is computed locally, and so can't be
+   * aggregated with percentiles computed across other dimensions (e.g. in a different instance).
+   *
+   * <p>Value type: {@link java.util.List List}&#60;{@link Double}&#62;
+   */
+  METRICS_NODE_GRAPH_MESSAGES_PUBLISH_PERCENTILES(
+      "advanced.metrics.node.graph-messages.publish-percentiles"),
+  /**
+   * Optional list of percentiles to publish for continuous paging requests metric. Produces an
+   * additional time series for each requested percentile. This percentile is computed locally, and
+   * so can't be aggregated with percentiles computed across other dimensions (e.g. in a different
+   * instance).
+   *
+   * <p>Value type: {@link java.util.List List}&#60;{@link Double}&#62;
+   */
+  CONTINUOUS_PAGING_METRICS_SESSION_CQL_REQUESTS_PUBLISH_PERCENTILES(
+      "advanced.metrics.session.continuous-cql-requests.publish-percentiles"),
   ;
 
   private final String path;
diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java
index e7e75d9..71d0723 100644
--- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java
+++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java
@@ -939,6 +939,41 @@
    * <p>Value-type: List of {@link String}
    */
   METADATA_SCHEMA_CHANGE_LISTENER_CLASSES("advanced.schema-change-listener.classes"),
+  /**
+   * Optional list of percentiles to publish for cql-requests metric. Produces an additional time
+   * series for each requested percentile. This percentile is computed locally, and so can't be
+   * aggregated with percentiles computed across other dimensions (e.g. in a different instance).
+   *
+   * <p>Value type: {@link java.util.List List}&#60;{@link Double}&#62;
+   */
+  METRICS_SESSION_CQL_REQUESTS_PUBLISH_PERCENTILES(
+      "advanced.metrics.session.cql-requests.publish-percentiles"),
+  /**
+   * Optional list of percentiles to publish for node cql-messages metric. Produces an additional
+   * time series for each requested percentile. This percentile is computed locally, and so can't be
+   * aggregated with percentiles computed across other dimensions (e.g. in a different instance).
+   *
+   * <p>Value type: {@link java.util.List List}&#60;{@link Double}&#62;
+   */
+  METRICS_NODE_CQL_MESSAGES_PUBLISH_PERCENTILES(
+      "advanced.metrics.node.cql-messages.publish-percentiles"),
+  /**
+   * Optional list of percentiles to publish for throttling delay metric.Produces an additional time
+   * series for each requested percentile. This percentile is computed locally, and so can't be
+   * aggregated with percentiles computed across other dimensions (e.g. in a different instance).
+   *
+   * <p>Value type: {@link java.util.List List}&#60;{@link Double}&#62;
+   */
+  METRICS_SESSION_THROTTLING_PUBLISH_PERCENTILES(
+      "advanced.metrics.session.throttling.delay.publish-percentiles"),
+  /**
+   * Adds histogram buckets used to generate aggregable percentile approximations in monitoring
+   * systems that have query facilities to do so (e.g. Prometheus histogram_quantile, Atlas
+   * percentiles).
+   *
+   * <p>Value-type: boolean
+   */
+  METRICS_GENERATE_AGGREGABLE_HISTOGRAMS("advanced.metrics.histograms.generate-aggregable"),
   ;
 
   private final String path;
diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/OptionsMap.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/OptionsMap.java
index 8f5aa01..2c7a116 100644
--- a/core/src/main/java/com/datastax/oss/driver/api/core/config/OptionsMap.java
+++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/OptionsMap.java
@@ -378,6 +378,7 @@
     map.put(TypedDriverOption.COALESCER_INTERVAL, Duration.of(10, ChronoUnit.MICROS));
     map.put(TypedDriverOption.LOAD_BALANCING_DC_FAILOVER_MAX_NODES_PER_REMOTE_DC, 0);
     map.put(TypedDriverOption.LOAD_BALANCING_DC_FAILOVER_ALLOW_FOR_LOCAL_CONSISTENCY_LEVELS, false);
+    map.put(TypedDriverOption.METRICS_GENERATE_AGGREGABLE_HISTOGRAMS, true);
   }
 
   @Immutable
diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java
index 2428be0..3f790e9 100644
--- a/core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java
+++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java
@@ -388,6 +388,10 @@
   /** The consistency level to use for trace queries. */
   public static final TypedDriverOption<String> REQUEST_TRACE_CONSISTENCY =
       new TypedDriverOption<>(DefaultDriverOption.REQUEST_TRACE_CONSISTENCY, GenericType.STRING);
+  /** Whether or not to publish aggregable histogram for metrics */
+  public static final TypedDriverOption<Boolean> METRICS_GENERATE_AGGREGABLE_HISTOGRAMS =
+      new TypedDriverOption<>(
+          DefaultDriverOption.METRICS_GENERATE_AGGREGABLE_HISTOGRAMS, GenericType.BOOLEAN);
   /** List of enabled session-level metrics. */
   public static final TypedDriverOption<List<String>> METRICS_SESSION_ENABLED =
       new TypedDriverOption<>(
@@ -409,6 +413,12 @@
       new TypedDriverOption<>(
           DefaultDriverOption.METRICS_SESSION_CQL_REQUESTS_SLO,
           GenericType.listOf(GenericType.DURATION));
+  /** Optional pre-defined percentile of cql requests to publish, as a list of percentiles . */
+  public static final TypedDriverOption<List<Double>>
+      METRICS_SESSION_CQL_REQUESTS_PUBLISH_PERCENTILES =
+          new TypedDriverOption<>(
+              DefaultDriverOption.METRICS_SESSION_CQL_REQUESTS_PUBLISH_PERCENTILES,
+              GenericType.listOf(GenericType.DOUBLE));
   /**
    * The number of significant decimal digits to which internal structures will maintain for
    * requests.
@@ -433,6 +443,12 @@
       new TypedDriverOption<>(
           DefaultDriverOption.METRICS_SESSION_THROTTLING_SLO,
           GenericType.listOf(GenericType.DURATION));
+  /** Optional pre-defined percentile of throttling delay to publish, as a list of percentiles . */
+  public static final TypedDriverOption<List<Double>>
+      METRICS_SESSION_THROTTLING_PUBLISH_PERCENTILES =
+          new TypedDriverOption<>(
+              DefaultDriverOption.METRICS_SESSION_THROTTLING_PUBLISH_PERCENTILES,
+              GenericType.listOf(GenericType.DOUBLE));
   /**
    * The number of significant decimal digits to which internal structures will maintain for
    * throttling.
@@ -457,6 +473,12 @@
       new TypedDriverOption<>(
           DefaultDriverOption.METRICS_NODE_CQL_MESSAGES_SLO,
           GenericType.listOf(GenericType.DURATION));
+  /** Optional pre-defined percentile of node cql messages to publish, as a list of percentiles . */
+  public static final TypedDriverOption<List<Double>>
+      METRICS_NODE_CQL_MESSAGES_PUBLISH_PERCENTILES =
+          new TypedDriverOption<>(
+              DefaultDriverOption.METRICS_NODE_CQL_MESSAGES_PUBLISH_PERCENTILES,
+              GenericType.listOf(GenericType.DOUBLE));
   /**
    * The number of significant decimal digits to which internal structures will maintain for
    * requests.
@@ -701,6 +723,15 @@
               DseDriverOption.CONTINUOUS_PAGING_METRICS_SESSION_CQL_REQUESTS_SLO,
               GenericType.listOf(GenericType.DURATION));
   /**
+   * Optional pre-defined percentile of continuous paging cql requests to publish, as a list of
+   * percentiles .
+   */
+  public static final TypedDriverOption<List<Double>>
+      CONTINUOUS_PAGING_METRICS_SESSION_CQL_REQUESTS_PUBLISH_PERCENTILES =
+          new TypedDriverOption<>(
+              DseDriverOption.CONTINUOUS_PAGING_METRICS_SESSION_CQL_REQUESTS_PUBLISH_PERCENTILES,
+              GenericType.listOf(GenericType.DOUBLE));
+  /**
    * The number of significant decimal digits to which internal structures will maintain for
    * continuous requests.
    */
@@ -774,6 +805,12 @@
       new TypedDriverOption<>(
           DseDriverOption.METRICS_SESSION_GRAPH_REQUESTS_SLO,
           GenericType.listOf(GenericType.DURATION));
+  /** Optional pre-defined percentile of graph requests to publish, as a list of percentiles . */
+  public static final TypedDriverOption<List<Double>>
+      METRICS_SESSION_GRAPH_REQUESTS_PUBLISH_PERCENTILES =
+          new TypedDriverOption<>(
+              DseDriverOption.METRICS_SESSION_GRAPH_REQUESTS_PUBLISH_PERCENTILES,
+              GenericType.listOf(GenericType.DOUBLE));
   /**
    * The number of significant decimal digits to which internal structures will maintain for graph
    * requests.
@@ -799,6 +836,14 @@
           DseDriverOption.METRICS_NODE_GRAPH_MESSAGES_SLO,
           GenericType.listOf(GenericType.DURATION));
   /**
+   * Optional pre-defined percentile of node graph requests to publish, as a list of percentiles .
+   */
+  public static final TypedDriverOption<List<Double>>
+      METRICS_NODE_GRAPH_MESSAGES_PUBLISH_PERCENTILES =
+          new TypedDriverOption<>(
+              DseDriverOption.METRICS_NODE_GRAPH_MESSAGES_PUBLISH_PERCENTILES,
+              GenericType.listOf(GenericType.DOUBLE));
+  /**
    * The number of significant decimal digits to which internal structures will maintain for graph
    * requests.
    */
diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf
index ee83280..4c58a16 100644
--- a/core/src/main/resources/reference.conf
+++ b/core/src/main/resources/reference.conf
@@ -1434,6 +1434,16 @@
       // prefix = "cassandra"
     }
 
+    histograms {
+      # Adds histogram buckets used to generate aggregable percentile approximations in monitoring
+      # systems that have query facilities to do so (e.g. Prometheus histogram_quantile, Atlas percentiles).
+      #
+      # Required: no
+      # Modifiable at runtime: no
+      # Overridable in a profile: no
+      generate-aggregable = true
+    }
+
     # The session-level metrics (all disabled by default).
     #
     # Required: yes
@@ -1526,7 +1536,7 @@
       # Modifiable at runtime: no
       # Overridable in a profile: no
       cql-requests {
-        
+
         # The largest latency that we expect to record.
         #
         # This should be slightly higher than request.timeout (in theory, readings can't be higher
@@ -1569,7 +1579,7 @@
         # time).
         # Valid for: Dropwizard.
         refresh-interval = 5 minutes
-        
+
         # An optional list of latencies to track as part of the application's service-level
         # objectives (SLOs).
         #
@@ -1577,7 +1587,11 @@
         # buckets used to generate aggregable percentile approximations.
         # Valid for: Micrometer.
         // slo = [ 100 milliseconds, 500 milliseconds, 1 second ]
-        
+
+        # An optional list of percentiles to be published by Micrometer. Produces an additional time series for each requested percentile.
+        # This percentile is computed locally, and so can't be aggregated with percentiles computed across other dimensions (e.g. in a different instance)
+        # Valid for: Micrometer.
+        // publish-percentiles = [ 0.75, 0.95, 0.99 ]
       }
 
       # Required: if the 'throttling.delay' metric is enabled, and Dropwizard or Micrometer is used.
@@ -1589,6 +1603,7 @@
         significant-digits = 3
         refresh-interval = 5 minutes
         // slo = [ 100 milliseconds, 500 milliseconds, 1 second ]
+        // publish-percentiles = [ 0.75, 0.95, 0.99 ]
       }
 
       # Required: if the 'continuous-cql-requests' metric is enabled, and Dropwizard or Micrometer
@@ -1601,6 +1616,7 @@
         significant-digits = 3
         refresh-interval = 5 minutes
         // slo = [ 100 milliseconds, 500 milliseconds, 1 second ]
+        // publish-percentiles = [ 0.75, 0.95, 0.99 ]
       }
 
       # Required: if the 'graph-requests' metric is enabled, and Dropwizard or Micrometer is used.
@@ -1612,6 +1628,7 @@
         significant-digits = 3
         refresh-interval = 5 minutes
         // slo = [ 100 milliseconds, 500 milliseconds, 1 second ]
+        // publish-percentiles = [ 0.75, 0.95, 0.99 ]
       }
     }
     # The node-level metrics (all disabled by default).
@@ -1776,6 +1793,7 @@
         significant-digits = 3
         refresh-interval = 5 minutes
         // slo = [ 100 milliseconds, 500 milliseconds, 1 second ]
+        // publish-percentiles = [ 0.75, 0.95, 0.99 ]
       }
 
       # See graph-requests in the `session` section
@@ -1789,6 +1807,7 @@
         significant-digits = 3
         refresh-interval = 5 minutes
         // slo = [ 100 milliseconds, 500 milliseconds, 1 second ]
+        // publish-percentiles = [ 0.75, 0.95, 0.99 ]
       }
 
       # The time after which the node level metrics will be evicted.
diff --git a/metrics/micrometer/src/main/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerMetricUpdater.java b/metrics/micrometer/src/main/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerMetricUpdater.java
index c30dcc1..4785da1 100644
--- a/metrics/micrometer/src/main/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerMetricUpdater.java
+++ b/metrics/micrometer/src/main/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerMetricUpdater.java
@@ -15,7 +15,9 @@
  */
 package com.datastax.oss.driver.internal.metrics.micrometer;
 
+import com.datastax.oss.driver.api.core.config.DefaultDriverOption;
 import com.datastax.oss.driver.api.core.config.DriverExecutionProfile;
+import com.datastax.oss.driver.api.core.config.DriverOption;
 import com.datastax.oss.driver.internal.core.context.InternalDriverContext;
 import com.datastax.oss.driver.internal.core.metrics.AbstractMetricUpdater;
 import com.datastax.oss.driver.internal.core.metrics.MetricId;
@@ -27,6 +29,7 @@
 import io.micrometer.core.instrument.MeterRegistry;
 import io.micrometer.core.instrument.Tag;
 import io.micrometer.core.instrument.Timer;
+import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -151,12 +154,31 @@
   }
 
   protected Timer.Builder configureTimer(Timer.Builder builder, MetricT metric, MetricId id) {
-    return builder.publishPercentileHistogram();
+    DriverExecutionProfile profile = context.getConfig().getDefaultProfile();
+    if (profile.getBoolean(DefaultDriverOption.METRICS_GENERATE_AGGREGABLE_HISTOGRAMS)) {
+      builder.publishPercentileHistogram();
+    }
+    return builder;
   }
 
   @SuppressWarnings("unused")
   protected DistributionSummary.Builder configureDistributionSummary(
       DistributionSummary.Builder builder, MetricT metric, MetricId id) {
-    return builder.publishPercentileHistogram();
+    DriverExecutionProfile profile = context.getConfig().getDefaultProfile();
+    if (profile.getBoolean(DefaultDriverOption.METRICS_GENERATE_AGGREGABLE_HISTOGRAMS)) {
+      builder.publishPercentileHistogram();
+    }
+    return builder;
+  }
+
+  static double[] toDoubleArray(List<Double> doubleList) {
+    return doubleList.stream().mapToDouble(Double::doubleValue).toArray();
+  }
+
+  static void configurePercentilesPublishIfDefined(
+      Timer.Builder builder, DriverExecutionProfile profile, DriverOption driverOption) {
+    if (profile.isDefined(driverOption)) {
+      builder.publishPercentiles(toDoubleArray(profile.getDoubleList(driverOption)));
+    }
   }
 }
diff --git a/metrics/micrometer/src/main/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerNodeMetricUpdater.java b/metrics/micrometer/src/main/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerNodeMetricUpdater.java
index 0f5dada..d6359bc 100644
--- a/metrics/micrometer/src/main/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerNodeMetricUpdater.java
+++ b/metrics/micrometer/src/main/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerNodeMetricUpdater.java
@@ -96,9 +96,9 @@
   @Override
   protected Timer.Builder configureTimer(Timer.Builder builder, NodeMetric metric, MetricId id) {
     DriverExecutionProfile profile = context.getConfig().getDefaultProfile();
+    super.configureTimer(builder, metric, id);
     if (metric == DefaultNodeMetric.CQL_MESSAGES) {
-      return builder
-          .publishPercentileHistogram()
+      builder
           .minimumExpectedValue(
               profile.getDuration(DefaultDriverOption.METRICS_NODE_CQL_MESSAGES_LOWEST))
           .maximumExpectedValue(
@@ -111,9 +111,11 @@
                   : null)
           .percentilePrecision(
               profile.getInt(DefaultDriverOption.METRICS_NODE_CQL_MESSAGES_DIGITS));
+
+      configurePercentilesPublishIfDefined(
+          builder, profile, DefaultDriverOption.METRICS_NODE_CQL_MESSAGES_PUBLISH_PERCENTILES);
     } else if (metric == DseNodeMetric.GRAPH_MESSAGES) {
-      return builder
-          .publishPercentileHistogram()
+      builder
           .minimumExpectedValue(
               profile.getDuration(DseDriverOption.METRICS_NODE_GRAPH_MESSAGES_LOWEST))
           .maximumExpectedValue(
@@ -125,7 +127,10 @@
                       .toArray(new Duration[0])
                   : null)
           .percentilePrecision(profile.getInt(DseDriverOption.METRICS_NODE_GRAPH_MESSAGES_DIGITS));
+
+      configurePercentilesPublishIfDefined(
+          builder, profile, DseDriverOption.METRICS_NODE_GRAPH_MESSAGES_PUBLISH_PERCENTILES);
     }
-    return super.configureTimer(builder, metric, id);
+    return builder;
   }
 }
diff --git a/metrics/micrometer/src/main/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerSessionMetricUpdater.java b/metrics/micrometer/src/main/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerSessionMetricUpdater.java
index bb361b8..f9387f1 100644
--- a/metrics/micrometer/src/main/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerSessionMetricUpdater.java
+++ b/metrics/micrometer/src/main/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerSessionMetricUpdater.java
@@ -63,9 +63,9 @@
   @Override
   protected Timer.Builder configureTimer(Timer.Builder builder, SessionMetric metric, MetricId id) {
     DriverExecutionProfile profile = context.getConfig().getDefaultProfile();
+    super.configureTimer(builder, metric, id);
     if (metric == DefaultSessionMetric.CQL_REQUESTS) {
-      return builder
-          .publishPercentileHistogram()
+      builder
           .minimumExpectedValue(
               profile.getDuration(DefaultDriverOption.METRICS_SESSION_CQL_REQUESTS_LOWEST))
           .maximumExpectedValue(
@@ -80,9 +80,11 @@
               profile.isDefined(DefaultDriverOption.METRICS_SESSION_CQL_REQUESTS_DIGITS)
                   ? profile.getInt(DefaultDriverOption.METRICS_SESSION_CQL_REQUESTS_DIGITS)
                   : null);
+
+      configurePercentilesPublishIfDefined(
+          builder, profile, DefaultDriverOption.METRICS_SESSION_CQL_REQUESTS_PUBLISH_PERCENTILES);
     } else if (metric == DefaultSessionMetric.THROTTLING_DELAY) {
-      return builder
-          .publishPercentileHistogram()
+      builder
           .minimumExpectedValue(
               profile.getDuration(DefaultDriverOption.METRICS_SESSION_THROTTLING_LOWEST))
           .maximumExpectedValue(
@@ -97,9 +99,11 @@
               profile.isDefined(DefaultDriverOption.METRICS_SESSION_THROTTLING_DIGITS)
                   ? profile.getInt(DefaultDriverOption.METRICS_SESSION_THROTTLING_DIGITS)
                   : null);
+
+      configurePercentilesPublishIfDefined(
+          builder, profile, DefaultDriverOption.METRICS_SESSION_THROTTLING_PUBLISH_PERCENTILES);
     } else if (metric == DseSessionMetric.CONTINUOUS_CQL_REQUESTS) {
-      return builder
-          .publishPercentileHistogram()
+      builder
           .minimumExpectedValue(
               profile.getDuration(
                   DseDriverOption.CONTINUOUS_PAGING_METRICS_SESSION_CQL_REQUESTS_LOWEST))
@@ -119,9 +123,13 @@
                   ? profile.getInt(
                       DseDriverOption.CONTINUOUS_PAGING_METRICS_SESSION_CQL_REQUESTS_DIGITS)
                   : null);
+
+      configurePercentilesPublishIfDefined(
+          builder,
+          profile,
+          DseDriverOption.CONTINUOUS_PAGING_METRICS_SESSION_CQL_REQUESTS_PUBLISH_PERCENTILES);
     } else if (metric == DseSessionMetric.GRAPH_REQUESTS) {
-      return builder
-          .publishPercentileHistogram()
+      builder
           .minimumExpectedValue(
               profile.getDuration(DseDriverOption.METRICS_SESSION_GRAPH_REQUESTS_LOWEST))
           .maximumExpectedValue(
@@ -136,7 +144,10 @@
               profile.isDefined(DseDriverOption.METRICS_SESSION_GRAPH_REQUESTS_DIGITS)
                   ? profile.getInt(DseDriverOption.METRICS_SESSION_GRAPH_REQUESTS_DIGITS)
                   : null);
+
+      configurePercentilesPublishIfDefined(
+          builder, profile, DseDriverOption.METRICS_SESSION_GRAPH_REQUESTS_PUBLISH_PERCENTILES);
     }
-    return super.configureTimer(builder, metric, id);
+    return builder;
   }
 }
diff --git a/metrics/micrometer/src/test/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerNodeMetricUpdaterTest.java b/metrics/micrometer/src/test/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerNodeMetricUpdaterTest.java
index fbdfb7b..e5482aa 100644
--- a/metrics/micrometer/src/test/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerNodeMetricUpdaterTest.java
+++ b/metrics/micrometer/src/test/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerNodeMetricUpdaterTest.java
@@ -152,7 +152,8 @@
       DriverOption lowest,
       DriverOption highest,
       DriverOption digits,
-      DriverOption sla) {
+      DriverOption sla,
+      DriverOption percentiles) {
     // given
     Node node = mock(Node.class);
     InternalDriverContext context = mock(InternalDriverContext.class);
@@ -174,6 +175,8 @@
     when(profile.isDefined(sla)).thenReturn(true);
     when(profile.getDurationList(sla))
         .thenReturn(Arrays.asList(Duration.ofMillis(100), Duration.ofMillis(500)));
+    when(profile.isDefined(percentiles)).thenReturn(true);
+    when(profile.getDoubleList(percentiles)).thenReturn(Arrays.asList(0.75, 0.95, 0.99));
     when(generator.nodeMetricId(node, metric)).thenReturn(METRIC_ID);
 
     SimpleMeterRegistry registry = spy(new SimpleMeterRegistry());
@@ -190,6 +193,63 @@
     assertThat(timer.count()).isEqualTo(10);
     HistogramSnapshot snapshot = timer.takeSnapshot();
     assertThat(snapshot.histogramCounts()).hasSize(2);
+    assertThat(snapshot.percentileValues()).hasSize(3);
+    assertThat(snapshot.percentileValues())
+        .satisfiesExactlyInAnyOrder(
+            valuePercentile -> assertThat(valuePercentile.percentile()).isEqualTo(0.75),
+            valuePercentile -> assertThat(valuePercentile.percentile()).isEqualTo(0.95),
+            valuePercentile -> assertThat(valuePercentile.percentile()).isEqualTo(0.99));
+  }
+
+  @Test
+  @UseDataProvider(value = "timerMetrics")
+  public void should_not_create_sla_percentiles(
+      NodeMetric metric,
+      DriverOption lowest,
+      DriverOption highest,
+      DriverOption digits,
+      DriverOption sla,
+      DriverOption percentiles) {
+    // given
+    Node node = mock(Node.class);
+    InternalDriverContext context = mock(InternalDriverContext.class);
+    DriverExecutionProfile profile = mock(DriverExecutionProfile.class);
+    DriverConfig config = mock(DriverConfig.class);
+    MetricIdGenerator generator = mock(MetricIdGenerator.class);
+    Set<NodeMetric> enabledMetrics = Collections.singleton(metric);
+
+    // when
+    when(context.getSessionName()).thenReturn("prefix");
+    when(context.getConfig()).thenReturn(config);
+    when(config.getDefaultProfile()).thenReturn(profile);
+    when(context.getMetricIdGenerator()).thenReturn(generator);
+    when(profile.getDuration(DefaultDriverOption.METRICS_NODE_EXPIRE_AFTER))
+        .thenReturn(Duration.ofHours(1));
+    when(profile.getDuration(lowest)).thenReturn(Duration.ofMillis(10));
+    when(profile.getDuration(highest)).thenReturn(Duration.ofSeconds(1));
+    when(profile.getInt(digits)).thenReturn(5);
+    when(profile.isDefined(sla)).thenReturn(false);
+    when(profile.getDurationList(sla))
+        .thenReturn(Arrays.asList(Duration.ofMillis(100), Duration.ofMillis(500)));
+    when(profile.isDefined(percentiles)).thenReturn(false);
+    when(profile.getDoubleList(percentiles)).thenReturn(Arrays.asList(0.75, 0.95, 0.99));
+    when(generator.nodeMetricId(node, metric)).thenReturn(METRIC_ID);
+
+    SimpleMeterRegistry registry = spy(new SimpleMeterRegistry());
+    MicrometerNodeMetricUpdater updater =
+        new MicrometerNodeMetricUpdater(node, context, enabledMetrics, registry);
+
+    for (int i = 0; i < 10; i++) {
+      updater.updateTimer(metric, null, 100, TimeUnit.MILLISECONDS);
+    }
+
+    // then
+    Timer timer = registry.find(METRIC_ID.getName()).timer();
+    assertThat(timer).isNotNull();
+    assertThat(timer.count()).isEqualTo(10);
+    HistogramSnapshot snapshot = timer.takeSnapshot();
+    assertThat(snapshot.histogramCounts()).hasSize(0);
+    assertThat(snapshot.percentileValues()).hasSize(0);
   }
 
   @DataProvider
@@ -201,6 +261,7 @@
         DefaultDriverOption.METRICS_NODE_CQL_MESSAGES_HIGHEST,
         DefaultDriverOption.METRICS_NODE_CQL_MESSAGES_DIGITS,
         DefaultDriverOption.METRICS_NODE_CQL_MESSAGES_SLO,
+        DefaultDriverOption.METRICS_NODE_CQL_MESSAGES_PUBLISH_PERCENTILES,
       },
       {
         DseNodeMetric.GRAPH_MESSAGES,
@@ -208,6 +269,7 @@
         DseDriverOption.METRICS_NODE_GRAPH_MESSAGES_HIGHEST,
         DseDriverOption.METRICS_NODE_GRAPH_MESSAGES_DIGITS,
         DseDriverOption.METRICS_NODE_GRAPH_MESSAGES_SLO,
+        DseDriverOption.METRICS_NODE_GRAPH_MESSAGES_PUBLISH_PERCENTILES,
       },
     };
   }
diff --git a/metrics/micrometer/src/test/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerSessionMetricUpdaterTest.java b/metrics/micrometer/src/test/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerSessionMetricUpdaterTest.java
index 09b3e44..1e2d210 100644
--- a/metrics/micrometer/src/test/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerSessionMetricUpdaterTest.java
+++ b/metrics/micrometer/src/test/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerSessionMetricUpdaterTest.java
@@ -59,7 +59,8 @@
       DriverOption lowest,
       DriverOption highest,
       DriverOption digits,
-      DriverOption sla) {
+      DriverOption sla,
+      DriverOption percentiles) {
     // given
     InternalDriverContext context = mock(InternalDriverContext.class);
     DriverExecutionProfile profile = mock(DriverExecutionProfile.class);
@@ -80,6 +81,8 @@
     when(profile.isDefined(sla)).thenReturn(true);
     when(profile.getDurationList(sla))
         .thenReturn(Arrays.asList(Duration.ofMillis(100), Duration.ofMillis(500)));
+    when(profile.isDefined(percentiles)).thenReturn(true);
+    when(profile.getDoubleList(percentiles)).thenReturn(Arrays.asList(0.75, 0.95, 0.99));
     when(generator.sessionMetricId(metric)).thenReturn(METRIC_ID);
 
     SimpleMeterRegistry registry = spy(new SimpleMeterRegistry());
@@ -96,6 +99,61 @@
     assertThat(timer.count()).isEqualTo(10);
     HistogramSnapshot snapshot = timer.takeSnapshot();
     assertThat(snapshot.histogramCounts()).hasSize(2);
+    assertThat(snapshot.percentileValues()).hasSize(3);
+    assertThat(snapshot.percentileValues())
+        .satisfiesExactlyInAnyOrder(
+            valuePercentile -> assertThat(valuePercentile.percentile()).isEqualTo(0.75),
+            valuePercentile -> assertThat(valuePercentile.percentile()).isEqualTo(0.95),
+            valuePercentile -> assertThat(valuePercentile.percentile()).isEqualTo(0.99));
+  }
+
+  @Test
+  @UseDataProvider(value = "timerMetrics")
+  public void should_not_create_sla_percentiles(
+      SessionMetric metric,
+      DriverOption lowest,
+      DriverOption highest,
+      DriverOption digits,
+      DriverOption sla,
+      DriverOption percentiles) {
+    // given
+    InternalDriverContext context = mock(InternalDriverContext.class);
+    DriverExecutionProfile profile = mock(DriverExecutionProfile.class);
+    DriverConfig config = mock(DriverConfig.class);
+    MetricIdGenerator generator = mock(MetricIdGenerator.class);
+    Set<SessionMetric> enabledMetrics = Collections.singleton(metric);
+
+    // when
+    when(context.getSessionName()).thenReturn("prefix");
+    when(context.getConfig()).thenReturn(config);
+    when(config.getDefaultProfile()).thenReturn(profile);
+    when(context.getMetricIdGenerator()).thenReturn(generator);
+    when(profile.getDuration(DefaultDriverOption.METRICS_NODE_EXPIRE_AFTER))
+        .thenReturn(Duration.ofHours(1));
+    when(profile.isDefined(sla)).thenReturn(false);
+    when(profile.getDurationList(sla))
+        .thenReturn(Arrays.asList(Duration.ofMillis(100), Duration.ofMillis(500)));
+    when(profile.getBoolean(DefaultDriverOption.METRICS_GENERATE_AGGREGABLE_HISTOGRAMS))
+        .thenReturn(true);
+    when(profile.isDefined(percentiles)).thenReturn(false);
+    when(profile.getDoubleList(percentiles)).thenReturn(Arrays.asList(0.75, 0.95, 0.99));
+    when(generator.sessionMetricId(metric)).thenReturn(METRIC_ID);
+
+    SimpleMeterRegistry registry = new SimpleMeterRegistry();
+    MicrometerSessionMetricUpdater updater =
+        new MicrometerSessionMetricUpdater(context, enabledMetrics, registry);
+
+    for (int i = 0; i < 10; i++) {
+      updater.updateTimer(metric, null, 100, TimeUnit.MILLISECONDS);
+    }
+
+    // then
+    Timer timer = registry.find(METRIC_ID.getName()).timer();
+    assertThat(timer).isNotNull();
+    assertThat(timer.count()).isEqualTo(10);
+    HistogramSnapshot snapshot = timer.takeSnapshot();
+    assertThat(snapshot.histogramCounts()).hasSize(0);
+    assertThat(snapshot.percentileValues()).hasSize(0);
   }
 
   @DataProvider
@@ -107,6 +165,7 @@
         DefaultDriverOption.METRICS_SESSION_CQL_REQUESTS_HIGHEST,
         DefaultDriverOption.METRICS_SESSION_CQL_REQUESTS_DIGITS,
         DefaultDriverOption.METRICS_SESSION_CQL_REQUESTS_SLO,
+        DefaultDriverOption.METRICS_SESSION_CQL_REQUESTS_PUBLISH_PERCENTILES,
       },
       {
         DseSessionMetric.GRAPH_REQUESTS,
@@ -114,6 +173,7 @@
         DseDriverOption.METRICS_SESSION_GRAPH_REQUESTS_HIGHEST,
         DseDriverOption.METRICS_SESSION_GRAPH_REQUESTS_DIGITS,
         DseDriverOption.METRICS_SESSION_GRAPH_REQUESTS_SLO,
+        DseDriverOption.METRICS_SESSION_GRAPH_REQUESTS_PUBLISH_PERCENTILES,
       },
       {
         DseSessionMetric.CONTINUOUS_CQL_REQUESTS,
@@ -121,6 +181,7 @@
         DseDriverOption.CONTINUOUS_PAGING_METRICS_SESSION_CQL_REQUESTS_HIGHEST,
         DseDriverOption.CONTINUOUS_PAGING_METRICS_SESSION_CQL_REQUESTS_DIGITS,
         DseDriverOption.CONTINUOUS_PAGING_METRICS_SESSION_CQL_REQUESTS_SLO,
+        DseDriverOption.CONTINUOUS_PAGING_METRICS_SESSION_CQL_REQUESTS_PUBLISH_PERCENTILES
       },
       {
         DefaultSessionMetric.THROTTLING_DELAY,
@@ -128,6 +189,7 @@
         DefaultDriverOption.METRICS_SESSION_THROTTLING_HIGHEST,
         DefaultDriverOption.METRICS_SESSION_THROTTLING_DIGITS,
         DefaultDriverOption.METRICS_SESSION_THROTTLING_SLO,
+        DefaultDriverOption.METRICS_SESSION_THROTTLING_PUBLISH_PERCENTILES
       },
     };
   }