This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch branch_9_9
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/branch_9_9 by this push:
     new 36c4ad90531 SOLR-17628: Add query quantiles metrics to prometheus 
endpoint (#3164)
36c4ad90531 is described below

commit 36c4ad9053182f078d5ac17e94f3dfec6c91dc40
Author: Jude Muriithi <[email protected]>
AuthorDate: Mon Jul 7 14:13:37 2025 -0400

    SOLR-17628: Add query quantiles metrics to prometheus endpoint (#3164)
    
    Export metric timers via `wt=prometheus` as Prometheus summaries. This 
introduces count, sum, and quantiles.
---
 solr/CHANGES.txt                                   |  2 +
 .../prometheus/SolrPrometheusFormatter.java        | 77 ++++++++++++++++++----
 .../prometheus/core/SolrCoreHandlerMetric.java     |  7 +-
 .../prometheus/core/SolrCoreSearcherMetric.java    |  2 +-
 .../solr/handler/admin/MetricsHandlerTest.java     | 35 +++++-----
 .../solr/metrics/SolrPrometheusFormatterTest.java  | 13 ++--
 6 files changed, 96 insertions(+), 40 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index a574d7dbda9..da039bc0cd6 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -71,6 +71,8 @@ Improvements
 
 * SOLR-17746: Provide long form --jettyconfig option to go with -j in bin/solr 
scripts. (Eric Pugh, Rahul Goswami)
 
+* SOLR-17628: Export metric timers via `wt=prometheus` as Prometheus 
summaries. (Jude Muriithi, Matthew Biscocho)
+
 Optimizations
 ---------------------
 * SOLR-17578: Remove ZkController internal core supplier, for slightly faster 
reconnection after Zookeeper session loss. (Pierre Salagnac)
diff --git 
a/solr/core/src/java/org/apache/solr/metrics/prometheus/SolrPrometheusFormatter.java
 
b/solr/core/src/java/org/apache/solr/metrics/prometheus/SolrPrometheusFormatter.java
index 8c1ba8652ab..17fa5dc282a 100644
--- 
a/solr/core/src/java/org/apache/solr/metrics/prometheus/SolrPrometheusFormatter.java
+++ 
b/solr/core/src/java/org/apache/solr/metrics/prometheus/SolrPrometheusFormatter.java
@@ -18,17 +18,24 @@ package org.apache.solr.metrics.prometheus;
 
 import com.codahale.metrics.Meter;
 import com.codahale.metrics.Metric;
+import com.codahale.metrics.Snapshot;
 import com.codahale.metrics.Timer;
 import io.prometheus.metrics.model.snapshots.CounterSnapshot;
+import io.prometheus.metrics.model.snapshots.Exemplars;
 import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
 import io.prometheus.metrics.model.snapshots.Labels;
 import io.prometheus.metrics.model.snapshots.MetricMetadata;
 import io.prometheus.metrics.model.snapshots.MetricSnapshot;
 import io.prometheus.metrics.model.snapshots.MetricSnapshots;
+import io.prometheus.metrics.model.snapshots.Quantile;
+import io.prometheus.metrics.model.snapshots.Quantiles;
+import io.prometheus.metrics.model.snapshots.SummarySnapshot;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import org.apache.solr.util.stats.MetricUtils;
 
 /**
  * Base class for all {@link SolrPrometheusFormatter} holding {@link 
MetricSnapshot}s. Can export
@@ -38,10 +45,12 @@ import java.util.Map;
 public abstract class SolrPrometheusFormatter {
   protected final Map<String, List<CounterSnapshot.CounterDataPointSnapshot>> 
metricCounters;
   protected final Map<String, List<GaugeSnapshot.GaugeDataPointSnapshot>> 
metricGauges;
+  protected final Map<String, List<SummarySnapshot.SummaryDataPointSnapshot>> 
metricSummaries;
 
   public SolrPrometheusFormatter() {
     this.metricCounters = new HashMap<>();
     this.metricGauges = new HashMap<>();
+    this.metricSummaries = new HashMap<>();
   }
 
   /**
@@ -93,8 +102,8 @@ public abstract class SolrPrometheusFormatter {
   }
 
   /**
-   * Export {@link Timer} ands its mean rate to {@link
-   * 
io.prometheus.metrics.model.snapshots.GaugeSnapshot.GaugeDataPointSnapshot} and 
collect
+   * Export {@link Timer} ands its quantile data to {@link
+   * 
io.prometheus.metrics.model.snapshots.SummarySnapshot.SummaryDataPointSnapshot} 
and collect
    * datapoint
    *
    * @param metricName name of prometheus metric
@@ -102,9 +111,27 @@ public abstract class SolrPrometheusFormatter {
    * @param labels label names and values to record
    */
   public void exportTimer(String metricName, Timer dropwizardMetric, Labels 
labels) {
-    GaugeSnapshot.GaugeDataPointSnapshot dataPoint =
-        createGaugeDatapoint(dropwizardMetric.getSnapshot().getMean(), labels);
-    collectGaugeDatapoint(metricName, dataPoint);
+    Snapshot snapshot = dropwizardMetric.getSnapshot();
+
+    long count = snapshot.size();
+    double sum =
+        Arrays.stream(snapshot.getValues())
+            .asDoubleStream()
+            .map(num -> MetricUtils.nsToMs(num))
+            .sum();
+
+    Quantiles quantiles =
+        Quantiles.of(
+            List.of(
+                new Quantile(0.50, MetricUtils.nsToMs(snapshot.getMedian())),
+                new Quantile(0.75, 
MetricUtils.nsToMs(snapshot.get75thPercentile())),
+                new Quantile(0.99, 
MetricUtils.nsToMs(snapshot.get99thPercentile())),
+                new Quantile(0.999, 
MetricUtils.nsToMs(snapshot.get999thPercentile()))));
+
+    var summary =
+        new SummarySnapshot.SummaryDataPointSnapshot(
+            count, sum, quantiles, labels, Exemplars.EMPTY, 0L);
+    collectSummaryDatapoint(metricName, summary);
   }
 
   /**
@@ -207,20 +234,44 @@ public abstract class SolrPrometheusFormatter {
     metricGauges.get(metricName).add(dataPoint);
   }
 
+  /**
+   * Collects {@link 
io.prometheus.metrics.model.snapshots.SummarySnapshot.SummaryDataPointSnapshot}
+   * and appends to existing metric or create new metric if name does not exist
+   *
+   * @param metricName Name of metric
+   * @param dataPoint Gauge datapoint to be collected
+   */
+  public void collectSummaryDatapoint(
+      String metricName, SummarySnapshot.SummaryDataPointSnapshot dataPoint) {
+    metricSummaries.computeIfAbsent(metricName, k -> new 
ArrayList<>()).add(dataPoint);
+  }
+
   /**
    * Returns an immutable {@link MetricSnapshots} from the {@link
    * io.prometheus.metrics.model.snapshots.DataPointSnapshot}s collected from 
the registry
    */
   public MetricSnapshots collect() {
     ArrayList<MetricSnapshot> snapshots = new ArrayList<>();
-    for (String metricName : metricCounters.keySet()) {
-      snapshots.add(
-          new CounterSnapshot(new MetricMetadata(metricName), 
metricCounters.get(metricName)));
-    }
-    for (String metricName : metricGauges.keySet()) {
-      snapshots.add(
-          new GaugeSnapshot(new MetricMetadata(metricName), 
metricGauges.get(metricName)));
-    }
+
+    metricCounters
+        .entrySet()
+        .forEach(
+            entry ->
+                snapshots.add(
+                    new CounterSnapshot(new MetricMetadata(entry.getKey()), 
entry.getValue())));
+    metricGauges
+        .entrySet()
+        .forEach(
+            entry ->
+                snapshots.add(
+                    new GaugeSnapshot(new MetricMetadata(entry.getKey()), 
entry.getValue())));
+    metricSummaries
+        .entrySet()
+        .forEach(
+            entry ->
+                snapshots.add(
+                    new SummarySnapshot(new MetricMetadata(entry.getKey()), 
entry.getValue())));
+
     return new MetricSnapshots(snapshots);
   }
 }
diff --git 
a/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrCoreHandlerMetric.java
 
b/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrCoreHandlerMetric.java
index 9273e825759..ac61d6ef6f7 100644
--- 
a/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrCoreHandlerMetric.java
+++ 
b/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrCoreHandlerMetric.java
@@ -27,8 +27,7 @@ import 
org.apache.solr.metrics.prometheus.SolrPrometheusFormatter;
 public class SolrCoreHandlerMetric extends SolrCoreMetric {
   public static final String CORE_REQUESTS_TOTAL = 
"solr_metrics_core_requests";
   public static final String CORE_REQUESTS_UPDATE_HANDLER = 
"solr_metrics_core_update_handler";
-  public static final String CORE_REQUESTS_TOTAL_TIME = 
"solr_metrics_core_requests_time";
-  public static final String CORE_REQUEST_TIMES = 
"solr_metrics_core_average_request_time";
+  public static final String CORE_REQUEST_TIMES = 
"solr_metrics_core_request_time_ms";
 
   public SolrCoreHandlerMetric(Metric dropwizardMetric, String metricName) {
     super(dropwizardMetric, metricName);
@@ -58,10 +57,6 @@ public class SolrCoreHandlerMetric extends SolrCoreMetric {
     } else if (dropwizardMetric instanceof Counter) {
       if (metricName.endsWith("requests")) {
         formatter.exportCounter(CORE_REQUESTS_TOTAL, (Counter) 
dropwizardMetric, getLabels());
-      } else if (metricName.endsWith("totalTime")) {
-        // Do not need type label for total time
-        labels.remove("type");
-        formatter.exportCounter(CORE_REQUESTS_TOTAL_TIME, (Counter) 
dropwizardMetric, getLabels());
       }
     } else if (dropwizardMetric instanceof Gauge) {
       if (!metricName.endsWith("handlerStart")) {
diff --git 
a/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrCoreSearcherMetric.java
 
b/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrCoreSearcherMetric.java
index 7ca694d311d..0ab89e325c7 100644
--- 
a/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrCoreSearcherMetric.java
+++ 
b/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrCoreSearcherMetric.java
@@ -27,7 +27,7 @@ import 
org.apache.solr.metrics.prometheus.SolrPrometheusFormatter;
 /** Dropwizard metrics of name SEARCHER.* */
 public class SolrCoreSearcherMetric extends SolrCoreMetric {
   public static final String CORE_SEARCHER_METRICS = 
"solr_metrics_core_searcher_documents";
-  public static final String CORE_SEARCHER_TIMES = 
"solr_metrics_core_average_searcher_warmup_time";
+  public static final String CORE_SEARCHER_TIMES = 
"solr_metrics_core_searcher_warmup_time_ms";
 
   public SolrCoreSearcherMetric(Metric dropwizardMetric, String metricName) {
     super(dropwizardMetric, metricName);
diff --git 
a/solr/core/src/test/org/apache/solr/handler/admin/MetricsHandlerTest.java 
b/solr/core/src/test/org/apache/solr/handler/admin/MetricsHandlerTest.java
index 8eaf99bd01f..c7d860181a8 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/MetricsHandlerTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/MetricsHandlerTest.java
@@ -25,6 +25,7 @@ import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
 import io.prometheus.metrics.model.snapshots.Labels;
 import io.prometheus.metrics.model.snapshots.MetricSnapshot;
 import io.prometheus.metrics.model.snapshots.MetricSnapshots;
+import io.prometheus.metrics.model.snapshots.SummarySnapshot;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
@@ -730,12 +731,12 @@ public class MetricsHandlerTest extends SolrTestCaseJ4 {
     assertNotNull(actualSnapshots);
 
     MetricSnapshot actualSnapshot =
-        getMetricSnapshot(actualSnapshots, 
"solr_metrics_core_average_request_time");
-    GaugeSnapshot.GaugeDataPointSnapshot actualGaugeDataPoint =
-        getGaugeDatapointSnapshot(
+        getMetricSnapshot(actualSnapshots, 
"solr_metrics_core_request_time_ms");
+    SummarySnapshot.SummaryDataPointSnapshot actualSummaryDataPoint =
+        getSummaryDataPointSnapshot(
             actualSnapshot,
             Labels.of("category", "QUERY", "core", "collection1", "handler", 
"/select[shard]"));
-    assertEquals(0, actualGaugeDataPoint.getValue(), 0);
+    assertEquals(0, (float) actualSummaryDataPoint.getCount(), 0);
 
     actualSnapshot = getMetricSnapshot(actualSnapshots, 
"solr_metrics_core_requests");
     CounterSnapshot.CounterDataPointSnapshot actualCounterDataPoint =
@@ -753,7 +754,7 @@ public class MetricsHandlerTest extends SolrTestCaseJ4 {
     assertEquals(0, actualCounterDataPoint.getValue(), 0);
 
     actualSnapshot = getMetricSnapshot(actualSnapshots, 
"solr_metrics_core_cache");
-    actualGaugeDataPoint =
+    GaugeSnapshot.GaugeDataPointSnapshot actualGaugeDataPoint =
         getGaugeDatapointSnapshot(
             actualSnapshot,
             Labels.of("cacheType", "fieldValueCache", "core", "collection1", 
"item", "hits"));
@@ -766,13 +767,6 @@ public class MetricsHandlerTest extends SolrTestCaseJ4 {
             Labels.of("item", "default", "core", "collection1", "type", 
"SolrFragmenter"));
     assertEquals(0, actualCounterDataPoint.getValue(), 0);
 
-    actualSnapshot = getMetricSnapshot(actualSnapshots, 
"solr_metrics_core_requests_time");
-    actualCounterDataPoint =
-        getCounterDatapointSnapshot(
-            actualSnapshot,
-            Labels.of("category", "QUERY", "core", "collection1", "handler", 
"/select[shard]"));
-    assertEquals(0, actualCounterDataPoint.getValue(), 0);
-
     actualSnapshot = getMetricSnapshot(actualSnapshots, 
"solr_metrics_core_searcher_documents");
     actualGaugeDataPoint =
         getGaugeDatapointSnapshot(
@@ -795,11 +789,11 @@ public class MetricsHandlerTest extends SolrTestCaseJ4 {
     assertEquals(0, actualGaugeDataPoint.getValue(), 0);
 
     actualSnapshot =
-        getMetricSnapshot(actualSnapshots, 
"solr_metrics_core_average_searcher_warmup_time");
-    actualGaugeDataPoint =
-        getGaugeDatapointSnapshot(
+        getMetricSnapshot(actualSnapshots, 
"solr_metrics_core_searcher_warmup_time_ms");
+    actualSummaryDataPoint =
+        getSummaryDataPointSnapshot(
             actualSnapshot, Labels.of("core", "collection1", "type", 
"warmup"));
-    assertEquals(0, actualGaugeDataPoint.getValue(), 0);
+    assertEquals(0, (float) actualSummaryDataPoint.getCount(), 0);
 
     handler.close();
   }
@@ -1183,6 +1177,15 @@ public class MetricsHandlerTest extends SolrTestCaseJ4 {
             .get();
   }
 
+  private SummarySnapshot.SummaryDataPointSnapshot getSummaryDataPointSnapshot(
+      MetricSnapshot snapshot, Labels labels) {
+    return (SummarySnapshot.SummaryDataPointSnapshot)
+        snapshot.getDataPoints().stream()
+            .filter(ss -> ss.getLabels().hasSameValues(labels))
+            .findAny()
+            .get();
+  }
+
   static class RefreshablePluginHolder extends 
PluginBag.PluginHolder<SolrRequestHandler> {
 
     private DumpRequestHandler rh;
diff --git 
a/solr/core/src/test/org/apache/solr/metrics/SolrPrometheusFormatterTest.java 
b/solr/core/src/test/org/apache/solr/metrics/SolrPrometheusFormatterTest.java
index 76077385800..6a99a790dff 100644
--- 
a/solr/core/src/test/org/apache/solr/metrics/SolrPrometheusFormatterTest.java
+++ 
b/solr/core/src/test/org/apache/solr/metrics/SolrPrometheusFormatterTest.java
@@ -28,6 +28,7 @@ import com.codahale.metrics.Timer;
 import io.prometheus.metrics.model.snapshots.CounterSnapshot;
 import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
 import io.prometheus.metrics.model.snapshots.Labels;
+import io.prometheus.metrics.model.snapshots.SummarySnapshot;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -84,11 +85,11 @@ public class SolrPrometheusFormatterTest extends 
SolrTestCaseJ4 {
 
     Labels expectedLabels = Labels.of("test", "test-value");
     testFormatter.exportTimer(expectedMetricName, metric, expectedLabels);
-    
assertTrue(testFormatter.getMetricGauges().containsKey(expectedMetricName));
+    
assertTrue(testFormatter.getMetricSummaries().containsKey(expectedMetricName));
 
-    GaugeSnapshot.GaugeDataPointSnapshot actual =
-        testFormatter.getMetricGauges().get("test_metric").get(0);
-    assertEquals(5000000000L, actual.getValue(), 500000000L);
+    SummarySnapshot.SummaryDataPointSnapshot actual =
+        testFormatter.getMetricSummaries().get("test_metric").get(0);
+    assertEquals(5000L, actual.getSum(), 500L);
     assertEquals(expectedLabels, actual.getLabels());
   }
 
@@ -199,5 +200,9 @@ public class SolrPrometheusFormatterTest extends 
SolrTestCaseJ4 {
     public Map<String, List<GaugeSnapshot.GaugeDataPointSnapshot>> 
getMetricGauges() {
       return metricGauges;
     }
+
+    public Map<String, List<SummarySnapshot.SummaryDataPointSnapshot>> 
getMetricSummaries() {
+      return metricSummaries;
+    }
   }
 }

Reply via email to