You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ml...@apache.org on 2025/09/26 22:11:27 UTC

(solr) branch feature/SOLR-17458-rebased created (now bed6205d9b4)

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

mlbiscoc pushed a change to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git


      at bed6205d9b4 SOLR-17806: Migrate suggest component and cross-dc metrics to OTEL (#3668)

This branch includes the following new commits:

     new 6a62ea4d31d SOLR-17774: Refactor initialization of GlobalOpenTelemetry (#3379)
     new 437767d5208 SOLR-17785: Integrate foundational OTEL meter instruments (#3384)
     new 50af82fbbc1 SOLR-17793: Create dedicated SdkMeterProviders for Solr core metrics (#3402)
     new fcfb995949e Change up requestHandlerBase (#3415)
     new d3e23eac4ce SOLR-17806: Migrate SolrCore metrics to OTEL (#3430)
     new 8f348551157 SOLR-17806: Migrate DirectUpdateHandler2 metrics to OTEL (#3417)
     new ed7f2d116ad SOLR-17806: Migrate PeerSync and PeerSyncWithLeader metrics to OTEL (#3440)
     new bee4a36db5b SOLR-17806: Migrate ADMIN node registry metrics to OTEL (#3444)
     new 2c2183d3239 SOLR-17806: Migrate UpdateLog metrics to OTEL (#3438)
     new 8e02bfc4e87 SOLR-17818: Expose Open Metrics 1.0 format from /admin/metrics and exemplar support (#3427)
     new 3d35f4defe9 SOLR-17827: Remove JXM, SLF4J and Graphite reporters (#3443)
     new 42c9b415738 SOLR-17806: Migrate ZkContainer metrics to OTEL (#3453)
     new bfa519d3689 SOLR-17806: Use OtelUnit to create Otel instruments with units as metadata (#3466)
     new 9561d06026f SOLR-14393: Remove HIGHLIGHTER, QUERYPARSER, SPELLCHECKER metrics (#3469)
     new 0832659324d Build failure from zkContainer
     new 09125e74c36 SOLR-17806: Recreate OTEL metric registries on core rename/swapping (#3458)
     new b966470824a Close OTEL Observable instruments (#3517)
     new a6b687c8b04 SOLR-17806: OTEL Java 17 Runtime telemetry for JVM metrics (#3494)
     new 5c87f8d8597 SOLR-17806: switch ReplicationHandler metrics to OTEL (#3514)
     new 046b206de34 SOLR-17854: Support filters to /admin/metrics endpoint for Prometheus metrics (#3499)
     new e551fa15e48 SOLR-17806: Migrate SolrIndexWriter metrics to OTEL (#3568)
     new 838b38a14bc SOLR-17806 Move SolrMetricTestUtils to test-framework (#3624)
     new ebe843be934 Fix test failure on AbstractBasicDistributedZkTestBase (#3620)
     new e6d4d446d75 SOLR-17806: Migrate Overseer and Blockcache metrics to OTEL (#3607)
     new e9ee7836d52 SOLR-17806 Migrate AuthenticationPlugin metrics to OTEL (#3636)
     new 63f8fccf259 SOLR-17806: Migrate thread pool metrics to OTEL (#3519)
     new 598b5d75d4f Migrate FieldCacheMetrics to OTEL (#3619)
     new 1bb61d17e94 SOLR-17806: Migrate AuditLoggerPlugin to OTEL (#3665)
     new e02bd5367a9 SOLR-17828: Remove Prometheus exporter (#3470)
     new 06775b0deb8 SOLR-17806: Migrate CaffeineCache to OTEL (#3671)
     new c022d6bb427 SOLR-17806: Migrate SolrIndexSearcher metrics to OTEL (#3482)
     new fc3545764a0 SOLR-17798: Integrate SDK OTLP metric exporter (#3413)
     new bed6205d9b4 SOLR-17806: Migrate suggest component and cross-dc metrics to OTEL (#3668)

The 33 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(solr) 08/33: SOLR-17806: Migrate ADMIN node registry metrics to OTEL (#3444)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit bee4a36db5b5b8f3bbae53d67eb1c991bede01dc
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Wed Aug 6 10:31:39 2025 -0400

    SOLR-17806: Migrate ADMIN node registry metrics to OTEL (#3444)
    
    * Migrate admin node metrics to OTEL
    
    * Cleanup
    
    * Fix up some tests
    
    * Move some things around
    
    * Bad apple test mbeans handler
    
    * mapToDouble
    
    * Skip whitespace trimming and skipping
---
 .../java/org/apache/solr/core/CoreContainer.java   |  40 +++---
 .../apache/solr/handler/RequestHandlerBase.java    | 141 +++++++++------------
 .../solr/handler/component/SearchHandler.java      |  17 +--
 .../apache/solr/jersey/RequestMetricHandling.java  |  12 +-
 .../apache/solr/metrics/SolrCoreMetricManager.java |   3 +-
 .../apache/solr/metrics/SolrMetricProducer.java    |   1 +
 .../solr/cloud/TestRandomRequestDistribution.java  |  59 +++++----
 .../org/apache/solr/core/RequestHandlersTest.java  |  31 +++--
 .../solr/handler/RequestHandlerBaseTest.java       |   5 +-
 .../solr/handler/RequestHandlerMetricsTest.java    |   3 +
 .../solr/handler/admin/MBeansHandlerTest.java      |   4 +
 .../solr/handler/admin/MetricsHandlerTest.java     |   4 +
 .../TestPrometheusResponseWriterCloud.java         |  42 ++----
 .../solr/client/solrj/SolrJMetricTestUtils.java    | 117 +++++++++++++++++
 .../solrj/impl/CloudHttp2SolrClientRetryTest.java  |  30 +----
 .../solrj/impl/CloudHttp2SolrClientTest.java       | 105 ++++-----------
 .../solrj/impl/CloudSolrClientRetryTest.java       |  32 +----
 .../client/solrj/impl/CloudSolrClientTest.java     |  98 ++++----------
 .../solr/client/solrj/request/TestCoreAdmin.java   |   2 +
 19 files changed, 360 insertions(+), 386 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index 02b63fe0c16..59e5fe8c561 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -26,6 +26,7 @@ import static org.apache.solr.common.params.CommonParams.INFO_HANDLER_PATH;
 import static org.apache.solr.common.params.CommonParams.METRICS_PATH;
 import static org.apache.solr.common.params.CommonParams.ZK_PATH;
 import static org.apache.solr.common.params.CommonParams.ZK_STATUS_PATH;
+import static org.apache.solr.metrics.SolrMetricProducer.HANDLER_ATTR;
 import static org.apache.solr.search.SolrIndexSearcher.EXECUTOR_MAX_CPU_THREADS;
 import static org.apache.solr.security.AuthenticationPlugin.AUTHENTICATION_PLUGIN_PROP;
 
@@ -515,9 +516,10 @@ public class CoreContainer {
               newVersion, getResourceLoader().newInstance(klas, AuditLoggerPlugin.class));
 
       newAuditloggerPlugin.plugin.init(auditConf);
-      // TODO SOLR-17458: Add Otel
       newAuditloggerPlugin.plugin.initializeMetrics(
-          solrMetricsContext, Attributes.empty(), "/auditlogging");
+          solrMetricsContext,
+          Attributes.builder().put(HANDLER_ATTR, "/auditlogging").build(),
+          "/auditlogging");
     } else {
       log.debug("Security conf doesn't exist. Skipping setup for audit logging module.");
     }
@@ -582,9 +584,10 @@ public class CoreContainer {
     if (authenticationPlugin != null) {
       authenticationPlugin.plugin.init(authenticationConfig);
       setupHttpClientForAuthPlugin(authenticationPlugin.plugin);
-      // TODO SOLR-17458: Add Otel
       authenticationPlugin.plugin.initializeMetrics(
-          solrMetricsContext, Attributes.empty(), "/authentication");
+          solrMetricsContext,
+          Attributes.builder().put(HANDLER_ATTR, "/authentication").build(),
+          "/authentication");
     }
     this.authenticationPlugin = authenticationPlugin;
     try {
@@ -779,14 +782,14 @@ public class CoreContainer {
     shardHandlerFactory =
         ShardHandlerFactory.newInstance(cfg.getShardHandlerFactoryPluginInfo(), loader);
     if (shardHandlerFactory instanceof SolrMetricProducer metricProducer) {
-      // TODO SOLR-17458: Add Otel
+      // NOCOMMIT SOLR-17458: Add Otel
       metricProducer.initializeMetrics(solrMetricsContext, Attributes.empty(), "httpShardHandler");
     }
 
     updateShardHandler = new UpdateShardHandler(cfg.getUpdateShardHandlerConfig());
     solrClientProvider =
         new HttpSolrClientProvider(cfg.getUpdateShardHandlerConfig(), solrMetricsContext);
-    // TODO SOLR-17458: Add Otel
+    // NOCOMMIT SOLR-17458: Add Otel
     updateShardHandler.initializeMetrics(
         solrMetricsContext, Attributes.empty(), "updateShardHandler");
     solrClientCache = new SolrClientCache(solrClientProvider.getSolrClient());
@@ -799,7 +802,7 @@ public class CoreContainer {
       for (Map.Entry<String, CacheConfig> e : cachesConfig.entrySet()) {
         SolrCache<?, ?> c = e.getValue().newInstance();
         String cacheName = e.getKey();
-        // TODO SOLR-17458: Add Otel
+        // NOCOMMIT SOLR-17458: Add Otel
         c.initializeMetrics(solrMetricsContext, Attributes.empty(), "nodeLevelCache/" + cacheName);
         m.put(cacheName, c);
       }
@@ -814,7 +817,7 @@ public class CoreContainer {
     if (isZooKeeperAware()) {
       solrClientCache.setDefaultZKHost(getZkController().getZkServerAddress());
       // initialize ZkClient metrics
-      // TODO SOLR-17458: Add Otel
+      // NOCOMMIT SOLR-17458: Add Otel
       zkSys
           .getZkMetricsProducer()
           .initializeMetrics(solrMetricsContext, Attributes.empty(), "zkClient");
@@ -823,9 +826,11 @@ public class CoreContainer {
               this,
               zkSys.getZkController().getNodeName(),
               (PublicKeyHandler) containerHandlers.get(PublicKeyHandler.PATH));
-      // TODO SOLR-17458: Add Otel
+      // NOCOMMIT SOLR-17458: AuthenticationPlugin.java
       pkiAuthenticationSecurityBuilder.initializeMetrics(
-          solrMetricsContext, Attributes.empty(), "/authentication/pki");
+          solrMetricsContext,
+          Attributes.builder().put(HANDLER_ATTR, "/authentication/pki").build(),
+          "/authentication/pki");
 
       fileStore = new DistribFileStore(this);
       registerV2ApiIfEnabled(ClusterFileStore.class);
@@ -890,11 +895,15 @@ public class CoreContainer {
     metricsHandler = new MetricsHandler(this);
     containerHandlers.put(METRICS_PATH, metricsHandler);
     // TODO SOLR-17458: Add Otel
-    metricsHandler.initializeMetrics(solrMetricsContext, Attributes.empty(), METRICS_PATH);
+    metricsHandler.initializeMetrics(
+        solrMetricsContext,
+        Attributes.builder().put(HANDLER_ATTR, METRICS_PATH).build(),
+        METRICS_PATH);
 
     containerHandlers.put(AUTHZ_PATH, securityConfHandler);
     // TODO SOLR-17458: Add Otel
-    securityConfHandler.initializeMetrics(solrMetricsContext, Attributes.empty(), AUTHZ_PATH);
+    securityConfHandler.initializeMetrics(
+        solrMetricsContext, Attributes.builder().put(HANDLER_ATTR, AUTHZ_PATH).build(), AUTHZ_PATH);
     containerHandlers.put(AUTHC_PATH, securityConfHandler);
 
     PluginInfo[] metricReporters = cfg.getMetricsConfig().getMetricReporters();
@@ -1016,7 +1025,7 @@ public class CoreContainer {
         "version");
 
     SolrFieldCacheBean fieldCacheBean = new SolrFieldCacheBean();
-    // TODO SOLR-17458: Otel migration
+    // NOCOMMIT SOLR-17458: Otel migration
     fieldCacheBean.initializeMetrics(solrMetricsContext, Attributes.empty(), "");
 
     if (isZooKeeperAware()) {
@@ -2344,8 +2353,9 @@ public class CoreContainer {
     }
     if (handler instanceof SolrMetricProducer) {
       ((SolrMetricProducer) handler)
-          // TODO SOLR-17458: Add Otel
-          .initializeMetrics(solrMetricsContext, Attributes.empty(), path);
+          // NOCOMMIT SOLR-17458: Add Otel
+          .initializeMetrics(
+              solrMetricsContext, Attributes.builder().put(HANDLER_ATTR, path).build(), path);
     }
     return handler;
   }
diff --git a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
index 8d51929f7cd..6a70abe9441 100644
--- a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
+++ b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
@@ -19,12 +19,11 @@ package org.apache.solr.handler;
 import static org.apache.solr.core.RequestParams.USEPARAM;
 import static org.apache.solr.response.SolrQueryResponse.haveCompleteResults;
 
-import com.codahale.metrics.Counter;
-import com.codahale.metrics.Meter;
-import com.codahale.metrics.Timer;
 import java.io.IOException;
 import io.opentelemetry.api.common.AttributeKey;
 import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.LongCounter;
+import io.opentelemetry.api.metrics.LongHistogram;
 import java.lang.invoke.MethodHandles;
 import java.util.Collection;
 import java.util.Collections;
@@ -179,7 +178,10 @@ public abstract class RequestHandlerBase
       this.solrMetricsContext = parentContext.getChildContext(this);
     }
 
-    metrics = new HandlerMetrics(solrMetricsContext, attributes, getCategory().toString(), scope);
+    metrics =
+        new HandlerMetrics(
+            solrMetricsContext,
+            attributes.toBuilder().put(CATEGORY_ATTR, getCategory().toString()).build());
 
     // NOCOMMIT: I don't see value in this metric
     solrMetricsContext.gauge(
@@ -197,74 +199,65 @@ public abstract class RequestHandlerBase
                 "NO_OP"),
             Attributes.empty());
 
-    public final Meter numErrors;
-    public final Meter numServerErrors;
-    public final Meter numClientErrors;
-    public final Meter numTimeouts;
-    public final Counter requests;
-    public final Timer requestTimes;
-    public final Counter totalTime;
-
-    public AttributedLongCounter otelRequests;
-    public AttributedLongCounter otelNumServerErrors;
-    public AttributedLongCounter otelNumClientErrors;
-    public AttributedLongCounter otelNumTimeouts;
-    public AttributedLongTimer otelRequestTimes;
-
-    public HandlerMetrics(
-        SolrMetricsContext solrMetricsContext, Attributes attributes, String... metricPath) {
-
-      // NOCOMMIT SOLR-17458: To be removed
-      numErrors = solrMetricsContext.meter("errors", metricPath);
-      numServerErrors = solrMetricsContext.meter("serverErrors", metricPath);
-      numClientErrors = solrMetricsContext.meter("clientErrors", metricPath);
-      numTimeouts = solrMetricsContext.meter("timeouts", metricPath);
-      requests = solrMetricsContext.counter("requests", metricPath);
-      requestTimes = solrMetricsContext.timer("requestTimes", metricPath);
-      totalTime = solrMetricsContext.counter("totalTime", metricPath);
-
-      var baseRequestMetric =
-          solrMetricsContext.longCounter("solr_metrics_core_requests", "HTTP Solr request counts");
-
-      var baseErrorRequestMetric =
-          solrMetricsContext.longCounter(
-              "solr_metrics_core_requests_errors", "HTTP Solr request error counts");
-
-      var baseRequestTimeMetric =
-          solrMetricsContext.longHistogram(
-              "solr_metrics_core_requests_times", "HTTP Solr request times", "ms");
-
-      otelRequests =
-          new AttributedLongCounter(
-              baseRequestMetric, Attributes.builder().putAll(attributes).build());
+    public AttributedLongCounter requests;
+    public AttributedLongCounter numServerErrors;
+    public AttributedLongCounter numClientErrors;
+    public AttributedLongCounter numTimeouts;
+    public AttributedLongTimer requestTimes;
+
+    public HandlerMetrics(SolrMetricsContext solrMetricsContext, Attributes attributes) {
+
+      LongCounter requestMetric;
+      LongCounter errorRequestMetric;
+      LongCounter timeoutRequestMetric;
+      LongHistogram requestTimeMetric;
+
+      if (solrMetricsContext.getRegistryName().equals("solr.node")) {
+        requestMetric =
+            solrMetricsContext.longCounter("solr_node_requests", "Http Solr node requests");
+        errorRequestMetric =
+            solrMetricsContext.longCounter(
+                "solr_node_requests_errors", "HTTP Solr node request errors");
+        timeoutRequestMetric =
+            solrMetricsContext.longCounter(
+                "solr_node_requests_timeout", "HTTP Solr node request timeouts");
+        requestTimeMetric =
+            solrMetricsContext.longHistogram(
+                "solr_node_requests_times", "HTTP Solr node request times", "ms");
+      } else {
+        requestMetric =
+            solrMetricsContext.longCounter("solr_core_requests", "HTTP Solr core requests");
+        errorRequestMetric =
+            solrMetricsContext.longCounter(
+                "solr_core_requests_errors", "HTTP Solr core request errors");
+        timeoutRequestMetric =
+            solrMetricsContext.longCounter(
+                "solr_core_requests_timeout", "HTTP Solr core request timeouts");
+        requestTimeMetric =
+            solrMetricsContext.longHistogram(
+                "solr_core_requests_times", "HTTP Solr core request times", "ms");
+      }
 
-      otelNumServerErrors =
-          new AttributedLongCounter(
-              baseErrorRequestMetric,
-              Attributes.builder()
-                  .putAll(attributes)
-                  .put(AttributeKey.stringKey("source"), "server")
-                  .build());
+      requests = new AttributedLongCounter(requestMetric, attributes);
 
-      otelNumClientErrors =
+      numServerErrors =
           new AttributedLongCounter(
-              baseErrorRequestMetric,
-              Attributes.builder()
-                  .putAll(attributes)
-                  .put(AttributeKey.stringKey("source"), "client")
-                  .build());
+              errorRequestMetric,
+              attributes.toBuilder().put(AttributeKey.stringKey("source"), "server").build());
 
-      otelNumTimeouts =
+      numClientErrors =
           new AttributedLongCounter(
-              baseErrorRequestMetric,
-              Attributes.builder().putAll(attributes).put(TYPE_ATTR, "timeouts").build());
+              errorRequestMetric,
+              attributes.toBuilder().put(AttributeKey.stringKey("source"), "client").build());
+
+      numTimeouts = new AttributedLongCounter(timeoutRequestMetric, attributes);
 
-      otelRequestTimes = new AttributedLongTimer(baseRequestTimeMetric, attributes);
+      requestTimes = new AttributedLongTimer(requestTimeMetric, attributes);
       // NOCOMMIT: Temporary to see metrics
-      otelRequests.add(0L);
-      otelNumTimeouts.add(0L);
-      otelNumClientErrors.add(0L);
-      otelNumServerErrors.add(0L);
+      requests.add(0L);
+      numTimeouts.add(0L);
+      numClientErrors.add(0L);
+      numServerErrors.add(0L);
     }
   }
 
@@ -291,10 +284,8 @@ public abstract class RequestHandlerBase
 
     HandlerMetrics metrics = getMetricsForThisRequest(req);
     metrics.requests.inc();
-    metrics.otelRequests.inc();
 
-    Timer.Context timer = metrics.requestTimes.time();
-    AttributedLongTimer.MetricTimer otelTimer = metrics.otelRequestTimes.start();
+    AttributedLongTimer.MetricTimer timer = metrics.requestTimes.start();
     try {
       TestInjection.injectLeaderTragedy(req.getCore());
       if (pluginInfo != null && pluginInfo.attributes.containsKey(USEPARAM))
@@ -306,8 +297,7 @@ public abstract class RequestHandlerBase
       // count timeouts
 
       if (!haveCompleteResults(rsp.getResponseHeader())) {
-        metrics.numTimeouts.mark();
-        metrics.otelNumTimeouts.inc();
+        metrics.numTimeouts.inc();
         rsp.setHttpCaching(false);
       }
     } catch (QueryLimitsExceededException e) {
@@ -318,9 +308,7 @@ public abstract class RequestHandlerBase
       rsp.setException(normalized);
     } finally {
       try {
-        long elapsed = timer.stop();
-        metrics.totalTime.inc(elapsed);
-        otelTimer.stop();
+        timer.stop();
 
         if (publishCpuTime) {
           Optional<Long> cpuTime = ThreadCpuTimer.readMSandReset(REQUEST_CPU_TIMER_CONTEXT);
@@ -362,15 +350,12 @@ public abstract class RequestHandlerBase
       }
     }
 
-    metrics.numErrors.mark();
     if (isClientError) {
       log.error("Client exception", e);
-      metrics.numClientErrors.mark();
-      metrics.otelNumClientErrors.inc();
+      metrics.numClientErrors.inc();
     } else {
       log.error("Server exception", e);
-      metrics.numServerErrors.mark();
-      metrics.otelNumServerErrors.inc();
+      metrics.numServerErrors.inc();
     }
   }
 
diff --git a/solr/core/src/java/org/apache/solr/handler/component/SearchHandler.java b/solr/core/src/java/org/apache/solr/handler/component/SearchHandler.java
index fdbf1a37f3b..b42e62d187b 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/SearchHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/SearchHandler.java
@@ -92,6 +92,8 @@ import org.slf4j.MDC;
 /** Refer SOLR-281 */
 public class SearchHandler extends RequestHandlerBase
     implements SolrCoreAware, PluginInfoInitialized, PermissionNameProvider {
+
+  public static final AttributeKey<Boolean> INTERNAL_ATTR = AttributeKey.booleanKey("internal");
   static final String INIT_COMPONENTS = "components";
   static final String INIT_FIRST_COMPONENTS = "first-components";
   static final String INIT_LAST_COMPONENTS = "last-components";
@@ -155,28 +157,21 @@ public class SearchHandler extends RequestHandlerBase
     }
   }
 
-  // TODO SOLR-17458: Fix metric Attributes
   @Override
   public void initializeMetrics(
       SolrMetricsContext parentContext, Attributes attributes, String scope) {
     super.initializeMetrics(
         parentContext,
-        Attributes.builder()
-            .putAll(attributes)
-            .put(AttributeKey.stringKey("category"), getCategory().toString())
-            .put(AttributeKey.stringKey("internal"), "false")
-            .build(),
+        Attributes.builder().putAll(attributes).put(INTERNAL_ATTR, false).build(),
         scope);
     metricsShard =
         new HandlerMetrics( // will register various metrics in the context
             solrMetricsContext,
             Attributes.builder()
                 .putAll(attributes)
-                .put(AttributeKey.stringKey("category"), getCategory().toString())
-                .put(AttributeKey.stringKey("internal"), "true")
-                .build(),
-            getCategory().toString(),
-            scope + SHARD_HANDLER_SUFFIX);
+                .put(CATEGORY_ATTR, getCategory().toString())
+                .put(INTERNAL_ATTR, true)
+                .build());
   }
 
   @Override
diff --git a/solr/core/src/java/org/apache/solr/jersey/RequestMetricHandling.java b/solr/core/src/java/org/apache/solr/jersey/RequestMetricHandling.java
index 6cea50d03d2..2ee9c079610 100644
--- a/solr/core/src/java/org/apache/solr/jersey/RequestMetricHandling.java
+++ b/solr/core/src/java/org/apache/solr/jersey/RequestMetricHandling.java
@@ -21,7 +21,6 @@ import static org.apache.solr.jersey.RequestContextKeys.HANDLER_METRICS;
 import static org.apache.solr.jersey.RequestContextKeys.SOLR_QUERY_REQUEST;
 import static org.apache.solr.jersey.RequestContextKeys.TIMER;
 
-import com.codahale.metrics.Timer;
 import jakarta.ws.rs.container.ContainerRequestContext;
 import jakarta.ws.rs.container.ContainerRequestFilter;
 import jakarta.ws.rs.container.ContainerResponseContext;
@@ -90,11 +89,8 @@ public class RequestMetricHandling {
       final RequestHandlerBase.HandlerMetrics metrics =
           handlerBase.getMetricsForThisRequest(solrQueryRequest);
 
-      // TODO SOLR-17458: Recording both Otel and dropwizard for now. Will remove Dropwizard after
-      // removal
       metrics.requests.inc();
-      requestContext.setProperty(TIMER, metrics.otelRequestTimes.start());
-      requestContext.setProperty("dropwizardTimer", metrics.requestTimes.time());
+      requestContext.setProperty(TIMER, metrics.requestTimes.start());
       requestContext.setProperty(HANDLER_METRICS, metrics);
     }
   }
@@ -120,15 +116,11 @@ public class RequestMetricHandling {
           && SolrJerseyResponse.class.isInstance(responseContext.getEntity())) {
         final SolrJerseyResponse response = (SolrJerseyResponse) responseContext.getEntity();
         if (Boolean.TRUE.equals(response.responseHeader.partialResults)) {
-          metrics.numTimeouts.mark();
-          metrics.otelNumTimeouts.inc();
+          metrics.numTimeouts.inc();
         }
       } else {
         log.debug("Skipping partialResults check because entity was not SolrJerseyResponse");
       }
-      final var dropwizardTimer = (Timer.Context) requestContext.getProperty("dropwizardTimer");
-      metrics.totalTime.inc(dropwizardTimer.stop());
-
       final var timer = (AttributedLongTimer.MetricTimer) requestContext.getProperty(TIMER);
       timer.stop();
     }
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
index a01183da9ad..01122a779e0 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
@@ -16,6 +16,8 @@
  */
 package org.apache.solr.metrics;
 
+import static org.apache.solr.metrics.SolrMetricProducer.HANDLER_ATTR;
+
 import com.codahale.metrics.MetricRegistry;
 import io.opentelemetry.api.common.AttributeKey;
 import io.opentelemetry.api.common.Attributes;
@@ -39,7 +41,6 @@ public class SolrCoreMetricManager implements Closeable {
   public static final AttributeKey<String> CORE_ATTR = AttributeKey.stringKey("core");
   public static final AttributeKey<String> SHARD_ATTR = AttributeKey.stringKey("shard");
   public static final AttributeKey<String> REPLICA_ATTR = AttributeKey.stringKey("replica");
-  public static final AttributeKey<String> HANDLER_ATTR = AttributeKey.stringKey("handler");
   public static final AttributeKey<String> SCOPE_ATTR = AttributeKey.stringKey("scope");
 
   private final SolrCore core;
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java
index e2b433108e6..94b740540af 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java
@@ -25,6 +25,7 @@ public interface SolrMetricProducer extends AutoCloseable {
 
   public static final AttributeKey<String> TYPE_ATTR = AttributeKey.stringKey("type");
   public static final AttributeKey<String> CATEGORY_ATTR = AttributeKey.stringKey("category");
+  public static final AttributeKey<String> HANDLER_ATTR = AttributeKey.stringKey("handler");
   public static final AttributeKey<String> OPERATION_ATTR = AttributeKey.stringKey("ops");
 
   /**
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestRandomRequestDistribution.java b/solr/core/src/test/org/apache/solr/cloud/TestRandomRequestDistribution.java
index 9cc1ccace2e..a83da0d2e7b 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestRandomRequestDistribution.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestRandomRequestDistribution.java
@@ -16,7 +16,6 @@
  */
 package org.apache.solr.cloud;
 
-import com.codahale.metrics.Counter;
 import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -42,7 +41,7 @@ import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.embedded.JettySolrRunner;
-import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.metrics.SolrMetricTestUtils;
 import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -84,23 +83,23 @@ public class TestRandomRequestDistribution extends AbstractFullDistribZkTestBase
 
     ZkStateReader.from(cloudClient).forceUpdateCollection("b1x1");
 
-    // get direct access to the metrics counters for each core/replica we're interested to monitor
-    // them
-    final Map<String, Counter> counters = new LinkedHashMap<>();
+    // get direct access to the SolrCore objects for each core/replica we're interested to monitor
+    final Map<String, SolrCore> cores = new LinkedHashMap<>();
     for (JettySolrRunner runner : jettys) {
       CoreContainer container = runner.getCoreContainer();
-      SolrMetricManager metricManager = container.getMetricManager();
       for (SolrCore core : container.getCores()) {
         if ("a1x2".equals(core.getCoreDescriptor().getCollectionName())) {
-          String registry = core.getCoreMetricManager().getRegistryName();
-          Counter cnt = metricManager.counter(null, registry, "requests", "QUERY./select");
-          // sanity check
-          assertEquals(core.getName() + " has already received some requests?", 0, cnt.getCount());
-          counters.put(core.getName(), cnt);
+          cores.put(core.getName(), core);
         }
       }
     }
-    assertEquals("Sanity Check: we know there should be 2 replicas", 2, counters.size());
+    assertEquals("Sanity Check: we know there should be 2 replicas", 2, cores.size());
+
+    // Sanity check - all cores should start with 0 requests
+    for (Map.Entry<String, SolrCore> entry : cores.entrySet()) {
+      double initialCount = getSelectRequestCount(entry.getValue());
+      assertEquals(entry.getKey() + " has already received some requests?", 0L, initialCount, 0.0);
+    }
 
     // send queries to the node that doesn't host any core/replica and see where it routes them
     ClusterState clusterState = cloudClient.getClusterState();
@@ -120,22 +119,23 @@ public class TestRandomRequestDistribution extends AbstractFullDistribZkTestBase
       Set<String> uniqueCoreNames = new LinkedHashSet<>();
 
       log.info("Making requests to {} a1x2", baseUrl);
-      while (uniqueCoreNames.size() < counters.keySet().size() && expectedTotalRequests < 1000L) {
+      while (uniqueCoreNames.size() < cores.size() && expectedTotalRequests < 1000L) {
         expectedTotalRequests++;
         client.query(new SolrQuery("*:*"));
 
-        long actualTotalRequests = 0;
-        for (Map.Entry<String, Counter> e : counters.entrySet()) {
-          final long coreCount = e.getValue().getCount();
+        double actualTotalRequests = 0;
+        for (Map.Entry<String, SolrCore> entry : cores.entrySet()) {
+          final double coreCount = getSelectRequestCount(entry.getValue());
           actualTotalRequests += coreCount;
           if (0 < coreCount) {
-            uniqueCoreNames.add(e.getKey());
+            uniqueCoreNames.add(entry.getKey());
           }
         }
         assertEquals(
             "Sanity Check: Num Queries So Far Doesn't Match Total????",
             expectedTotalRequests,
-            actualTotalRequests);
+            actualTotalRequests,
+            0.0);
       }
       log.info("Total requests: {}", expectedTotalRequests);
       assertEquals(
@@ -144,7 +144,7 @@ public class TestRandomRequestDistribution extends AbstractFullDistribZkTestBase
               + expectedTotalRequests
               + " requests",
           uniqueCoreNames.size(),
-          counters.size());
+          cores.size());
     }
   }
 
@@ -240,10 +240,6 @@ public class TestRandomRequestDistribution extends AbstractFullDistribZkTestBase
       }
       assertNotNull(leaderCore);
 
-      SolrMetricManager leaderMetricManager = leaderCore.getCoreContainer().getMetricManager();
-      String leaderRegistry = leaderCore.getCoreMetricManager().getRegistryName();
-      Counter cnt = leaderMetricManager.counter(null, leaderRegistry, "requests", "QUERY./select");
-
       // All queries should be served by the active replica to make sure that's true we keep
       // querying the down replica. If queries are getting processed by the down replica then the
       // cluster state hasn't updated for that replica locally. So we keep trying till it has
@@ -253,7 +249,7 @@ public class TestRandomRequestDistribution extends AbstractFullDistribZkTestBase
         count++;
         client.query(new SolrQuery("*:*"));
 
-        long c = cnt.getCount();
+        double c = getSelectRequestCount(leaderCore);
 
         if (c == 1) {
           break; // cluster state has got update locally
@@ -274,10 +270,21 @@ public class TestRandomRequestDistribution extends AbstractFullDistribZkTestBase
         client.query(new SolrQuery("*:*"));
         count++;
 
-        long c = cnt.getCount();
+        double c = getSelectRequestCount(leaderCore);
 
-        assertEquals("Query wasn't served by leader", count, c);
+        assertEquals("Query wasn't served by leader", count, c, 0.0);
       }
     }
   }
+
+  private Double getSelectRequestCount(SolrCore core) {
+    var labels =
+        SolrMetricTestUtils.getCloudLabelsBase(core)
+            .label("category", "QUERY")
+            .label("handler", "/select")
+            .label("internal", "false")
+            .build();
+    var datapoint = SolrMetricTestUtils.getCounterDatapoint(core, "solr_core_requests", labels);
+    return datapoint != null ? datapoint.getValue() : 0.0;
+  }
 }
diff --git a/solr/core/src/test/org/apache/solr/core/RequestHandlersTest.java b/solr/core/src/test/org/apache/solr/core/RequestHandlersTest.java
index 7ac9d6a107e..083337dfb97 100644
--- a/solr/core/src/test/org/apache/solr/core/RequestHandlersTest.java
+++ b/solr/core/src/test/org/apache/solr/core/RequestHandlersTest.java
@@ -17,9 +17,9 @@
 package org.apache.solr.core;
 
 import com.codahale.metrics.Gauge;
-import java.util.Map;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.metrics.SolrMetricTestUtils;
 import org.apache.solr.request.SolrRequestHandler;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -114,8 +114,6 @@ public class RequestHandlersTest extends SolrTestCaseJ4 {
   @Test
   public void testStatistics() {
     SolrCore core = h.getCore();
-    SolrRequestHandler updateHandler = core.getRequestHandler("/update");
-    SolrRequestHandler termHandler = core.getRequestHandler("/terms");
 
     assertU(
         adoc(
@@ -125,12 +123,25 @@ public class RequestHandlersTest extends SolrTestCaseJ4 {
             "line up and fly directly at the enemy death cannons, clogging them with wreckage!"));
     assertU(commit());
 
-    Map<String, Object> updateStats = updateHandler.getSolrMetricsContext().getMetricsSnapshot();
-    Map<String, Object> termStats = termHandler.getSolrMetricsContext().getMetricsSnapshot();
-
-    Long updateTime = (Long) updateStats.get("UPDATE./update.totalTime");
-    Long termTime = (Long) termStats.get("QUERY./terms.totalTime");
-
-    assertNotEquals("RequestHandlers should not share statistics!", updateTime, termTime);
+    var updateDp =
+        SolrMetricTestUtils.getHistogramDatapoint(
+            core,
+            "solr_core_requests_times_milliseconds",
+            SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+                .label("category", "UPDATE")
+                .label("handler", "/update")
+                .build());
+    var termDp =
+        SolrMetricTestUtils.getHistogramDatapoint(
+            core,
+            "solr_core_requests_times_milliseconds",
+            SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+                .label("category", "QUERY")
+                .label("handler", "/terms")
+                .build());
+
+    // RequestHandlers should not share statistics
+    assertTrue(updateDp.getSum() > 0);
+    assertNull("/terms should not have any time value", termDp);
   }
 }
diff --git a/solr/core/src/test/org/apache/solr/handler/RequestHandlerBaseTest.java b/solr/core/src/test/org/apache/solr/handler/RequestHandlerBaseTest.java
index 1ea000903cc..74d3d6b2510 100644
--- a/solr/core/src/test/org/apache/solr/handler/RequestHandlerBaseTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/RequestHandlerBaseTest.java
@@ -187,6 +187,7 @@ public class RequestHandlerBaseTest extends SolrTestCaseJ4 {
   private RequestHandlerBase.HandlerMetrics createHandlerMetrics() {
     final SolrMetricsContext metricsContext = mock(SolrMetricsContext.class);
 
+    when(metricsContext.getRegistryName()).thenReturn("solr.core");
     when(metricsContext.timer(any(), any())).thenReturn(mock(Timer.class));
     when(metricsContext.meter(any(), any())).then(invocation -> mock(Meter.class));
     when(metricsContext.counter(any(), any())).thenReturn(mock(Counter.class));
@@ -195,8 +196,6 @@ public class RequestHandlerBaseTest extends SolrTestCaseJ4 {
     when(metricsContext.longHistogram(any(), any())).thenReturn(mockLongHistogram);
 
     return new RequestHandlerBase.HandlerMetrics(
-        metricsContext,
-        Attributes.of(AttributeKey.stringKey("scope"), "someBaseMetricPath"),
-        "someBaseMetricPath");
+        metricsContext, Attributes.of(AttributeKey.stringKey("/handler"), "/someBaseMetricPath"));
   }
 }
diff --git a/solr/core/src/test/org/apache/solr/handler/RequestHandlerMetricsTest.java b/solr/core/src/test/org/apache/solr/handler/RequestHandlerMetricsTest.java
index 96a038f7bc4..7a23fd88094 100644
--- a/solr/core/src/test/org/apache/solr/handler/RequestHandlerMetricsTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/RequestHandlerMetricsTest.java
@@ -54,7 +54,10 @@ public class RequestHandlerMetricsTest extends SolrCloudTestCase {
     System.clearProperty("metricsEnabled");
   }
 
+  // NOCOMMIT: Need to fix aggregateNodeLevelMetricsEnabled for OTEL. Delegate registries currently
+  // do not play nice with OTEL instrumentation
   @Test
+  @BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17458")
   @SuppressWarnings({"unchecked"})
   public void testAggregateNodeLevelMetrics() throws SolrServerException, IOException {
     String collection1 = "testRequestHandlerMetrics1";
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/MBeansHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/admin/MBeansHandlerTest.java
index e7395188bb0..1dab6cf5d42 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/MBeansHandlerTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/MBeansHandlerTest.java
@@ -25,6 +25,7 @@ import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import org.apache.lucene.tests.util.LuceneTestCase;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.util.ContentStream;
@@ -38,6 +39,9 @@ import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+// NOCOMMIT: Test fails because of the /admin/mbeans endpoint. Need to create a bridge or shim after
+// migrating all metrics to support this.
+@LuceneTestCase.BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17458")
 public class MBeansHandlerTest extends SolrTestCaseJ4 {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
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 af27e7d1a6e..0878bf2c8cd 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
@@ -429,7 +429,9 @@ public class MetricsHandlerTest extends SolrTestCaseJ4 {
     handler.close();
   }
 
+  // NOCOMMIT: Have not implemented any kind of filtering for OTEL yet
   @Test
+  @BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17458")
   public void testKeyMetrics() throws Exception {
     MetricsHandler handler = new MetricsHandler(h.getCoreContainer());
 
@@ -577,7 +579,9 @@ public class MetricsHandlerTest extends SolrTestCaseJ4 {
     handler.close();
   }
 
+  // NOCOMMIT: Have not implemented any kind of filtering for OTEL yet
   @Test
+  @BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17458")
   @SuppressWarnings("unchecked")
   public void testExprMetrics() throws Exception {
     MetricsHandler handler = new MetricsHandler(h.getCoreContainer());
diff --git a/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriterCloud.java b/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriterCloud.java
index 0c12b80ce32..003745fe383 100644
--- a/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriterCloud.java
+++ b/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriterCloud.java
@@ -26,7 +26,7 @@ import org.apache.solr.client.solrj.impl.InputStreamResponseParser;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.GenericSolrRequest;
 import org.apache.solr.cloud.SolrCloudTestCase;
-import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.NamedList;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -65,16 +65,13 @@ public class TestPrometheusResponseWriterCloud extends SolrCloudTestCase {
   public void testPrometheusCloudLabels() throws Exception {
     var solrClient = cluster.getSolrClient();
 
-    // Increment solr_metrics_core_requests metric for /select
+    // Increment solr_core_requests metric for /select
     SolrQuery query = new SolrQuery("*:*");
     solrClient.query("collection1", query);
 
     var req =
         new GenericSolrRequest(
-            METHOD.GET,
-            "/admin/metrics",
-            SolrRequestType.ADMIN,
-            new ModifiableSolrParams().set("wt", "prometheus"));
+            METHOD.GET, "/admin/metrics", SolrRequestType.ADMIN, SolrParams.of("wt", "prometheus"));
     req.setResponseParser(new InputStreamResponseParser("prometheus"));
 
     NamedList<Object> resp = solrClient.request(req);
@@ -86,7 +83,7 @@ public class TestPrometheusResponseWriterCloud extends SolrCloudTestCase {
               .lines()
               .anyMatch(
                   line ->
-                      line.startsWith("solr_metrics_core_requests_total")
+                      line.startsWith("solr_core_requests_total")
                           && line.contains("handler=\"/select\"")
                           && line.contains("collection=\"collection1\"")
                           && line.contains("core=\"collection1_shard1_replica_n1\"")
@@ -99,17 +96,14 @@ public class TestPrometheusResponseWriterCloud extends SolrCloudTestCase {
   public void testCollectionDeletePrometheusOutput() throws Exception {
     var solrClient = cluster.getSolrClient();
 
-    // Increment solr_metrics_core_requests metric for /select and assert it exists
+    // Increment solr_core_requests metric for /select and assert it exists
     SolrQuery query = new SolrQuery("*:*");
     solrClient.query("collection1", query);
     solrClient.query("collection2", query);
 
     var req =
         new GenericSolrRequest(
-            METHOD.GET,
-            "/admin/metrics",
-            SolrRequestType.ADMIN,
-            new ModifiableSolrParams().set("wt", "prometheus"));
+            METHOD.GET, "/admin/metrics", SolrRequestType.ADMIN, SolrParams.of("wt", "prometheus"));
     req.setResponseParser(new InputStreamResponseParser("prometheus"));
 
     NamedList<Object> resp = solrClient.request(req);
@@ -118,21 +112,17 @@ public class TestPrometheusResponseWriterCloud extends SolrCloudTestCase {
       String output = new String(in.readAllBytes(), StandardCharsets.UTF_8);
 
       assertTrue(
-          "Prometheus output should contains solr_metrics_core_requests for collection1",
+          "Prometheus output should contains solr_core_requests for collection1",
           output
               .lines()
               .anyMatch(
-                  line ->
-                      line.startsWith("solr_metrics_core_requests")
-                          && line.contains("collection1")));
+                  line -> line.startsWith("solr_core_requests") && line.contains("collection1")));
       assertTrue(
-          "Prometheus output should contains solr_metrics_core_requests for collection2",
+          "Prometheus output should contains solr_core_requests for collection2",
           output
               .lines()
               .anyMatch(
-                  line ->
-                      line.startsWith("solr_metrics_core_requests")
-                          && line.contains("collection2")));
+                  line -> line.startsWith("solr_core_requests") && line.contains("collection2")));
     }
 
     // Delete collection and assert metrics have been removed
@@ -143,21 +133,17 @@ public class TestPrometheusResponseWriterCloud extends SolrCloudTestCase {
     try (InputStream in = (InputStream) resp.get("stream")) {
       String output = new String(in.readAllBytes(), StandardCharsets.UTF_8);
       assertFalse(
-          "Prometheus output should not contain solr_metrics_core_requests after collection was deleted",
+          "Prometheus output should not contain solr_core_requests after collection was deleted",
           output
               .lines()
               .anyMatch(
-                  line ->
-                      line.startsWith("solr_metrics_core_requests")
-                          && line.contains("collection1")));
+                  line -> line.startsWith("solr_core_requests") && line.contains("collection1")));
       assertTrue(
-          "Prometheus output should contains solr_metrics_core_requests for collection2",
+          "Prometheus output should contains solr_core_requests for collection2",
           output
               .lines()
               .anyMatch(
-                  line ->
-                      line.startsWith("solr_metrics_core_requests")
-                          && line.contains("collection2")));
+                  line -> line.startsWith("solr_core_requests") && line.contains("collection2")));
     }
   }
 }
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/SolrJMetricTestUtils.java b/solr/solrj/src/test/org/apache/solr/client/solrj/SolrJMetricTestUtils.java
new file mode 100644
index 00000000000..755be090eac
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/SolrJMetricTestUtils.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.solrj;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import org.apache.solr.client.solrj.impl.CloudSolrClient;
+import org.apache.solr.client.solrj.impl.Http2SolrClient;
+import org.apache.solr.client.solrj.impl.InputStreamResponseParser;
+import org.apache.solr.client.solrj.request.GenericSolrRequest;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.NamedList;
+
+public final class SolrJMetricTestUtils {
+
+  public static double getPrometheusMetricValue(CloudSolrClient solrClient, String metricName)
+      throws SolrServerException, IOException {
+    var req =
+        new GenericSolrRequest(
+            SolrRequest.METHOD.GET,
+            "/admin/metrics",
+            SolrRequest.SolrRequestType.ADMIN,
+            SolrParams.of("wt", "prometheus"));
+    req.setResponseParser(new InputStreamResponseParser("prometheus"));
+
+    NamedList<Object> resp = solrClient.request(req);
+    try (InputStream in = (InputStream) resp.get("stream")) {
+      String output = new String(in.readAllBytes(), StandardCharsets.UTF_8);
+      return output
+          .lines()
+          .filter(l -> l.startsWith(metricName))
+          .mapToDouble(s -> Double.parseDouble(s.substring(s.lastIndexOf(" "))))
+          .sum();
+    }
+  }
+
+  public static Double getNumCoreRequests(
+      String baseUrl, String collectionName, String category, String handler)
+      throws SolrServerException, IOException {
+
+    try (Http2SolrClient client = new Http2SolrClient.Builder(baseUrl).build()) {
+      var req =
+          new GenericSolrRequest(
+              SolrRequest.METHOD.GET,
+              "/admin/metrics",
+              SolrRequest.SolrRequestType.ADMIN,
+              SolrParams.of("wt", "prometheus"));
+      req.setResponseParser(new InputStreamResponseParser("prometheus"));
+
+      NamedList<Object> resp = client.request(req);
+      try (InputStream in = (InputStream) resp.get("stream")) {
+        String output = new String(in.readAllBytes(), StandardCharsets.UTF_8);
+        String metricName;
+
+        metricName = "solr_core_requests_total";
+
+        return output
+            .lines()
+            .filter(
+                l ->
+                    l.contains(metricName)
+                        && l.contains(String.format("category=\"%s\"", category))
+                        && l.contains(String.format("collection=\"%s\"", collectionName))
+                        && l.contains(String.format("handler=\"%s\"", handler)))
+            .mapToDouble(s -> Double.parseDouble(s.substring(s.lastIndexOf(" "))))
+            .sum();
+      }
+    }
+  }
+
+  public static Double getNumNodeRequestErrors(String baseUrl, String category, String handler)
+      throws SolrServerException, IOException {
+
+    try (Http2SolrClient client = new Http2SolrClient.Builder(baseUrl).build()) {
+      var req =
+          new GenericSolrRequest(
+              SolrRequest.METHOD.GET,
+              "/admin/metrics",
+              SolrRequest.SolrRequestType.ADMIN,
+              SolrParams.of("wt", "prometheus"));
+      req.setResponseParser(new InputStreamResponseParser("prometheus"));
+
+      NamedList<Object> resp = client.request(req);
+      try (InputStream in = (InputStream) resp.get("stream")) {
+        String output = new String(in.readAllBytes(), StandardCharsets.UTF_8);
+        String metricName;
+        metricName = "solr_node_requests_errors_total";
+        // Sum both client and server errors
+        return output
+            .lines()
+            .filter(
+                l ->
+                    l.contains(metricName)
+                        && l.contains(String.format("category=\"%s\"", category))
+                        && l.contains(String.format("handler=\"%s\"", handler)))
+            .mapToDouble(s -> Double.parseDouble(s.substring(s.lastIndexOf(" "))))
+            .sum();
+      }
+    }
+  }
+}
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientRetryTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientRetryTest.java
index 39f10b2c324..7b5a9b8a0f5 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientRetryTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientRetryTest.java
@@ -17,17 +17,13 @@
 
 package org.apache.solr.client.solrj.impl;
 
+import static org.apache.solr.client.solrj.SolrJMetricTestUtils.getPrometheusMetricValue;
+
 import java.util.Collections;
 import java.util.Optional;
-import org.apache.solr.client.solrj.SolrRequest.METHOD;
-import org.apache.solr.client.solrj.SolrRequest.SolrRequestType;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.client.solrj.request.GenericSolrRequest;
 import org.apache.solr.cloud.SolrCloudTestCase;
 import org.apache.solr.common.SolrInputDocument;
-import org.apache.solr.common.params.CommonParams;
-import org.apache.solr.common.params.ModifiableSolrParams;
-import org.apache.solr.common.util.NamedList;
 import org.apache.solr.util.TestInjection;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -52,6 +48,8 @@ public class CloudHttp2SolrClientRetryTest extends SolrCloudTestCase {
   @Test
   public void testRetry() throws Exception {
     String collectionName = "testRetry";
+    String prometheusMetric =
+        "solr_core_requests_total{category=\"UPDATE\",collection=\"testRetry\",core=\"testRetry_shard1_replica_n1\",handler=\"/update\",otel_scope_name=\"org.apache.solr\",replica=\"replica_n1\",shard=\"shard1\"}";
     try (CloudSolrClient solrClient =
         new CloudHttp2SolrClient.Builder(
                 Collections.singletonList(cluster.getZkServer().getZkAddress()), Optional.empty())
@@ -60,20 +58,7 @@ public class CloudHttp2SolrClientRetryTest extends SolrCloudTestCase {
 
       solrClient.add(collectionName, new SolrInputDocument("id", "1"));
 
-      ModifiableSolrParams params = new ModifiableSolrParams();
-      String updateRequestCountKey =
-          "solr.core.testRetry.shard1.replica_n1:UPDATE./update.requestTimes:count";
-      params.set("key", updateRequestCountKey);
-      params.set("indent", "true");
-      params.set(CommonParams.WT, "xml");
-
-      var metricsRequest =
-          new GenericSolrRequest(METHOD.GET, "/admin/metrics", SolrRequestType.ADMIN, params);
-
-      NamedList<Object> namedList = solrClient.request(metricsRequest);
-      System.out.println(namedList);
-      NamedList<?> metrics = (NamedList<?>) namedList.get("metrics");
-      assertEquals(1L, metrics.get(updateRequestCountKey));
+      assertEquals(1.0, getPrometheusMetricValue(solrClient, prometheusMetric), 0.0);
 
       TestInjection.failUpdateRequests = "true:100";
       try {
@@ -87,10 +72,7 @@ public class CloudHttp2SolrClientRetryTest extends SolrCloudTestCase {
         TestInjection.reset();
       }
 
-      namedList = solrClient.request(metricsRequest);
-      System.out.println(namedList);
-      metrics = (NamedList<?>) namedList.get("metrics");
-      assertEquals(2L, metrics.get(updateRequestCountKey));
+      assertEquals(2.0, getPrometheusMetricValue(solrClient, prometheusMetric), 0.0);
     }
   }
 }
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientTest.java
index a6d9c13e129..76ce6a9f90e 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientTest.java
@@ -33,15 +33,13 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrJMetricTestUtils;
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.client.solrj.SolrRequest;
-import org.apache.solr.client.solrj.SolrRequest.METHOD;
-import org.apache.solr.client.solrj.SolrRequest.SolrRequestType;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
@@ -402,7 +400,7 @@ public class CloudHttp2SolrClientTest extends SolrCloudTestCase {
     // Track request counts on each node before query calls
     ClusterState clusterState = cluster.getSolrClient().getClusterState();
     DocCollection col = clusterState.getCollection("routing_collection");
-    Map<String, Long> requestCountsMap = new HashMap<>();
+    Map<String, Double> requestCountsMap = new HashMap<>();
     for (Slice slice : col.getSlices()) {
       for (Replica replica : slice.getReplicas()) {
         String baseURL = replica.getBaseUrl();
@@ -463,17 +461,17 @@ public class CloudHttp2SolrClientTest extends SolrCloudTestCase {
 
     // Request counts increase from expected nodes should aggregate to 1000, while there should be
     // no increase in unexpected nodes.
-    long increaseFromExpectedUrls = 0;
-    long increaseFromUnexpectedUrls = 0;
-    Map<String, Long> numRequestsToUnexpectedUrls = new HashMap<>();
+    double increaseFromExpectedUrls = 0.0;
+    double increaseFromUnexpectedUrls = 0.0;
+    Map<String, Double> numRequestsToUnexpectedUrls = new HashMap<>();
     for (Slice slice : col.getSlices()) {
       for (Replica replica : slice.getReplicas()) {
         String baseURL = replica.getBaseUrl();
 
-        Long prevNumRequests = requestCountsMap.get(baseURL);
-        Long curNumRequests = getNumRequests(baseURL, "routing_collection");
+        Double prevNumRequests = requestCountsMap.get(baseURL);
+        Double curNumRequests = getNumRequests(baseURL, "routing_collection");
 
-        long delta = curNumRequests - prevNumRequests;
+        Double delta = curNumRequests - prevNumRequests;
         if (expectedBaseURLs.contains(baseURL)) {
           increaseFromExpectedUrls += delta;
         } else {
@@ -483,11 +481,13 @@ public class CloudHttp2SolrClientTest extends SolrCloudTestCase {
       }
     }
 
-    assertEquals("Unexpected number of requests to expected URLs", n, increaseFromExpectedUrls);
+    assertEquals(
+        "Unexpected number of requests to expected URLs", n, increaseFromExpectedUrls, 0.0);
     assertEquals(
         "Unexpected number of requests to unexpected URLs: " + numRequestsToUnexpectedUrls,
         0,
-        increaseFromUnexpectedUrls);
+        increaseFromUnexpectedUrls,
+        0.0);
   }
 
   /**
@@ -643,57 +643,6 @@ public class CloudHttp2SolrClientTest extends SolrCloudTestCase {
         replicaTypeToReplicas.get(shardAddresses.get(0)).toUpperCase(Locale.ROOT));
   }
 
-  private Long getNumRequests(String baseUrl, String collectionName)
-      throws SolrServerException, IOException {
-    return getNumRequests(baseUrl, collectionName, "QUERY", "/select", null, false);
-  }
-
-  private Long getNumRequests(
-      String baseUrl,
-      String collectionName,
-      String category,
-      String key,
-      String scope,
-      boolean returnNumErrors)
-      throws SolrServerException, IOException {
-
-    NamedList<Object> resp;
-    try (SolrClient client =
-        new HttpSolrClient.Builder(baseUrl)
-            .withDefaultCollection(collectionName)
-            .withConnectionTimeout(15000, TimeUnit.MILLISECONDS)
-            .withSocketTimeout(60000, TimeUnit.MILLISECONDS)
-            .build()) {
-      ModifiableSolrParams params = new ModifiableSolrParams();
-      params.set("stats", "true");
-      params.set("key", key);
-      params.set("cat", category);
-      // use generic request to avoid extra processing of queries
-      var req =
-          new GenericSolrRequest(
-                  SolrRequest.METHOD.GET, "/admin/mbeans", SolrRequestType.ADMIN, params)
-              .setRequiresCollection(true);
-      resp = client.request(req);
-    }
-    String name;
-    if (returnNumErrors) {
-      name = category + "." + (scope != null ? scope : key) + ".errors";
-    } else {
-      name = category + "." + (scope != null ? scope : key) + ".requests";
-    }
-    @SuppressWarnings({"unchecked"})
-    Map<String, Object> map =
-        (Map<String, Object>) resp._get(List.of("solr-mbeans", category, key, "stats"), null);
-    if (map == null) {
-      return null;
-    }
-    if (scope != null) { // admin handler uses a meter instead of counter here
-      return (Long) map.get(name + ".count");
-    } else {
-      return (Long) map.get(name);
-    }
-  }
-
   @Test
   public void testNonRetryableRequests() throws Exception {
 
@@ -720,14 +669,11 @@ public class CloudHttp2SolrClientTest extends SolrCloudTestCase {
         for (String adminPath : adminPathToMbean.keySet()) {
           long errorsBefore = 0;
           for (JettySolrRunner runner : cluster.getJettySolrRunners()) {
-            Long numRequests =
-                getNumRequests(
+            Double numRequests =
+                SolrJMetricTestUtils.getNumNodeRequestErrors(
                     runner.getBaseUrl().toString(),
-                    "foo",
-                    "ADMIN",
-                    adminPathToMbean.get(adminPath),
-                    adminPath,
-                    true);
+                    SolrRequest.SolrRequestType.ADMIN.name(),
+                    adminPath);
             errorsBefore += numRequests;
             if (log.isInfoEnabled()) {
               log.info(
@@ -739,7 +685,8 @@ public class CloudHttp2SolrClientTest extends SolrCloudTestCase {
           params.set("action", "foobar"); // this should cause an error
 
           var request =
-              new GenericSolrRequest(METHOD.GET, adminPath, SolrRequestType.ADMIN, params);
+              new GenericSolrRequest(
+                  SolrRequest.METHOD.GET, adminPath, SolrRequest.SolrRequestType.ADMIN, params);
           try {
             NamedList<Object> resp = client.request(request);
             fail("call to foo for admin path " + adminPath + " should have failed");
@@ -748,14 +695,11 @@ public class CloudHttp2SolrClientTest extends SolrCloudTestCase {
           }
           long errorsAfter = 0;
           for (JettySolrRunner runner : cluster.getJettySolrRunners()) {
-            Long numRequests =
-                getNumRequests(
+            Double numRequests =
+                SolrJMetricTestUtils.getNumNodeRequestErrors(
                     runner.getBaseUrl().toString(),
-                    "foo",
-                    "ADMIN",
-                    adminPathToMbean.get(adminPath),
-                    adminPath,
-                    true);
+                    SolrRequest.SolrRequestType.ADMIN.name(),
+                    adminPath);
             errorsAfter += numRequests;
             if (log.isInfoEnabled()) {
               log.info(
@@ -770,6 +714,11 @@ public class CloudHttp2SolrClientTest extends SolrCloudTestCase {
     }
   }
 
+  private Double getNumRequests(String baseUrl, String collectionName)
+      throws SolrServerException, IOException {
+    return SolrJMetricTestUtils.getNumCoreRequests(baseUrl, collectionName, "QUERY", "/select");
+  }
+
   @Test
   public void checkCollectionParameters() throws Exception {
     try (CloudSolrClient client =
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientRetryTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientRetryTest.java
index 92be2220c3d..1bf8c9135cf 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientRetryTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientRetryTest.java
@@ -17,15 +17,11 @@
 
 package org.apache.solr.client.solrj.impl;
 
-import org.apache.solr.client.solrj.SolrRequest.METHOD;
-import org.apache.solr.client.solrj.SolrRequest.SolrRequestType;
+import static org.apache.solr.client.solrj.SolrJMetricTestUtils.getPrometheusMetricValue;
+
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.client.solrj.request.GenericSolrRequest;
 import org.apache.solr.cloud.SolrCloudTestCase;
 import org.apache.solr.common.SolrInputDocument;
-import org.apache.solr.common.params.CommonParams;
-import org.apache.solr.common.params.ModifiableSolrParams;
-import org.apache.solr.common.util.NamedList;
 import org.apache.solr.util.TestInjection;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -52,24 +48,11 @@ public class CloudSolrClientRetryTest extends SolrCloudTestCase {
     String collectionName = "testRetry";
     CloudSolrClient solrClient = cluster.getSolrClient();
     CollectionAdminRequest.createCollection(collectionName, 1, 1).process(solrClient);
-
+    String prometheusMetric =
+        "solr_core_requests_total{category=\"UPDATE\",collection=\"testRetry\",core=\"testRetry_shard1_replica_n1\",handler=\"/update\",otel_scope_name=\"org.apache.solr\",replica=\"replica_n1\",shard=\"shard1\"}";
     solrClient.add(collectionName, new SolrInputDocument("id", "1"));
 
-    ModifiableSolrParams params = new ModifiableSolrParams();
-    String updateRequestCountKey =
-        "solr.core.testRetry.shard1.replica_n1:UPDATE./update.requestTimes:count";
-    params.set("key", updateRequestCountKey);
-    params.set("indent", "true");
-    params.set(CommonParams.WT, "xml");
-
-    var metricsRequest =
-        new GenericSolrRequest(METHOD.GET, "/admin/metrics", SolrRequestType.ADMIN, params);
-
-    NamedList<Object> namedList = solrClient.request(metricsRequest);
-    System.out.println(namedList);
-    @SuppressWarnings({"rawtypes"})
-    NamedList metrics = (NamedList) namedList.get("metrics");
-    assertEquals(1L, metrics.get(updateRequestCountKey));
+    assertEquals(1.0, getPrometheusMetricValue(solrClient, prometheusMetric), 0.0);
 
     TestInjection.failUpdateRequests = "true:100";
     try {
@@ -83,9 +66,6 @@ public class CloudSolrClientRetryTest extends SolrCloudTestCase {
       TestInjection.reset();
     }
 
-    namedList = solrClient.request(metricsRequest);
-    System.out.println(namedList);
-    metrics = (NamedList) namedList.get("metrics");
-    assertEquals(2L, metrics.get(updateRequestCountKey));
+    assertEquals(2.0, getPrometheusMetricValue(solrClient, prometheusMetric), 0.0);
   }
 }
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientTest.java
index a43da7a8c1d..aa4c8ae5b61 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientTest.java
@@ -33,11 +33,11 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrJMetricTestUtils;
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrRequest.METHOD;
@@ -339,11 +339,11 @@ public class CloudSolrClientTest extends SolrCloudTestCase {
     // Track request counts on each node before query calls
     ClusterState clusterState = cluster.getSolrClient().getClusterState();
     DocCollection col = clusterState.getCollection("routing_collection");
-    Map<String, Long> requestCountsMap = new HashMap<>();
+    Map<String, Double> requestCountsMap = new HashMap<>();
     for (Slice slice : col.getSlices()) {
       for (Replica replica : slice.getReplicas()) {
         String baseURL = replica.getBaseUrl();
-        requestCountsMap.put(baseURL, getNumRequests(baseURL, "routing_collection"));
+        requestCountsMap.put(baseURL, getNumSelectRequests(baseURL));
       }
     }
 
@@ -399,17 +399,17 @@ public class CloudSolrClientTest extends SolrCloudTestCase {
 
     // Request counts increase from expected nodes should aggregate to 1000, while there should be
     // no increase in unexpected nodes.
-    long increaseFromExpectedUrls = 0;
-    long increaseFromUnexpectedUrls = 0;
-    Map<String, Long> numRequestsToUnexpectedUrls = new HashMap<>();
+    Double increaseFromExpectedUrls = 0.0;
+    Double increaseFromUnexpectedUrls = 0.0;
+    Map<String, Double> numRequestsToUnexpectedUrls = new HashMap<>();
     for (Slice slice : col.getSlices()) {
       for (Replica replica : slice.getReplicas()) {
         String baseURL = replica.getBaseUrl();
 
-        Long prevNumRequests = requestCountsMap.get(baseURL);
-        Long curNumRequests = getNumRequests(baseURL, "routing_collection");
+        Double prevNumRequests = requestCountsMap.get(baseURL);
+        Double curNumRequests = getNumSelectRequests(baseURL);
 
-        long delta = curNumRequests - prevNumRequests;
+        double delta = curNumRequests - prevNumRequests;
         if (expectedBaseURLs.contains(baseURL)) {
           increaseFromExpectedUrls += delta;
         } else {
@@ -419,11 +419,13 @@ public class CloudSolrClientTest extends SolrCloudTestCase {
       }
     }
 
-    assertEquals("Unexpected number of requests to expected URLs", n, increaseFromExpectedUrls);
+    assertEquals(
+        "Unexpected number of requests to expected URLs", n, increaseFromExpectedUrls, 0.0);
     assertEquals(
         "Unexpected number of requests to unexpected URLs: " + numRequestsToUnexpectedUrls,
         0,
-        increaseFromUnexpectedUrls);
+        increaseFromUnexpectedUrls,
+        0.0);
   }
 
   /**
@@ -578,55 +580,9 @@ public class CloudSolrClientTest extends SolrCloudTestCase {
         replicaTypeToReplicas.get(shardAddresses.get(0)).toUpperCase(Locale.ROOT));
   }
 
-  private Long getNumRequests(String baseUrl, String collectionName)
-      throws SolrServerException, IOException {
-    return getNumRequests(baseUrl, collectionName, "QUERY", "/select", null, false);
-  }
-
-  private Long getNumRequests(
-      String baseUrl,
-      String collectionName,
-      String category,
-      String key,
-      String scope,
-      boolean returnNumErrors)
-      throws SolrServerException, IOException {
-
-    NamedList<Object> resp;
-    try (SolrClient client =
-        new HttpSolrClient.Builder(baseUrl)
-            .withDefaultCollection(collectionName)
-            .withConnectionTimeout(15000, TimeUnit.MILLISECONDS)
-            .withSocketTimeout(60000, TimeUnit.MILLISECONDS)
-            .build()) {
-      ModifiableSolrParams params = new ModifiableSolrParams();
-      params.set("stats", "true");
-      params.set("key", key);
-      params.set("cat", category);
-      // use generic request to avoid extra processing of queries
-      var req =
-          new GenericSolrRequest(
-                  SolrRequest.METHOD.GET, "/admin/mbeans", SolrRequestType.ADMIN, params)
-              .setRequiresCollection(true);
-      resp = client.request(req);
-    }
-    String name;
-    if (returnNumErrors) {
-      name = category + "." + (scope != null ? scope : key) + ".errors";
-    } else {
-      name = category + "." + (scope != null ? scope : key) + ".requests";
-    }
-    @SuppressWarnings({"unchecked"})
-    Map<String, Object> map =
-        (Map<String, Object>) resp._get(List.of("solr-mbeans", category, key, "stats"), null);
-    if (map == null) {
-      return null;
-    }
-    if (scope != null) { // admin handler uses a meter instead of counter here
-      return (Long) map.get(name + ".count");
-    } else {
-      return (Long) map.get(name);
-    }
+  private Double getNumSelectRequests(String baseUrl) throws SolrServerException, IOException {
+    return SolrJMetricTestUtils.getNumCoreRequests(
+        baseUrl, "routing_collection", "QUERY", "/select");
   }
 
   @Test
@@ -656,14 +612,9 @@ public class CloudSolrClientTest extends SolrCloudTestCase {
         for (String adminPath : adminPathToMbean.keySet()) {
           long errorsBefore = 0;
           for (JettySolrRunner runner : cluster.getJettySolrRunners()) {
-            Long numRequests =
-                getNumRequests(
-                    runner.getBaseUrl().toString(),
-                    collection,
-                    "ADMIN",
-                    adminPathToMbean.get(adminPath),
-                    adminPath,
-                    true);
+            Double numRequests =
+                SolrJMetricTestUtils.getNumNodeRequestErrors(
+                    runner.getBaseUrl().toString(), SolrRequestType.ADMIN.name(), adminPath);
             errorsBefore += numRequests;
             if (log.isInfoEnabled()) {
               log.info(
@@ -684,14 +635,9 @@ public class CloudSolrClientTest extends SolrCloudTestCase {
           }
           long errorsAfter = 0;
           for (JettySolrRunner runner : cluster.getJettySolrRunners()) {
-            Long numRequests =
-                getNumRequests(
-                    runner.getBaseUrl().toString(),
-                    collection,
-                    "ADMIN",
-                    adminPathToMbean.get(adminPath),
-                    adminPath,
-                    true);
+            Double numRequests =
+                SolrJMetricTestUtils.getNumNodeRequestErrors(
+                    runner.getBaseUrl().toString(), SolrRequestType.ADMIN.name(), adminPath);
             errorsAfter += numRequests;
             if (log.isInfoEnabled()) {
               log.info(
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestCoreAdmin.java b/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestCoreAdmin.java
index d0a52e9322b..980a326a32f 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestCoreAdmin.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestCoreAdmin.java
@@ -168,7 +168,9 @@ public class TestCoreAdmin extends AbstractEmbeddedSolrServerTestCase {
     assertEquals(names.size(), cores.getNumAllCores());
   }
 
+  // NOCOMMIT: We have not yet implemented core swapping or renaming for OTEL yet
   @Test
+  @BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17458")
   public void testCoreSwap() throws Exception {
     // index marker docs to core0
     SolrClient cli0 = getSolrCore0();


(solr) 33/33: SOLR-17806: Migrate suggest component and cross-dc metrics to OTEL (#3668)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit bed6205d9b4a23a05d7b6d129686ce8f27c0ac91
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Fri Sep 26 14:19:49 2025 -0400

    SOLR-17806: Migrate suggest component and cross-dc metrics to OTEL (#3668)
    
    * Migrate cross-dc and suggester metrics
    
    * Change based on feedback
---
 .../src/java/org/apache/solr/core/SolrCore.java    |  6 +-
 .../solr/handler/component/SuggestComponent.java   | 35 ++++++-----
 .../apache/solr/metrics/SolrCoreMetricManager.java |  3 +-
 .../org/apache/solr/search/SolrIndexSearcher.java  | 23 +------
 .../apache/solr/cloud/TestBaseStatsCacheCloud.java |  9 ++-
 .../TestPrometheusResponseWriterCloud.java         |  2 +-
 .../update/processor/MirroringUpdateProcessor.java |  2 +-
 .../crossdc/update/processor/ProducerMetrics.java  | 73 ++++++++++++++--------
 .../processor/MirroringUpdateProcessorTest.java    | 28 +++++----
 .../solrj/impl/CloudHttp2SolrClientRetryTest.java  |  2 +-
 .../solrj/impl/CloudSolrClientRetryTest.java       |  2 +-
 .../org/apache/solr/util/SolrMetricTestUtils.java  |  6 +-
 12 files changed, 100 insertions(+), 91 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/core/SolrCore.java b/solr/core/src/java/org/apache/solr/core/SolrCore.java
index ea2b9a08e8f..2d31429eab4 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrCore.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java
@@ -21,7 +21,7 @@ import static org.apache.solr.handler.admin.MetricsHandler.OPEN_METRICS_WT;
 import static org.apache.solr.handler.admin.MetricsHandler.PROMETHEUS_METRICS_WT;
 import static org.apache.solr.metrics.SolrCoreMetricManager.COLLECTION_ATTR;
 import static org.apache.solr.metrics.SolrCoreMetricManager.CORE_ATTR;
-import static org.apache.solr.metrics.SolrCoreMetricManager.REPLICA_ATTR;
+import static org.apache.solr.metrics.SolrCoreMetricManager.REPLICA_TYPE_ATTR;
 import static org.apache.solr.metrics.SolrCoreMetricManager.SHARD_ATTR;
 
 import com.github.benmanes.caffeine.cache.Cache;
@@ -572,8 +572,8 @@ public class SolrCore implements SolrInfoBean, Closeable {
                 .put(CORE_ATTR, getName())
                 .put(SHARD_ATTR, coreDescriptor.getCloudDescriptor().getShardId())
                 .put(
-                    REPLICA_ATTR,
-                    Utils.parseMetricsReplicaName(coreDescriptor.getCollectionName(), getName()))
+                    REPLICA_TYPE_ATTR,
+                    coreDescriptor.getCloudDescriptor().getReplicaType().toString())
                 .build()
             : Attributes.builder().put(CORE_ATTR, getName()).build();
   }
diff --git a/solr/core/src/java/org/apache/solr/handler/component/SuggestComponent.java b/solr/core/src/java/org/apache/solr/handler/component/SuggestComponent.java
index f32934451ac..2702f79e58e 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/SuggestComponent.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/SuggestComponent.java
@@ -43,13 +43,14 @@ import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.params.ShardParams;
 import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrEventListener;
-import org.apache.solr.metrics.MetricsMap;
 import org.apache.solr.metrics.SolrMetricProducer;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.OtelUnit;
 import org.apache.solr.search.QueryLimits;
 import org.apache.solr.search.SolrIndexSearcher;
 import org.apache.solr.spelling.suggest.SolrSuggester;
@@ -95,6 +96,8 @@ public class SuggestComponent extends SearchComponent
    */
   protected Map<String, SolrSuggester> suggesters = new ConcurrentHashMap<>();
 
+  private AutoCloseable toClose;
+
   /** Container for various labels used in the responses generated by this component */
   private static class SuggesterResultLabels {
     static final String SUGGEST = "suggest";
@@ -382,24 +385,20 @@ public class SuggestComponent extends SearchComponent
     return "Suggester component";
   }
 
-  // TODO SOLR-17458: Migrate to OTEL
   @Override
   public void initializeMetrics(
       SolrMetricsContext parentContext, Attributes attributes, String scope) {
     super.initializeMetrics(parentContext, attributes, scope);
-
-    this.solrMetricsContext.gauge(
-        () -> ramBytesUsed(), true, "totalSizeInBytes", getCategory().toString());
-    MetricsMap suggestersMap =
-        new MetricsMap(
-            map -> {
-              for (Map.Entry<String, SolrSuggester> entry : suggesters.entrySet()) {
-                SolrSuggester suggester = entry.getValue();
-                map.putNoEx(entry.getKey(), suggester.toString());
-              }
-            });
-    this.solrMetricsContext.gauge(
-        suggestersMap, true, "suggesters", getCategory().toString(), scope);
+    var suggesterAttributes =
+        attributes.toBuilder().put(CATEGORY_ATTR, getCategory().toString()).build();
+    this.toClose =
+        this.solrMetricsContext.observableLongGauge(
+            "solr_core_suggester_total_size",
+            "Total memory size in bytes of all suggester",
+            (observableLongMeasurement) -> {
+              observableLongMeasurement.record(ramBytesUsed(), suggesterAttributes);
+            },
+            OtelUnit.BYTES);
   }
 
   @Override
@@ -520,6 +519,12 @@ public class SuggestComponent extends SearchComponent
     return result;
   }
 
+  @Override
+  public void close() throws IOException {
+    IOUtils.closeQuietly(toClose);
+    super.close();
+  }
+
   /** Listener to build or reload the maintained {@link SolrSuggester} by this component */
   private static class SuggesterListener implements SolrEventListener {
     private final SolrCore core;
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
index aa64dcbacb1..99dec259ff6 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
@@ -41,7 +41,8 @@ public class SolrCoreMetricManager implements Closeable {
   public static final AttributeKey<String> COLLECTION_ATTR = AttributeKey.stringKey("collection");
   public static final AttributeKey<String> CORE_ATTR = AttributeKey.stringKey("core");
   public static final AttributeKey<String> SHARD_ATTR = AttributeKey.stringKey("shard");
-  public static final AttributeKey<String> REPLICA_ATTR = AttributeKey.stringKey("replica");
+  public static final AttributeKey<String> REPLICA_TYPE_ATTR =
+      AttributeKey.stringKey("replica_type");
 
   private final SolrCore core;
   private SolrMetricsContext solrMetricsContext;
diff --git a/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java b/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
index 51dd2c83930..14e44b4fcc0 100644
--- a/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
+++ b/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
@@ -16,10 +16,6 @@
  */
 package org.apache.solr.search;
 
-import static org.apache.solr.metrics.SolrCoreMetricManager.COLLECTION_ATTR;
-import static org.apache.solr.metrics.SolrCoreMetricManager.CORE_ATTR;
-import static org.apache.solr.metrics.SolrCoreMetricManager.REPLICA_ATTR;
-import static org.apache.solr.metrics.SolrCoreMetricManager.SHARD_ATTR;
 import static org.apache.solr.search.CpuAllowedLimit.TIMING_CONTEXT;
 
 import com.codahale.metrics.Gauge;
@@ -102,7 +98,6 @@ import org.apache.solr.common.util.ExecutorUtil.MDCAwareThreadPoolExecutor;
 import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.common.util.ObjectReleaseTracker;
 import org.apache.solr.common.util.SolrNamedThreadFactory;
-import org.apache.solr.common.util.Utils;
 import org.apache.solr.core.DirectoryFactory;
 import org.apache.solr.core.DirectoryFactory.DirContext;
 import org.apache.solr.core.NodeConfig;
@@ -621,23 +616,7 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
           core.getCoreAttributes().toBuilder().put(NAME_ATTR, cache.name()).build(),
           "solr_searcher_cache");
     }
-    Attributes baseAttributes;
-    if (core.getCoreContainer().isZooKeeperAware()) {
-      baseAttributes =
-          Attributes.builder()
-              .put(COLLECTION_ATTR, core.getCoreDescriptor().getCollectionName())
-              .put(CORE_ATTR, core.getCoreDescriptor().getName())
-              .put(SHARD_ATTR, core.getCoreDescriptor().getCloudDescriptor().getShardId())
-              .put(
-                  REPLICA_ATTR,
-                  Utils.parseMetricsReplicaName(
-                      core.getCoreDescriptor().getCollectionName(), core.getName()))
-              .build();
-    } else {
-      baseAttributes =
-          Attributes.builder().put(CORE_ATTR, core.getCoreDescriptor().getName()).build();
-    }
-    initializeMetrics(solrMetricsContext, baseAttributes, STATISTICS_KEY);
+    initializeMetrics(solrMetricsContext, core.getCoreAttributes(), STATISTICS_KEY);
     registerTime = new Date();
   }
 
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestBaseStatsCacheCloud.java b/solr/core/src/test/org/apache/solr/cloud/TestBaseStatsCacheCloud.java
index 607715f9aa4..a42e4bfb7e0 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestBaseStatsCacheCloud.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestBaseStatsCacheCloud.java
@@ -245,9 +245,12 @@ public abstract class TestBaseStatsCacheCloud extends SolrCloudTestCase {
    * "solr_searcher_stats_cache{...type="lookups",...}" -> "lookups"
    */
   private String extractTypeAttribute(String line) {
-    int typeStart = line.indexOf("type=\"");
-    int typeEnd = line.indexOf('"', typeStart += 6);
-    return line.substring(typeStart, typeEnd);
+    java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("\\btype=\"([^\"]+)\"");
+    java.util.regex.Matcher matcher = pattern.matcher(line);
+    if (matcher.find()) {
+      return matcher.group(1);
+    }
+    throw new IllegalArgumentException("No type attribute found in line: " + line);
   }
 
   /**
diff --git a/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriterCloud.java b/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriterCloud.java
index 003745fe383..47c8d50791f 100644
--- a/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriterCloud.java
+++ b/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriterCloud.java
@@ -87,7 +87,7 @@ public class TestPrometheusResponseWriterCloud extends SolrCloudTestCase {
                           && line.contains("handler=\"/select\"")
                           && line.contains("collection=\"collection1\"")
                           && line.contains("core=\"collection1_shard1_replica_n1\"")
-                          && line.contains("replica=\"replica_n1\"")
+                          && line.contains("replica_type=\"NRT\"")
                           && line.contains("shard=\"shard1\"")));
     }
   }
diff --git a/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/update/processor/MirroringUpdateProcessor.java b/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/update/processor/MirroringUpdateProcessor.java
index 3b391f83b83..346bf19d81b 100644
--- a/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/update/processor/MirroringUpdateProcessor.java
+++ b/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/update/processor/MirroringUpdateProcessor.java
@@ -143,7 +143,7 @@ public class MirroringUpdateProcessor extends UpdateRequestProcessor {
           estimatedDocSizeInBytes,
           maxMirroringDocSizeBytes);
     }
-    producerMetrics.getDocumentSize().update(estimatedDocSizeInBytes);
+    producerMetrics.getDocumentSize().record(estimatedDocSizeInBytes);
     final boolean tooLargeForKafka = estimatedDocSizeInBytes > maxMirroringDocSizeBytes;
     if (tooLargeForKafka && !indexUnmirrorableDocs) {
       throw new SolrException(
diff --git a/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/update/processor/ProducerMetrics.java b/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/update/processor/ProducerMetrics.java
index 1efdb7ded95..65fd30668a8 100644
--- a/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/update/processor/ProducerMetrics.java
+++ b/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/update/processor/ProducerMetrics.java
@@ -16,52 +16,73 @@
  */
 package org.apache.solr.crossdc.update.processor;
 
-import com.codahale.metrics.Counter;
-import com.codahale.metrics.Histogram;
+import static org.apache.solr.metrics.SolrMetricProducer.TYPE_ATTR;
+
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.OtelUnit;
+import org.apache.solr.metrics.otel.instruments.AttributedLongCounter;
+import org.apache.solr.metrics.otel.instruments.AttributedLongHistogram;
 
 /** Metrics presented for each SolrCore using `crossdc.producer.` path. */
 public class ProducerMetrics {
 
-  private final Counter local;
-  private final Counter localError;
-  private final Counter submitted;
-  private final Counter submitError;
-  private final Histogram documentSize;
-  private final Counter documentTooLarge;
+  private final AttributedLongCounter local;
+  private final AttributedLongCounter localError;
+  private final AttributedLongCounter submitted;
+  private final AttributedLongCounter submitError;
+  private final AttributedLongHistogram documentSize;
+  private final AttributedLongCounter documentTooLarge;
 
   public ProducerMetrics(SolrMetricsContext solrMetricsContext, SolrCore solrCore) {
+    var attributes = solrCore.getCoreAttributes();
+
+    var localProcessed =
+        solrMetricsContext.longCounter(
+            "solr_core_crossdc_producer_local_processed",
+            "The number of local documents processed (success or error)");
+    var localSubmitted =
+        solrMetricsContext.longCounter(
+            "solr_core_crossdc_producer_submitted",
+            "The number of documents submitted to the Kafka topic (success or error)");
+    var histogramDocSizes =
+        solrMetricsContext.longHistogram(
+            "solr_core_crossdc_producer_document_size",
+            "Histogram of the processed document sizes processed",
+            OtelUnit.BYTES);
+    var tooLargeErrors =
+        solrMetricsContext.longCounter(
+            "solr_core_crossdc_producer_doc_too_large_errors",
+            "The number of documents that were too large to send to the Kafka topic");
+
     this.local =
-        solrMetricsContext.counter(String.valueOf(solrCore), "local", "crossdc", "producer");
+        new AttributedLongCounter(
+            localProcessed, attributes.toBuilder().put(TYPE_ATTR, "success").build());
     this.localError =
-        solrMetricsContext.counter(
-            String.valueOf(solrCore), "local", "crossdc", "producer", "errors");
+        new AttributedLongCounter(
+            localProcessed, attributes.toBuilder().put(TYPE_ATTR, "error").build());
     this.submitted =
-        solrMetricsContext.counter(String.valueOf(solrCore), "submitted", "crossdc", "producer");
+        new AttributedLongCounter(
+            localSubmitted, attributes.toBuilder().put(TYPE_ATTR, "success").build());
     this.submitError =
-        solrMetricsContext.counter(
-            String.valueOf(solrCore), "submit", "crossdc", "producer", "errors");
-    this.documentSize =
-        solrMetricsContext.histogram(
-            String.valueOf(solrCore), "documentSize", "crossdc", "producer");
-    this.documentTooLarge =
-        solrMetricsContext.counter(
-            String.valueOf(solrCore), "documentTooLarge", "crossdc", "producer", "errors");
+        new AttributedLongCounter(
+            localSubmitted, attributes.toBuilder().put(TYPE_ATTR, "error").build());
+    this.documentSize = new AttributedLongHistogram(histogramDocSizes, attributes);
+    this.documentTooLarge = new AttributedLongCounter(tooLargeErrors, attributes);
   }
 
   /** Counter representing the number of local documents processed successfully. */
-  public Counter getLocal() {
+  public AttributedLongCounter getLocal() {
     return this.local;
   }
 
   /** Counter representing the number of local documents processed with error. */
-  public Counter getLocalError() {
+  public AttributedLongCounter getLocalError() {
     return this.localError;
   }
 
   /** Counter representing the number of documents submitted to the Kafka topic. */
-  public Counter getSubmitted() {
+  public AttributedLongCounter getSubmitted() {
     return this.submitted;
   }
 
@@ -69,19 +90,19 @@ public class ProducerMetrics {
    * Counter representing the number of documents that were not submitted to the Kafka topic because
    * of exception during execution.
    */
-  public Counter getSubmitError() {
+  public AttributedLongCounter getSubmitError() {
     return this.submitError;
   }
 
   /** Histogram of the processed document size. */
-  public Histogram getDocumentSize() {
+  public AttributedLongHistogram getDocumentSize() {
     return this.documentSize;
   }
 
   /**
    * Counter representing the number of documents that were too large to send to the Kafka topic.
    */
-  public Counter getDocumentTooLarge() {
+  public AttributedLongCounter getDocumentTooLarge() {
     return this.documentTooLarge;
   }
 }
diff --git a/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/update/processor/MirroringUpdateProcessorTest.java b/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/update/processor/MirroringUpdateProcessorTest.java
index a24f8728717..1c30f956dbb 100644
--- a/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/update/processor/MirroringUpdateProcessorTest.java
+++ b/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/update/processor/MirroringUpdateProcessorTest.java
@@ -23,8 +23,7 @@ import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import com.codahale.metrics.Counter;
-import com.codahale.metrics.Histogram;
+import io.opentelemetry.api.common.Attributes;
 import java.io.IOException;
 import java.util.Map;
 import org.apache.solr.SolrTestCaseJ4;
@@ -48,6 +47,8 @@ import org.apache.solr.core.CoreDescriptor;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.crossdc.common.CrossDcConf;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.instruments.AttributedLongCounter;
+import org.apache.solr.metrics.otel.instruments.AttributedLongHistogram;
 import org.apache.solr.request.SolrQueryRequestBase;
 import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.update.AddUpdateCommand;
@@ -113,39 +114,42 @@ public class MirroringUpdateProcessorTest extends SolrTestCaseJ4 {
     commitUpdateCommand.openSearcher = true;
     commitUpdateCommand.waitSearcher = true;
 
+    core = mock(SolrCore.class);
+    when(core.getCoreAttributes()).thenReturn(Attributes.empty());
+
     producerMetrics =
         spy(
-            new ProducerMetrics(mock(SolrMetricsContext.class), mock(SolrCore.class)) {
-              private final Counter counterMock = mock(Counter.class);
+            new ProducerMetrics(mock(SolrMetricsContext.class), core) {
+              private final AttributedLongCounter counterMock = mock(AttributedLongCounter.class);
 
               @Override
-              public Counter getLocal() {
+              public AttributedLongCounter getLocal() {
                 return counterMock;
               }
 
               @Override
-              public Counter getLocalError() {
+              public AttributedLongCounter getLocalError() {
                 return counterMock;
               }
 
               @Override
-              public Counter getSubmitted() {
+              public AttributedLongCounter getSubmitted() {
                 return counterMock;
               }
 
               @Override
-              public Counter getDocumentTooLarge() {
+              public AttributedLongCounter getDocumentTooLarge() {
                 return counterMock;
               }
 
               @Override
-              public Counter getSubmitError() {
+              public AttributedLongCounter getSubmitError() {
                 return counterMock;
               }
 
               @Override
-              public Histogram getDocumentSize() {
-                return mock(Histogram.class);
+              public AttributedLongHistogram getDocumentSize() {
+                return mock(AttributedLongHistogram.class);
               }
             });
 
@@ -168,8 +172,6 @@ public class MirroringUpdateProcessorTest extends SolrTestCaseJ4 {
             return requestMock;
           }
         };
-
-    core = mock(SolrCore.class);
     CoreDescriptor coreDesc = mock(CoreDescriptor.class);
     cloudDesc = mock(CloudDescriptor.class);
     when(cloudDesc.getShardId()).thenReturn("shard1");
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientRetryTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientRetryTest.java
index 7b5a9b8a0f5..07d829b2718 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientRetryTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientRetryTest.java
@@ -49,7 +49,7 @@ public class CloudHttp2SolrClientRetryTest extends SolrCloudTestCase {
   public void testRetry() throws Exception {
     String collectionName = "testRetry";
     String prometheusMetric =
-        "solr_core_requests_total{category=\"UPDATE\",collection=\"testRetry\",core=\"testRetry_shard1_replica_n1\",handler=\"/update\",otel_scope_name=\"org.apache.solr\",replica=\"replica_n1\",shard=\"shard1\"}";
+        "solr_core_requests_total{category=\"UPDATE\",collection=\"testRetry\",core=\"testRetry_shard1_replica_n1\",handler=\"/update\",otel_scope_name=\"org.apache.solr\",replica_type=\"NRT\",shard=\"shard1\"}";
     try (CloudSolrClient solrClient =
         new CloudHttp2SolrClient.Builder(
                 Collections.singletonList(cluster.getZkServer().getZkAddress()), Optional.empty())
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientRetryTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientRetryTest.java
index 1bf8c9135cf..19c182485ab 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientRetryTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientRetryTest.java
@@ -49,7 +49,7 @@ public class CloudSolrClientRetryTest extends SolrCloudTestCase {
     CloudSolrClient solrClient = cluster.getSolrClient();
     CollectionAdminRequest.createCollection(collectionName, 1, 1).process(solrClient);
     String prometheusMetric =
-        "solr_core_requests_total{category=\"UPDATE\",collection=\"testRetry\",core=\"testRetry_shard1_replica_n1\",handler=\"/update\",otel_scope_name=\"org.apache.solr\",replica=\"replica_n1\",shard=\"shard1\"}";
+        "solr_core_requests_total{category=\"UPDATE\",collection=\"testRetry\",core=\"testRetry_shard1_replica_n1\",handler=\"/update\",otel_scope_name=\"org.apache.solr\",replica_type=\"NRT\",shard=\"shard1\"}";
     solrClient.add(collectionName, new SolrInputDocument("id", "1"));
 
     assertEquals(1.0, getPrometheusMetricValue(solrClient, prometheusMetric), 0.0);
diff --git a/solr/test-framework/src/java/org/apache/solr/util/SolrMetricTestUtils.java b/solr/test-framework/src/java/org/apache/solr/util/SolrMetricTestUtils.java
index 1fb3b0082cf..6bd14012567 100644
--- a/solr/test-framework/src/java/org/apache/solr/util/SolrMetricTestUtils.java
+++ b/solr/test-framework/src/java/org/apache/solr/util/SolrMetricTestUtils.java
@@ -33,7 +33,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Random;
 import org.apache.lucene.tests.util.TestUtil;
-import org.apache.solr.common.util.Utils;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrInfoBean;
@@ -193,9 +192,8 @@ public final class SolrMetricTestUtils {
         .label("shard", core.getCoreDescriptor().getCloudDescriptor().getShardId())
         .label("core", core.getName())
         .label(
-            "replica",
-            Utils.parseMetricsReplicaName(
-                core.getCoreDescriptor().getCollectionName(), core.getName()))
+            "replica_type",
+            core.getCoreDescriptor().getCloudDescriptor().getReplicaType().toString())
         .label("otel_scope_name", "org.apache.solr");
   }
 


(solr) 25/33: SOLR-17806 Migrate AuthenticationPlugin metrics to OTEL (#3636)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit e9ee7836d52f4c8bd7f400471d41ac239f872576
Author: bjacobowitz <br...@gmail.com>
AuthorDate: Tue Sep 16 15:11:13 2025 -0400

    SOLR-17806 Migrate AuthenticationPlugin metrics to OTEL (#3636)
    
    * SOLR-17806 Migrate AuthenticationPlugin metrics to OTEL
    
    * SOLR-17806 Code review comments
---
 .../otel/instruments/AttributedLongTimer.java      |  16 +-
 .../apache/solr/security/AuthenticationPlugin.java |  83 +++++---
 .../org/apache/solr/security/BasicAuthPlugin.java  |   4 +-
 .../org/apache/solr/security/MultiAuthPlugin.java  |   1 +
 .../solr/security/PKIAuthenticationPlugin.java     |   2 +-
 .../core/src/java/org/apache/solr/util/RTimer.java |  15 +-
 .../solr/security/BasicAuthIntegrationTest.java    |  29 +--
 .../apache/solr/security/CertAuthPluginTest.java   |  53 ++++-
 .../solr/security/TestPKIAuthenticationPlugin.java |  17 ++
 solr/modules/jwt-auth/build.gradle                 |   2 +
 solr/modules/jwt-auth/gradle.lockfile              |   6 +-
 .../apache/solr/security/jwt/JWTAuthPlugin.java    |   6 +-
 .../security/jwt/JWTAuthPluginIntegrationTest.java |  18 ++
 .../apache/solr/cloud/SolrCloudAuthTestCase.java   | 218 ++++++++++++++++-----
 .../org/apache/solr/util/SolrMetricTestUtils.java  |  20 +-
 15 files changed, 393 insertions(+), 97 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedLongTimer.java b/solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedLongTimer.java
index 588a81507db..cda4daa4ba5 100644
--- a/solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedLongTimer.java
+++ b/solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedLongTimer.java
@@ -18,6 +18,7 @@ package org.apache.solr.metrics.otel.instruments;
 
 import io.opentelemetry.api.common.Attributes;
 import io.opentelemetry.api.metrics.LongHistogram;
+import java.util.concurrent.TimeUnit;
 import org.apache.solr.util.RTimer;
 
 /**
@@ -32,16 +33,29 @@ public class AttributedLongTimer extends AttributedLongHistogram {
 
   /**
    * Return a {@link MetricTimer} and starts the timer. When the timer calls {@link
-   * MetricTimer#stop()}, the elapsed time is recorded
+   * MetricTimer#stop()}, the elapsed time is recorded, in milliseconds
    */
   public MetricTimer start() {
     return new MetricTimer(this);
   }
 
+  /**
+   * Return a {@link MetricTimer} and starts the timer. When the timer calls {@link
+   * MetricTimer#stop()}, the elapsed time is recorded, in the specified TimeUnit
+   */
+  public MetricTimer start(TimeUnit unit) {
+    return new MetricTimer(this, unit);
+  }
+
   public static class MetricTimer extends RTimer {
     private final AttributedLongTimer bound;
 
     private MetricTimer(AttributedLongTimer bound) {
+      this(bound, TimeUnit.MILLISECONDS);
+    }
+
+    private MetricTimer(AttributedLongTimer bound, TimeUnit unit) {
+      super(unit);
       this.bound = bound;
     }
 
diff --git a/solr/core/src/java/org/apache/solr/security/AuthenticationPlugin.java b/solr/core/src/java/org/apache/solr/security/AuthenticationPlugin.java
index 8d588aada46..ed7646ad7b4 100644
--- a/solr/core/src/java/org/apache/solr/security/AuthenticationPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/AuthenticationPlugin.java
@@ -16,10 +16,8 @@
  */
 package org.apache.solr.security;
 
-import com.codahale.metrics.Counter;
-import com.codahale.metrics.Meter;
-import com.codahale.metrics.Timer;
 import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.LongCounter;
 import jakarta.servlet.FilterChain;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletRequestWrapper;
@@ -28,8 +26,12 @@ import java.security.Principal;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
 import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.OtelUnit;
+import org.apache.solr.metrics.otel.instruments.AttributedLongCounter;
+import org.apache.solr.metrics.otel.instruments.AttributedLongTimer;
 import org.eclipse.jetty.client.Request;
 
 /**
@@ -44,14 +46,13 @@ public abstract class AuthenticationPlugin implements SolrInfoBean {
   private Set<String> metricNames = ConcurrentHashMap.newKeySet();
   protected SolrMetricsContext solrMetricsContext;
 
-  protected Meter numErrors = new Meter();
-  protected Counter requests = new Counter();
-  protected Timer requestTimes = new Timer();
-  protected Counter totalTime = new Counter();
-  protected Counter numAuthenticated = new Counter();
-  protected Counter numPassThrough = new Counter();
-  protected Counter numWrongCredentials = new Counter();
-  protected Counter numMissingCredentials = new Counter();
+  protected AttributedLongCounter numErrors;
+  protected AttributedLongCounter requests;
+  protected AttributedLongTimer requestTimes;
+  protected AttributedLongCounter numAuthenticated;
+  protected AttributedLongCounter numPassThrough;
+  protected AttributedLongCounter numWrongCredentials;
+  protected AttributedLongCounter numMissingCredentials;
 
   /**
    * This is called upon loading up of a plugin, used for setting it up.
@@ -84,16 +85,16 @@ public abstract class AuthenticationPlugin implements SolrInfoBean {
   public final boolean authenticate(
       HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
       throws Exception {
-    Timer.Context timer = requestTimes.time();
+    AttributedLongTimer.MetricTimer timer = requestTimes.start(TimeUnit.NANOSECONDS);
     requests.inc();
     try {
       return doAuthenticate(request, response, filterChain);
     } catch (Exception e) {
-      numErrors.mark();
+      numErrors.inc();
       throw e;
     } finally {
-      long elapsed = timer.stop();
-      totalTime.inc(elapsed);
+      // Record the timing metric
+      timer.stop();
     }
   }
 
@@ -142,24 +143,58 @@ public abstract class AuthenticationPlugin implements SolrInfoBean {
     return solrMetricsContext;
   }
 
-  // TODO SOLR-17458: Migrate to Otel
   @Override
   public void initializeMetrics(
       SolrMetricsContext parentContext, Attributes attributes, String scope) {
     this.solrMetricsContext = parentContext.getChildContext(this);
+    Attributes attrsWithCategory =
+        Attributes.builder()
+            .putAll(attributes)
+            .put("category", getCategory().toString())
+            .put("plugin_name", this.getClass().getSimpleName())
+            .build();
     // Metrics
-    numErrors = this.solrMetricsContext.meter("errors", getCategory().toString(), scope);
-    requests = this.solrMetricsContext.counter("requests", getCategory().toString(), scope);
+    numErrors =
+        new AttributedLongCounter(
+            this.solrMetricsContext.longCounter(
+                "solr_authentication_errors", "Count of errors during authentication"),
+            attrsWithCategory);
+    requests =
+        new AttributedLongCounter(
+            this.solrMetricsContext.longCounter(
+                "solr_authentication_requests", "Count of requests for authentication"),
+            attrsWithCategory);
     numAuthenticated =
-        this.solrMetricsContext.counter("authenticated", getCategory().toString(), scope);
+        new AttributedLongCounter(
+            this.solrMetricsContext.longCounter(
+                "solr_authentication_num_authenticated",
+                "Count of successful requests for authentication"),
+            attrsWithCategory);
     numPassThrough =
-        this.solrMetricsContext.counter("passThrough", getCategory().toString(), scope);
+        new AttributedLongCounter(
+            this.solrMetricsContext.longCounter(
+                "solr_authentication_num_pass_through",
+                "Count of requests allowed to pass through without authentication credentials (as enabled with configuration \"blockUnknown\": false)"),
+            attrsWithCategory);
+    LongCounter solrAuthenticationPluginFail =
+        this.solrMetricsContext.longCounter(
+            "solr_authentication_failures",
+            "Count of authentication failures (unsuccessful, but processed correctly)");
     numWrongCredentials =
-        this.solrMetricsContext.counter("failWrongCredentials", getCategory().toString(), scope);
+        new AttributedLongCounter(
+            solrAuthenticationPluginFail,
+            attrsWithCategory.toBuilder().put(TYPE_ATTR, "wrong_credentials").build());
     numMissingCredentials =
-        this.solrMetricsContext.counter("failMissingCredentials", getCategory().toString(), scope);
-    requestTimes = this.solrMetricsContext.timer("requestTimes", getCategory().toString(), scope);
-    totalTime = this.solrMetricsContext.counter("totalTime", getCategory().toString(), scope);
+        new AttributedLongCounter(
+            solrAuthenticationPluginFail,
+            attrsWithCategory.toBuilder().put(TYPE_ATTR, "missing_credentials").build());
+    requestTimes =
+        new AttributedLongTimer(
+            this.solrMetricsContext.longHistogram(
+                "solr_authentication_request_times",
+                "Distribution of authentication request durations",
+                OtelUnit.NANOSECONDS),
+            attrsWithCategory);
   }
 
   @Override
diff --git a/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java b/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java
index 753a19ad0d1..be9b4e8deba 100644
--- a/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java
@@ -148,7 +148,7 @@ public class BasicAuthPlugin extends AuthenticationPlugin
                   return true;
                 }
               } else {
-                numErrors.mark();
+                numErrors.inc();
                 authenticationFailure(response, isAjaxRequest, "Invalid authentication token");
                 return false;
               }
@@ -156,7 +156,7 @@ public class BasicAuthPlugin extends AuthenticationPlugin
               throw new Error("Couldn't retrieve authentication", e);
             }
           } else {
-            numErrors.mark();
+            numErrors.inc();
             authenticationFailure(response, isAjaxRequest, "Malformed Basic Auth header");
             return false;
           }
diff --git a/solr/core/src/java/org/apache/solr/security/MultiAuthPlugin.java b/solr/core/src/java/org/apache/solr/security/MultiAuthPlugin.java
index 903218eed56..7c7e5a4b5df 100644
--- a/solr/core/src/java/org/apache/solr/security/MultiAuthPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/MultiAuthPlugin.java
@@ -202,6 +202,7 @@ public class MultiAuthPlugin extends AuthenticationPlugin
       // TODO SOLR-17458: Add Otel
       plugin.initializeMetrics(parentContext, Attributes.empty(), scope);
     }
+    super.initializeMetrics(parentContext, attributes, scope);
   }
 
   private String getSchemeFromAuthHeader(final String authHeader) {
diff --git a/solr/core/src/java/org/apache/solr/security/PKIAuthenticationPlugin.java b/solr/core/src/java/org/apache/solr/security/PKIAuthenticationPlugin.java
index 3a7009682fd..44251bb324d 100644
--- a/solr/core/src/java/org/apache/solr/security/PKIAuthenticationPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/PKIAuthenticationPlugin.java
@@ -224,7 +224,7 @@ public class PKIAuthenticationPlugin extends AuthenticationPlugin
    */
   private boolean sendError(HttpServletResponse response, boolean v2, String message)
       throws IOException {
-    numErrors.mark();
+    numErrors.inc();
     log.error(message);
     response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), v2 ? HEADER_V2 : HEADER);
     response.sendError(HttpServletResponse.SC_UNAUTHORIZED, message);
diff --git a/solr/core/src/java/org/apache/solr/util/RTimer.java b/solr/core/src/java/org/apache/solr/util/RTimer.java
index e4a46bffb71..40934cb2868 100644
--- a/solr/core/src/java/org/apache/solr/util/RTimer.java
+++ b/solr/core/src/java/org/apache/solr/util/RTimer.java
@@ -35,6 +35,7 @@ public class RTimer {
   private TimerImpl timerImpl;
   private double time;
   private double culmTime;
+  private TimeUnit timeUnit;
 
   protected interface TimerImpl {
     void start();
@@ -43,8 +44,13 @@ public class RTimer {
   }
 
   private static class NanoTimeTimerImpl implements TimerImpl {
+    private final TimeUnit timeUnit;
     private long start;
 
+    public NanoTimeTimerImpl(TimeUnit timeUnit) {
+      this.timeUnit = timeUnit;
+    }
+
     @Override
     public void start() {
       start = System.nanoTime();
@@ -52,15 +58,20 @@ public class RTimer {
 
     @Override
     public double elapsed() {
-      return TimeUnit.MILLISECONDS.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS);
+      return timeUnit.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS);
     }
   }
 
   protected TimerImpl newTimerImpl() {
-    return new NanoTimeTimerImpl();
+    return new NanoTimeTimerImpl(timeUnit);
   }
 
   public RTimer() {
+    this(TimeUnit.MILLISECONDS);
+  }
+
+  public RTimer(TimeUnit timeUnit) {
+    this.timeUnit = timeUnit;
     time = 0;
     culmTime = 0;
     timerImpl = newTimerImpl();
diff --git a/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java b/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java
index 4ac0ca904a7..c4035ea0380 100644
--- a/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java
@@ -19,7 +19,6 @@ package org.apache.solr.security;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Collections.singletonMap;
 
-import com.codahale.metrics.MetricRegistry;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.nio.charset.StandardCharsets;
@@ -128,7 +127,7 @@ public class BasicAuthIntegrationTest extends SolrCloudAuthTestCase {
       baseUrl = randomJetty.getBaseUrl().toString();
       verifySecurityStatus(
           cl, baseUrl + authcPrefix, "authentication/class", "solr.BasicAuthPlugin", 20);
-      assertNumberOfMetrics(16); // Basic auth metrics available
+
       assertAuthMetricsMinimums(1, 0, 1, 0, 0, 0);
       assertPkiAuthMetricsMinimums(0, 0, 0, 0, 0, 0);
 
@@ -399,16 +398,22 @@ public class BasicAuthIntegrationTest extends SolrCloudAuthTestCase {
     }
   }
 
-  private void assertNumberOfMetrics(int num) {
-    MetricRegistry registry0 =
-        cluster.getJettySolrRunner(0).getCoreContainer().getMetricManager().registry("solr.node");
-    assertNotNull(registry0);
-
-    assertEquals(
-        num,
-        registry0.getMetrics().entrySet().stream()
-            .filter(e -> e.getKey().startsWith("SECURITY"))
-            .count());
+  private void assertAuthMetricsMinimums(
+      int requests,
+      int authenticated,
+      int passThrough,
+      int failWrongCredentials,
+      int failMissingCredentials,
+      int errors)
+      throws InterruptedException {
+    super.assertAuthMetricsMinimums(
+        BasicAuthPlugin.class,
+        requests,
+        authenticated,
+        passThrough,
+        failWrongCredentials,
+        failMissingCredentials,
+        errors);
   }
 
   private QueryResponse executeQuery(ModifiableSolrParams params, String user, String pass)
diff --git a/solr/core/src/test/org/apache/solr/security/CertAuthPluginTest.java b/solr/core/src/test/org/apache/solr/security/CertAuthPluginTest.java
index e724ad7ab6e..db37c071ec5 100644
--- a/solr/core/src/test/org/apache/solr/security/CertAuthPluginTest.java
+++ b/solr/core/src/test/org/apache/solr/security/CertAuthPluginTest.java
@@ -23,6 +23,9 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import io.opentelemetry.api.common.Attributes;
+import io.prometheus.metrics.model.snapshots.CounterSnapshot;
+import io.prometheus.metrics.model.snapshots.Labels;
 import jakarta.servlet.FilterChain;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
@@ -30,6 +33,11 @@ import java.security.cert.X509Certificate;
 import java.util.Collections;
 import javax.security.auth.x500.X500Principal;
 import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.util.SolrMetricTestUtils;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -46,10 +54,23 @@ public class CertAuthPluginTest extends SolrTestCaseJ4 {
   @Before
   public void setUp() throws Exception {
     super.setUp();
+    CoreContainer coreContainer =
+        SolrTestCaseJ4.createCoreContainer(
+            "collection1", "data", "solrconfig-basic.xml", "schema.xml");
+    String registryName = "solr.core.collection1";
     plugin = new CertAuthPlugin();
+    SolrMetricsContext solrMetricsContext =
+        new SolrMetricsContext(coreContainer.getMetricManager(), registryName, "foo");
+    plugin.initializeMetrics(solrMetricsContext, Attributes.empty(), null);
     plugin.init(Collections.emptyMap());
   }
 
+  @After
+  public void tearDown() throws Exception {
+    deleteCore();
+    super.tearDown();
+  }
+
   @Test
   public void testAuthenticateOk() throws Exception {
     X500Principal principal = new X500Principal("CN=NAME");
@@ -63,7 +84,17 @@ public class CertAuthPluginTest extends SolrTestCaseJ4 {
         (req, rsp) -> assertEquals(principal, ((HttpServletRequest) req).getUserPrincipal());
     assertTrue(plugin.doAuthenticate(request, null, chain));
 
-    assertEquals(1, plugin.numAuthenticated.getCount());
+    Labels labels =
+        Labels.of(
+            "otel_scope_name",
+            "org.apache.solr",
+            "category",
+            "SECURITY",
+            "plugin_name",
+            "CertAuthPlugin");
+    long missingCredentialsCount =
+        getLongMetricValue("solr_authentication_num_authenticated", labels);
+    assertEquals(1L, missingCredentialsCount);
   }
 
   @Test
@@ -76,6 +107,24 @@ public class CertAuthPluginTest extends SolrTestCaseJ4 {
     assertFalse(plugin.doAuthenticate(request, response, null));
     verify(response).sendError(eq(401), anyString());
 
-    assertEquals(1, plugin.numMissingCredentials.getCount());
+    Labels labels =
+        Labels.of(
+            "otel_scope_name",
+            "org.apache.solr",
+            "category",
+            "SECURITY",
+            "type",
+            "missing_credentials",
+            "plugin_name",
+            "CertAuthPlugin");
+    long missingCredentialsCount = getLongMetricValue("solr_authentication_failures", labels);
+    assertEquals(1L, missingCredentialsCount);
+  }
+
+  private long getLongMetricValue(String metricName, Labels labels) {
+    SolrCore core = h.getCore();
+    CounterSnapshot.CounterDataPointSnapshot metric =
+        SolrMetricTestUtils.getCounterDatapoint(core, metricName, labels);
+    return (metric != null) ? (long) metric.getValue() : 0L;
   }
 }
diff --git a/solr/core/src/test/org/apache/solr/security/TestPKIAuthenticationPlugin.java b/solr/core/src/test/org/apache/solr/security/TestPKIAuthenticationPlugin.java
index 699f6249dc3..42e1800274f 100644
--- a/solr/core/src/test/org/apache/solr/security/TestPKIAuthenticationPlugin.java
+++ b/solr/core/src/test/org/apache/solr/security/TestPKIAuthenticationPlugin.java
@@ -23,6 +23,9 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.LongCounter;
+import io.opentelemetry.api.metrics.LongHistogram;
 import jakarta.servlet.FilterChain;
 import jakarta.servlet.ServletRequest;
 import jakarta.servlet.http.HttpServletRequest;
@@ -39,6 +42,7 @@ import org.apache.http.auth.BasicUserPrincipal;
 import org.apache.http.message.BasicHttpRequest;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.metrics.SolrMetricsContext;
 import org.apache.solr.request.LocalSolrQueryRequest;
 import org.apache.solr.request.SolrRequestInfo;
 import org.apache.solr.response.SolrQueryResponse;
@@ -118,9 +122,20 @@ public class TestPKIAuthenticationPlugin extends SolrTestCaseJ4 {
 
     mockReq = createMockRequest(header);
     mock = new MockPKIAuthenticationPlugin(nodeName);
+    mockMetrics(mock);
     request = new BasicHttpRequest("GET", "http://localhost:56565");
   }
 
+  private static void mockMetrics(MockPKIAuthenticationPlugin mock) {
+    SolrMetricsContext smcMock = mock(SolrMetricsContext.class);
+    when(smcMock.getChildContext(any())).thenReturn(smcMock);
+    LongCounter longCounterMock = mock(LongCounter.class);
+    LongHistogram longHistogramMock = mock(LongHistogram.class);
+    when(smcMock.longCounter(any(), any())).thenReturn(longCounterMock);
+    when(smcMock.longHistogram(any(), any(), any())).thenReturn(longHistogramMock);
+    mock.initializeMetrics(smcMock, Attributes.empty(), "");
+  }
+
   @Override
   public void tearDown() throws Exception {
     if (mock != null) {
@@ -165,6 +180,7 @@ public class TestPKIAuthenticationPlugin extends SolrTestCaseJ4 {
             }
           }
         };
+    mockMetrics(mock1);
 
     // Setup regular superuser request
     mock.solrRequestInfo = null;
@@ -191,6 +207,7 @@ public class TestPKIAuthenticationPlugin extends SolrTestCaseJ4 {
     System.setProperty(PKIAuthenticationPlugin.SEND_VERSION, "v1");
     System.setProperty(PKIAuthenticationPlugin.ACCEPT_VERSIONS, "v2");
     mock = new MockPKIAuthenticationPlugin(nodeName);
+    mockMetrics(mock);
 
     principal.set(new BasicUserPrincipal("solr"));
     mock.solrRequestInfo = new SolrRequestInfo(localSolrQueryRequest, new SolrQueryResponse());
diff --git a/solr/modules/jwt-auth/build.gradle b/solr/modules/jwt-auth/build.gradle
index 9f4078f0478..be4ae8f38bc 100644
--- a/solr/modules/jwt-auth/build.gradle
+++ b/solr/modules/jwt-auth/build.gradle
@@ -46,6 +46,8 @@ dependencies {
   testImplementation project(':solr:test-framework')
   testImplementation libs.apache.lucene.testframework
   testImplementation libs.junit.junit
+  testImplementation libs.prometheus.metrics.model
+  testImplementation libs.opentelemetry.exporter.prometheus
 
   testImplementation(libs.mockito.core, {
     exclude group: "net.bytebuddy", module: "byte-buddy-agent"
diff --git a/solr/modules/jwt-auth/gradle.lockfile b/solr/modules/jwt-auth/gradle.lockfile
index 88772eaf17e..bf36af6c0b0 100644
--- a/solr/modules/jwt-auth/gradle.lockfile
+++ b/solr/modules/jwt-auth/gradle.lockfile
@@ -77,10 +77,12 @@ io.opentelemetry:opentelemetry-api:1.53.0=compileClasspath,jarValidation,runtime
 io.opentelemetry:opentelemetry-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-exporter-common:1.50.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.50.0=testCompileClasspath
 io.opentelemetry:opentelemetry-sdk-common:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.50.0=testCompileClasspath
 io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
@@ -90,7 +92,7 @@ io.prometheus:prometheus-metrics-exporter-httpserver:1.3.6=jarValidation,testRun
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=runtimeClasspath,runtimeLibs,solrPlatformLibs
 io.prometheus:prometheus-metrics-exposition-formats:1.3.6=jarValidation,testRuntimeClasspath
 io.prometheus:prometheus-metrics-exposition-textformats:1.3.6=jarValidation,testRuntimeClasspath
-io.prometheus:prometheus-metrics-model:1.1.0=runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-model:1.1.0=runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath
 io.prometheus:prometheus-metrics-model:1.3.6=jarValidation,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.swagger.core.v3:swagger-annotations-jakarta:2.2.22=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTAuthPlugin.java b/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTAuthPlugin.java
index bf7068588db..323e4a6646f 100644
--- a/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTAuthPlugin.java
+++ b/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTAuthPlugin.java
@@ -443,7 +443,7 @@ public class JWTAuthPlugin extends AuthenticationPlugin
       }
       if (jwtConsumer == null) {
         log.warn("JWTAuth not configured");
-        numErrors.mark();
+        numErrors.inc();
         throw new SolrException(
             SolrException.ErrorCode.SERVER_ERROR, "JWTAuth plugin not correctly configured");
       }
@@ -484,7 +484,7 @@ public class JWTAuthPlugin extends AuthenticationPlugin
         final Principal principal = authResponse.getPrincipal();
         request = wrapWithPrincipal(request, principal);
         if (!(principal instanceof JWTPrincipal)) {
-          numErrors.mark();
+          numErrors.inc();
           throw new SolrException(
               SolrException.ErrorCode.SERVER_ERROR,
               "JWTAuth plugin says AUTHENTICATED but no token extracted");
@@ -510,7 +510,7 @@ public class JWTAuthPlugin extends AuthenticationPlugin
             "Authentication failed. {}, {}",
             authResponse.getAuthCode(),
             authResponse.getAuthCode().getMsg());
-        numErrors.mark();
+        numErrors.inc();
         authenticationFailure(
             response,
             authResponse.getAuthCode().getMsg(),
diff --git a/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTAuthPluginIntegrationTest.java b/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTAuthPluginIntegrationTest.java
index 45e35d753fa..f0ca70fe55c 100644
--- a/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTAuthPluginIntegrationTest.java
+++ b/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTAuthPluginIntegrationTest.java
@@ -342,6 +342,24 @@ public class JWTAuthPluginIntegrationTest extends SolrCloudAuthTestCase {
     return "Bearer " + jws.getCompactSerialization();
   }
 
+  private void assertAuthMetricsMinimums(
+      int requests,
+      int authenticated,
+      int passThrough,
+      int failWrongCredentials,
+      int failMissingCredentials,
+      int errors)
+      throws InterruptedException {
+    super.assertAuthMetricsMinimums(
+        JWTAuthPlugin.class,
+        requests,
+        authenticated,
+        passThrough,
+        failWrongCredentials,
+        failMissingCredentials,
+        errors);
+  }
+
   /**
    * Configure solr cluster with a security.json talking to MockOAuth2 server
    *
diff --git a/solr/test-framework/src/java/org/apache/solr/cloud/SolrCloudAuthTestCase.java b/solr/test-framework/src/java/org/apache/solr/cloud/SolrCloudAuthTestCase.java
index 99085837700..c9ed378aa27 100644
--- a/solr/test-framework/src/java/org/apache/solr/cloud/SolrCloudAuthTestCase.java
+++ b/solr/test-framework/src/java/org/apache/solr/cloud/SolrCloudAuthTestCase.java
@@ -24,6 +24,11 @@ import com.codahale.metrics.Meter;
 import com.codahale.metrics.Metric;
 import com.codahale.metrics.MetricRegistry;
 import com.codahale.metrics.Timer;
+import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
+import io.prometheus.metrics.model.snapshots.CounterSnapshot;
+import io.prometheus.metrics.model.snapshots.DataPointSnapshot;
+import io.prometheus.metrics.model.snapshots.HistogramSnapshot;
+import io.prometheus.metrics.model.snapshots.Labels;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
@@ -44,7 +49,10 @@ import org.apache.http.util.EntityUtils;
 import org.apache.solr.client.solrj.impl.HttpClientUtil;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.common.util.Utils;
+import org.apache.solr.core.CoreContainer;
 import org.apache.solr.embedded.JettySolrRunner;
+import org.apache.solr.security.AuthenticationPlugin;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.apache.solr.util.TimeOut;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
@@ -60,32 +68,22 @@ public class SolrCloudAuthTestCase extends SolrCloudTestCase {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
   private static final List<String> AUTH_METRICS_KEYS =
       Arrays.asList(
-          "errors",
-          "requests",
-          "authenticated",
-          "passThrough",
-          "failWrongCredentials",
-          "failMissingCredentials",
-          "requestTimes",
-          "totalTime");
-  private static final List<String> AUTH_METRICS_METER_KEYS = Arrays.asList("errors", "count");
+          "solr_authentication_errors",
+          "solr_authentication_requests",
+          "solr_authentication_num_authenticated",
+          "solr_authentication_num_pass_through",
+          "solr_authentication_failures/wrong_credentials",
+          "solr_authentication_failures/missing_credentials",
+          "solr_authentication_request_times_nanoseconds");
+  private static final List<String> AUTH_METRICS_METER_KEYS =
+      Arrays.asList("solr_authentication_errors", "count");
   private static final List<String> AUTH_METRICS_TIMER_KEYS =
-      Collections.singletonList("requestTimes");
-  private static final String METRICS_PREFIX_PKI = "SECURITY./authentication/pki.";
-  private static final String METRICS_PREFIX = "SECURITY./authentication.";
+      Collections.singletonList("solr_authentication_request_times_nanoseconds");
 
   @SuppressWarnings({"rawtypes"})
   public static final Predicate NOT_NULL_PREDICATE = o -> o != null;
 
   private static final List<String> AUDIT_METRICS_KEYS = Arrays.asList("count");
-  private static final List<String> AUTH_METRICS_TO_COMPARE =
-      Arrays.asList(
-          "requests",
-          "authenticated",
-          "passThrough",
-          "failWrongCredentials",
-          "failMissingCredentials",
-          "errors");
   private static final List<String> AUDIT_METRICS_TO_COMPARE = Arrays.asList("count");
 
   @BeforeClass
@@ -107,8 +105,22 @@ public class SolrCloudAuthTestCase extends SolrCloudTestCase {
       int failMissingCredentials,
       int errors)
       throws InterruptedException {
-    assertAuthMetricsMinimums(
-        METRICS_PREFIX_PKI,
+    String handler = "/authentication/pki";
+    String registryName = "solr.node";
+    Labels labels =
+        Labels.of(
+            "otel_scope_name",
+            "org.apache.solr",
+            "category",
+            "SECURITY",
+            "handler",
+            handler,
+            "plugin_name",
+            org.apache.solr.security.PKIAuthenticationPlugin.class.getSimpleName());
+    assertAuthMetricsMinimumsPrometheus(
+        handler,
+        registryName,
+        labels,
         requests,
         authenticated,
         passThrough,
@@ -124,6 +136,7 @@ public class SolrCloudAuthTestCase extends SolrCloudTestCase {
    * desired params and timeout
    */
   protected void assertAuthMetricsMinimums(
+      Class<? extends AuthenticationPlugin> authPluginClass,
       int requests,
       int authenticated,
       int passThrough,
@@ -131,8 +144,22 @@ public class SolrCloudAuthTestCase extends SolrCloudTestCase {
       int failMissingCredentials,
       int errors)
       throws InterruptedException {
-    assertAuthMetricsMinimums(
-        METRICS_PREFIX,
+    String handler = "/authentication";
+    String registryName = "solr.node";
+    Labels labels =
+        Labels.of(
+            "otel_scope_name",
+            "org.apache.solr",
+            "category",
+            "SECURITY",
+            "handler",
+            handler,
+            "plugin_name",
+            authPluginClass.getSimpleName());
+    assertAuthMetricsMinimumsPrometheus(
+        handler,
+        registryName,
+        labels,
         requests,
         authenticated,
         passThrough,
@@ -170,35 +197,35 @@ public class SolrCloudAuthTestCase extends SolrCloudTestCase {
     return counts;
   }
 
-  /**
-   * Common test method to be able to check auth metrics from any authentication plugin
-   *
-   * @param prefix the metrics key prefix, currently "SECURITY./authentication." for basic auth and
-   *     "SECURITY./authentication/pki." for PKI
-   */
-  private void assertAuthMetricsMinimums(
-      String prefix,
+  /** Common test method to be able to check auth metrics from any authentication plugin */
+  void assertAuthMetricsMinimumsPrometheus(
+      String handler,
+      String registryName,
+      Labels labels,
       int requests,
       int authenticated,
       int passThrough,
       int failWrongCredentials,
       int failMissingCredentials,
-      int errors)
-      throws InterruptedException {
+      int errors) {
     Map<String, Long> expectedCounts = new HashMap<>();
-    expectedCounts.put("requests", (long) requests);
-    expectedCounts.put("authenticated", (long) authenticated);
-    expectedCounts.put("passThrough", (long) passThrough);
-    expectedCounts.put("failWrongCredentials", (long) failWrongCredentials);
-    expectedCounts.put("failMissingCredentials", (long) failMissingCredentials);
-    expectedCounts.put("errors", (long) errors);
+    expectedCounts.put("solr_authentication_requests", (long) requests);
+    expectedCounts.put("solr_authentication_num_authenticated", (long) authenticated);
+    expectedCounts.put("solr_authentication_num_pass_through", (long) passThrough);
+    expectedCounts.put(
+        "solr_authentication_failures/wrong_credentials", (long) failWrongCredentials);
+    expectedCounts.put(
+        "solr_authentication_failures/missing_credentials", (long) failMissingCredentials);
+    expectedCounts.put("solr_authentication_errors", (long) errors);
 
-    final Map<String, Long> counts = countSecurityMetrics(cluster, prefix, AUTH_METRICS_KEYS);
-    final boolean success = isMetricsEqualOrLarger(AUTH_METRICS_TO_COMPARE, expectedCounts, counts);
+    final Map<String, Long> counts =
+        countSecurityMetricsPrometheus(cluster, AUTH_METRICS_KEYS, registryName, labels);
+    final boolean success =
+        expectedCounts.keySet().stream().allMatch(k -> counts.get(k) >= expectedCounts.get(k));
 
     assertTrue(
-        "Expected metric minimums for prefix "
-            + prefix
+        "Expected metric minimums for handler "
+            + handler
             + ": "
             + expectedCounts
             + ", but got: "
@@ -207,10 +234,107 @@ public class SolrCloudAuthTestCase extends SolrCloudTestCase {
             + "security.json; see SOLR-13464 for test work around)",
         success);
 
-    if (counts.get("requests") > 0) {
-      assertTrue("requestTimes count not > 1", counts.get("requestTimes") > 1);
-      assertTrue("totalTime not > 0", counts.get("totalTime") > 0);
+    if (counts.get("solr_authentication_requests") > 0) {
+      assertTrue(
+          "requestTimes count not > 0",
+          counts.get("solr_authentication_request_times_nanoseconds") > 0);
+    }
+  }
+
+  /**
+   * Common test method to sum the prometheus metrics from any authentication plugin from all solr
+   * core containers
+   */
+  Map<String, Long> countSecurityMetricsPrometheus(
+      MiniSolrCloudCluster cluster, List<String> keys, String registryName, Labels labels) {
+    List<Map<String, DataPointSnapshot>> metrics = new ArrayList<>();
+    cluster
+        .getJettySolrRunners()
+        .forEach(
+            r -> {
+              metrics.add(getMetricValues(r.getCoreContainer(), keys, registryName, labels));
+            });
+
+    Map<String, Long> counts = new HashMap<>();
+    keys.forEach(
+        k -> {
+          counts.put(k, sumCountPrometheus(k, metrics));
+        });
+    return counts;
+  }
+
+  private long counterToLong(CounterSnapshot.CounterDataPointSnapshot metric) {
+    if (metric == null) {
+      return 0L;
+    }
+    return (long) metric.getValue();
+  }
+
+  private long histogramToLongCount(HistogramSnapshot.HistogramDataPointSnapshot metric) {
+    if (metric == null) {
+      return 0;
+    }
+    return metric.getCount();
+  }
+
+  // Have to sum the metrics from all three shards/nodes
+  private long sumCountPrometheus(String key, List<Map<String, DataPointSnapshot>> metricsPerNode) {
+    assertTrue("Metric " + key + " does not exist", metricsPerNode.get(0).containsKey(key));
+    if (AUTH_METRICS_METER_KEYS.contains(key)) {
+      return metricsPerNode.stream()
+          .mapToLong(
+              nodeMap -> counterToLong((CounterSnapshot.CounterDataPointSnapshot) nodeMap.get(key)))
+          .sum();
+    } else if (AUTH_METRICS_TIMER_KEYS.contains(key)) {
+      // Sum of the count of timer metrics (NOT the sum of their values)
+      return metricsPerNode.stream()
+          .mapToLong(
+              nodeMap ->
+                  histogramToLongCount(
+                      (HistogramSnapshot.HistogramDataPointSnapshot) nodeMap.get(key)))
+          .sum();
+    } else {
+      return metricsPerNode.stream()
+          .mapToLong(
+              nodeMap -> counterToLong((CounterSnapshot.CounterDataPointSnapshot) nodeMap.get(key)))
+          .sum();
+    }
+  }
+
+  private static Map<String, DataPointSnapshot> getMetricValues(
+      CoreContainer coreContainer, List<String> metricNames, String registryName, Labels labels) {
+    Map<String, DataPointSnapshot> metrics = new HashMap<>();
+    PrometheusMetricReader prometheusMetricReader =
+        SolrMetricTestUtils.getPrometheusMetricReader(coreContainer, registryName);
+    for (String metricName : metricNames) {
+      if ("solr_authentication_request_times_nanoseconds".equals(metricName)) {
+        HistogramSnapshot.HistogramDataPointSnapshot metric =
+            SolrMetricTestUtils.getHistogramDatapoint(prometheusMetricReader, metricName, labels);
+        metrics.put(metricName, metric);
+      } else if ("solr_authentication_failures/wrong_credentials".equals(metricName)) {
+        // Fake metric name, actual metric will be in solr_authentication_failures with label type:
+        // wrong_credentials
+        Labels wrongCredsLabels = Labels.of("type", "wrong_credentials").merge(labels);
+        CounterSnapshot.CounterDataPointSnapshot wrongCredsMetric =
+            SolrMetricTestUtils.getCounterDatapoint(
+                prometheusMetricReader, "solr_authentication_failures", wrongCredsLabels);
+
+        metrics.put("solr_authentication_failures/wrong_credentials", wrongCredsMetric);
+      } else if ("solr_authentication_failures/missing_credentials".equals(metricName)) {
+        // Fake metric name, actual metric will be in solr_authentication_failures with label type:
+        // missing_credentials
+        Labels missingCredsLabels = Labels.of("type", "missing_credentials").merge(labels);
+        CounterSnapshot.CounterDataPointSnapshot missingCredsMetric =
+            SolrMetricTestUtils.getCounterDatapoint(
+                prometheusMetricReader, "solr_authentication_failures", missingCredsLabels);
+        metrics.put("solr_authentication_failures/missing_credentials", missingCredsMetric);
+      } else {
+        CounterSnapshot.CounterDataPointSnapshot metric =
+            SolrMetricTestUtils.getCounterDatapoint(prometheusMetricReader, metricName, labels);
+        metrics.put(metricName, metric);
+      }
     }
+    return metrics;
   }
 
   /**
diff --git a/solr/test-framework/src/java/org/apache/solr/util/SolrMetricTestUtils.java b/solr/test-framework/src/java/org/apache/solr/util/SolrMetricTestUtils.java
index 3de85bf3018..8fcafc125c9 100644
--- a/solr/test-framework/src/java/org/apache/solr/util/SolrMetricTestUtils.java
+++ b/solr/test-framework/src/java/org/apache/solr/util/SolrMetricTestUtils.java
@@ -25,6 +25,7 @@ import io.prometheus.metrics.model.snapshots.DataPointSnapshot;
 import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
 import io.prometheus.metrics.model.snapshots.HistogramSnapshot;
 import io.prometheus.metrics.model.snapshots.Labels;
+import io.prometheus.metrics.model.snapshots.MetricSnapshots;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -169,7 +170,8 @@ public final class SolrMetricTestUtils {
 
   public static DataPointSnapshot getDataPointSnapshot(
       PrometheusMetricReader reader, String metricName, Labels labels) {
-    return reader.collect().stream()
+    MetricSnapshots metricSnapshots = reader.collect();
+    return metricSnapshots.stream()
         .filter(ms -> ms.getMetadata().getPrometheusName().equals(metricName))
         .findFirst()
         .flatMap(
@@ -211,6 +213,11 @@ public final class SolrMetricTestUtils {
   private static <T> T getDatapoint(
       SolrCore core, String metricName, Labels labels, Class<T> snapshotType) {
     var reader = getPrometheusMetricReader(core);
+    return getDataPoint(reader, metricName, labels, snapshotType);
+  }
+
+  private static <T> T getDataPoint(
+      PrometheusMetricReader reader, String metricName, Labels labels, Class<T> snapshotType) {
     return snapshotType.cast(SolrMetricTestUtils.getDataPointSnapshot(reader, metricName, labels));
   }
 
@@ -224,6 +231,17 @@ public final class SolrMetricTestUtils {
     return getDatapoint(core, metricName, labels, CounterSnapshot.CounterDataPointSnapshot.class);
   }
 
+  public static CounterSnapshot.CounterDataPointSnapshot getCounterDatapoint(
+      PrometheusMetricReader reader, String metricName, Labels labels) {
+    return getDataPoint(reader, metricName, labels, CounterSnapshot.CounterDataPointSnapshot.class);
+  }
+
+  public static HistogramSnapshot.HistogramDataPointSnapshot getHistogramDatapoint(
+      PrometheusMetricReader reader, String metricName, Labels labels) {
+    return getDataPoint(
+        reader, metricName, labels, HistogramSnapshot.HistogramDataPointSnapshot.class);
+  }
+
   public static HistogramSnapshot.HistogramDataPointSnapshot getHistogramDatapoint(
       SolrCore core, String metricName, Labels labels) {
     return getDatapoint(


(solr) 28/33: SOLR-17806: Migrate AuditLoggerPlugin to OTEL (#3665)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 1bb61d17e949e51847801d81b3403b9b25a9bb22
Author: Adam Quigley <ad...@gmail.com>
AuthorDate: Thu Sep 25 13:33:21 2025 -0400

    SOLR-17806: Migrate AuditLoggerPlugin to OTEL (#3665)
    
    * SOLR-17806: Migrate AuditLoggerPlugin to OTEL
    
    * Address PR comment on InterruptedException in test
    
    * Fix AuditLoggerPlugin description for solr_auditlogger_request_times metric
    
    * Register Nanosecond Histogram view in SolrMetricManager
    
    * Run gradlew tidy
    
    * Refactor plugin_name as a shared metric category attribute
    
    * Address part of PR feedback
    
    * Improve description for solr_auditlogger_lost OTEL metric
    
    * Add TODO note about making histogram buckets configurable
    
    * Use synchronous gauge for solr_auditlogger_async_enabled metric
    
    * Run gradlew tidy
---
 .../org/apache/solr/metrics/SolrMetricManager.java |  35 +++++-
 .../apache/solr/metrics/SolrMetricProducer.java    |   1 +
 .../apache/solr/security/AuditLoggerPlugin.java    | 126 +++++++++++++--------
 .../apache/solr/security/AuthenticationPlugin.java |   4 +-
 .../solr/security/MultiDestinationAuditLogger.java |   3 +-
 .../solr/security/AuditLoggerIntegrationTest.java  | 126 ++++++++++++++++++---
 .../solr/security/AuditLoggerPluginTest.java       |   4 +
 .../security/MockSolrMetricsContextFactory.java    |  49 ++++++++
 .../security/MultiDestinationAuditLoggerTest.java  |   5 +
 .../security/SolrLogAuditLoggerPluginTest.java     |   4 +
 .../deployment-guide/pages/audit-logging.adoc      |  25 ++--
 .../apache/solr/cloud/SolrCloudAuthTestCase.java   |  40 -------
 12 files changed, 299 insertions(+), 123 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
index e179306eaae..98d18cea32f 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
@@ -52,7 +52,11 @@ import io.opentelemetry.api.metrics.ObservableLongGauge;
 import io.opentelemetry.api.metrics.ObservableLongMeasurement;
 import io.opentelemetry.api.metrics.ObservableLongUpDownCounter;
 import io.opentelemetry.api.metrics.ObservableMeasurement;
+import io.opentelemetry.sdk.metrics.Aggregation;
+import io.opentelemetry.sdk.metrics.InstrumentSelector;
+import io.opentelemetry.sdk.metrics.InstrumentType;
 import io.opentelemetry.sdk.metrics.SdkMeterProvider;
+import io.opentelemetry.sdk.metrics.View;
 import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil;
 import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter;
 import java.io.IOException;
@@ -157,6 +161,23 @@ public class SolrMetricManager {
   private final ConcurrentMap<String, MeterProviderAndReaders> meterProviderAndReaders =
       new ConcurrentHashMap<>();
 
+  private static final List<Double> SOLR_NANOSECOND_HISTOGRAM_BOUNDARIES =
+      List.of(
+          0.0,
+          5_000.0,
+          10_000.0,
+          25_000.0,
+          50_000.0,
+          100_000.0,
+          250_000.0,
+          500_000.0,
+          1_000_000.0,
+          2_500_000.0,
+          5_000_000.0,
+          25_000_000.0,
+          100_000_000.0,
+          1_000_000_000.0);
+
   public SolrMetricManager() {
     metricsConfig = new MetricsConfig.MetricsConfigBuilder().build();
     counterSupplier = MetricSuppliers.counterSupplier(null, null);
@@ -773,7 +794,19 @@ public class SolrMetricManager {
               var reader = new FilterablePrometheusMetricReader(true, null);
               // NOCOMMIT: We need to add a Periodic Metric Reader here if we want to push with OTLP
               // with an exporter
-              var provider = SdkMeterProvider.builder().registerMetricReader(reader);
+              var provider =
+                  SdkMeterProvider.builder()
+                      .registerMetricReader(reader)
+                      .registerView(
+                          InstrumentSelector.builder()
+                              .setType(InstrumentType.HISTOGRAM)
+                              .setUnit(OtelUnit.NANOSECONDS.getSymbol())
+                              .build(),
+                          View.builder()
+                              .setAggregation(
+                                  Aggregation.explicitBucketHistogram(
+                                      SOLR_NANOSECOND_HISTOGRAM_BOUNDARIES))
+                              .build()); // TODO: Make histogram bucket boundaries configurable
               SdkMeterProviderUtil.setExemplarFilter(provider, ExemplarFilter.traceBased());
               return new MeterProviderAndReaders(provider.build(), reader);
             })
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java
index 94b740540af..435b7c0051c 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java
@@ -27,6 +27,7 @@ public interface SolrMetricProducer extends AutoCloseable {
   public static final AttributeKey<String> CATEGORY_ATTR = AttributeKey.stringKey("category");
   public static final AttributeKey<String> HANDLER_ATTR = AttributeKey.stringKey("handler");
   public static final AttributeKey<String> OPERATION_ATTR = AttributeKey.stringKey("ops");
+  public static final AttributeKey<String> PLUGIN_NAME_ATTR = AttributeKey.stringKey("plugin_name");
 
   /**
    * Unique metric tag identifies components with the same life-cycle, which should be registered /
diff --git a/solr/core/src/java/org/apache/solr/security/AuditLoggerPlugin.java b/solr/core/src/java/org/apache/solr/security/AuditLoggerPlugin.java
index 78dfdd4d572..89a6c923f7a 100644
--- a/solr/core/src/java/org/apache/solr/security/AuditLoggerPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/AuditLoggerPlugin.java
@@ -16,9 +16,6 @@
  */
 package org.apache.solr.security;
 
-import com.codahale.metrics.Counter;
-import com.codahale.metrics.Meter;
-import com.codahale.metrics.Timer;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.SerializationFeature;
@@ -33,10 +30,8 @@ import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -46,6 +41,10 @@ import org.apache.solr.common.util.SolrNamedThreadFactory;
 import org.apache.solr.common.util.TimeSource;
 import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.OtelUnit;
+import org.apache.solr.metrics.otel.instruments.AttributedLongCounter;
+import org.apache.solr.metrics.otel.instruments.AttributedLongGauge;
+import org.apache.solr.metrics.otel.instruments.AttributedLongTimer;
 import org.apache.solr.security.AuditEvent.EventType;
 import org.apache.solr.util.TimeOut;
 import org.slf4j.Logger;
@@ -77,18 +76,16 @@ public abstract class AuditLoggerPlugin implements Closeable, Runnable, SolrInfo
   int blockingQueueSize;
 
   protected AuditEventFormatter formatter;
-  private Set<String> metricNames = ConcurrentHashMap.newKeySet();
   private ExecutorService executorService;
   private boolean closed;
   private MuteRules muteRules;
 
   protected SolrMetricsContext solrMetricsContext;
-  protected Meter numErrors = new Meter();
-  protected Meter numLost = new Meter();
-  protected Meter numLogged = new Meter();
-  protected Timer requestTimes = new Timer();
-  protected Timer queuedTime = new Timer();
-  protected Counter totalTime = new Counter();
+  protected AttributedLongCounter numLogged;
+  protected AttributedLongCounter numErrors;
+  protected AttributedLongCounter numLost;
+  protected AttributedLongTimer requestTimes;
+  private AttributedLongTimer queuedTime;
 
   // Event types to be logged by default
   protected List<String> eventTypes =
@@ -163,15 +160,16 @@ public abstract class AuditLoggerPlugin implements Closeable, Runnable, SolrInfo
     if (async) {
       auditAsync(event);
     } else {
-      Timer.Context timer = requestTimes.time();
-      numLogged.mark();
+      AttributedLongTimer.MetricTimer timer = requestTimes.start(TimeUnit.NANOSECONDS);
+      numLogged.inc();
       try {
         audit(event);
       } catch (Exception e) {
-        numErrors.mark();
+        numErrors.inc();
         log.error("Exception when attempting to audit log", e);
       } finally {
-        totalTime.inc(timer.stop());
+        // Record the timing metric
+        timer.stop();
       }
     }
   }
@@ -207,7 +205,7 @@ public abstract class AuditLoggerPlugin implements Closeable, Runnable, SolrInfo
             "Audit log async queue is full (size={}), not blocking since {}==false",
             blockingQueueSize,
             PARAM_BLOCKASYNC);
-        numLost.mark();
+        numLost.inc();
       }
     }
   }
@@ -222,19 +220,21 @@ public abstract class AuditLoggerPlugin implements Closeable, Runnable, SolrInfo
         auditsInFlight.incrementAndGet();
         if (event == null) continue;
         if (event.getDate() != null) {
-          queuedTime.update(
-              new Date().getTime() - event.getDate().getTime(), TimeUnit.MILLISECONDS);
+          long queueTimeNanos =
+              TimeUnit.MILLISECONDS.toNanos(new Date().getTime() - event.getDate().getTime());
+          queuedTime.record(queueTimeNanos);
         }
-        Timer.Context timer = requestTimes.time();
+        AttributedLongTimer.MetricTimer timer = requestTimes.start(TimeUnit.NANOSECONDS);
         audit(event);
-        numLogged.mark();
-        totalTime.inc(timer.stop());
+        numLogged.inc();
+        // Record the timing metric
+        timer.stop();
       } catch (InterruptedException e) {
         log.warn("Interrupted while waiting for next audit log event");
         Thread.currentThread().interrupt();
       } catch (Exception ex) {
         log.error("Exception when attempting to audit log asynchronously", ex);
-        numErrors.mark();
+        numErrors.inc();
       } finally {
         auditsInFlight.decrementAndGet();
       }
@@ -261,39 +261,73 @@ public abstract class AuditLoggerPlugin implements Closeable, Runnable, SolrInfo
     this.formatter = formatter;
   }
 
-  // TODO SOLR-17458: Migrate to Otel
   @Override
   public void initializeMetrics(
       SolrMetricsContext parentContext, Attributes attributes, final String scope) {
     solrMetricsContext = parentContext.getChildContext(this);
     String className = this.getClass().getSimpleName();
     log.debug("Initializing metrics for {}", className);
-    numErrors = solrMetricsContext.meter("errors", getCategory().toString(), scope, className);
-    numLost = solrMetricsContext.meter("lost", getCategory().toString(), scope, className);
-    numLogged = solrMetricsContext.meter("count", getCategory().toString(), scope, className);
+
+    Attributes attrsWithCategory =
+        Attributes.builder()
+            .putAll(attributes)
+            .put(CATEGORY_ATTR, Category.SECURITY.toString())
+            .put(PLUGIN_NAME_ATTR, this.getClass().getSimpleName())
+            .build();
+
+    numLogged =
+        new AttributedLongCounter(
+            solrMetricsContext.longCounter(
+                "solr_auditlogger_count",
+                "The number of audit events that were successfully logged."),
+            attrsWithCategory);
+    numErrors =
+        new AttributedLongCounter(
+            solrMetricsContext.longCounter(
+                "solr_auditlogger_errors", "The number of audit events that resulted in errors."),
+            attrsWithCategory);
+    numLost =
+        new AttributedLongCounter(
+            solrMetricsContext.longCounter(
+                "solr_auditlogger_lost",
+                "The number of audit events that were lost due to async queue being full."),
+            attrsWithCategory);
     requestTimes =
-        solrMetricsContext.timer("requestTimes", getCategory().toString(), scope, className);
-    totalTime = solrMetricsContext.counter("totalTime", getCategory().toString(), scope, className);
+        new AttributedLongTimer(
+            this.solrMetricsContext.longHistogram(
+                "solr_auditlogger_request_times",
+                "Distribution of audit event request durations",
+                OtelUnit.NANOSECONDS),
+            attrsWithCategory);
+
     if (async) {
-      solrMetricsContext.gauge(
-          () -> blockingQueueSize,
-          true,
-          "queueCapacity",
-          getCategory().toString(),
-          scope,
-          className);
-      solrMetricsContext.gauge(
-          () -> blockingQueueSize - queue.remainingCapacity(),
-          true,
-          "queueSize",
-          getCategory().toString(),
-          scope,
-          className);
+      solrMetricsContext.observableLongGauge(
+          "solr_auditlogger_queue",
+          "Metrics around the audit logger queue when running in async mode",
+          (observableLongMeasurement -> {
+            observableLongMeasurement.record(
+                blockingQueueSize,
+                attrsWithCategory.toBuilder().put(TYPE_ATTR, "capacity").build());
+            observableLongMeasurement.record(
+                blockingQueueSize - queue.remainingCapacity(),
+                attrsWithCategory.toBuilder().put(TYPE_ATTR, "size").build());
+          }));
       queuedTime =
-          solrMetricsContext.timer("queuedTime", getCategory().toString(), scope, className);
+          new AttributedLongTimer(
+              solrMetricsContext.longHistogram(
+                  "solr_auditlogger_queued_time",
+                  "Distribution of time events spend queued before processing",
+                  OtelUnit.NANOSECONDS),
+              attrsWithCategory);
     }
-    solrMetricsContext.gauge(
-        () -> async, true, "async", getCategory().toString(), scope, className);
+
+    AttributedLongGauge asyncEnabledGauge =
+        new AttributedLongGauge(
+            solrMetricsContext.longGauge(
+                "solr_auditlogger_async_enabled",
+                "Whether the audit logger is running in async mode (1) or not (0)"),
+            attrsWithCategory);
+    asyncEnabledGauge.set(async ? 1L : 0L);
   }
 
   @Override
diff --git a/solr/core/src/java/org/apache/solr/security/AuthenticationPlugin.java b/solr/core/src/java/org/apache/solr/security/AuthenticationPlugin.java
index ed7646ad7b4..f68c0dc0397 100644
--- a/solr/core/src/java/org/apache/solr/security/AuthenticationPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/AuthenticationPlugin.java
@@ -150,8 +150,8 @@ public abstract class AuthenticationPlugin implements SolrInfoBean {
     Attributes attrsWithCategory =
         Attributes.builder()
             .putAll(attributes)
-            .put("category", getCategory().toString())
-            .put("plugin_name", this.getClass().getSimpleName())
+            .put(CATEGORY_ATTR, getCategory().toString())
+            .put(PLUGIN_NAME_ATTR, this.getClass().getSimpleName())
             .build();
     // Metrics
     numErrors =
diff --git a/solr/core/src/java/org/apache/solr/security/MultiDestinationAuditLogger.java b/solr/core/src/java/org/apache/solr/security/MultiDestinationAuditLogger.java
index d834d3b3c3b..13d3ea48cd2 100644
--- a/solr/core/src/java/org/apache/solr/security/MultiDestinationAuditLogger.java
+++ b/solr/core/src/java/org/apache/solr/security/MultiDestinationAuditLogger.java
@@ -133,8 +133,7 @@ public class MultiDestinationAuditLogger extends AuditLoggerPlugin implements Re
   public void initializeMetrics(
       SolrMetricsContext parentContext, Attributes attributes, String scope) {
     super.initializeMetrics(parentContext, attributes, scope);
-    // TODO SOLR-17458: Add Otel
-    plugins.forEach(p -> p.initializeMetrics(solrMetricsContext, Attributes.empty(), scope));
+    plugins.forEach(p -> p.initializeMetrics(parentContext, Attributes.empty(), scope));
   }
 
   @Override
diff --git a/solr/core/src/test/org/apache/solr/security/AuditLoggerIntegrationTest.java b/solr/core/src/test/org/apache/solr/security/AuditLoggerIntegrationTest.java
index 6f800b34f54..f774abbbd58 100644
--- a/solr/core/src/test/org/apache/solr/security/AuditLoggerIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/security/AuditLoggerIntegrationTest.java
@@ -28,9 +28,13 @@ import static org.apache.solr.security.AuditEvent.RequestType.SEARCH;
 import static org.apache.solr.security.Sha256AuthenticationProvider.getSaltedHashedValue;
 
 import com.codahale.metrics.MetricRegistry;
-import com.codahale.metrics.Timer;
 import com.fasterxml.jackson.databind.DeserializationFeature;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
+import io.prometheus.metrics.model.snapshots.CounterSnapshot;
+import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
+import io.prometheus.metrics.model.snapshots.HistogramSnapshot;
+import io.prometheus.metrics.model.snapshots.Labels;
 import java.io.BufferedReader;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -61,9 +65,11 @@ import org.apache.solr.cloud.SolrCloudAuthTestCase;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.MapSolrParams;
 import org.apache.solr.common.util.SolrNamedThreadFactory;
+import org.apache.solr.core.CoreContainer;
 import org.apache.solr.security.AuditEvent.EventType;
 import org.apache.solr.security.AuditEvent.RequestType;
 import org.apache.solr.security.AuditLoggerPlugin.JSONAuditEventFormatter;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -81,6 +87,8 @@ public class AuditLoggerIntegrationTest extends SolrCloudAuthTestCase {
   // Use a harness per thread to be able to beast this test
   private ThreadLocal<AuditTestHarness> testHarness = new ThreadLocal<>();
 
+  private PrometheusMetricReader metricsReader;
+
   @Override
   @Before
   public void setUp() throws Exception {
@@ -103,8 +111,16 @@ public class AuditLoggerIntegrationTest extends SolrCloudAuthTestCase {
     setupCluster(false, null, false);
     runThreeTestAdminCommands();
     assertThreeTestAdminEvents();
-    assertAuditMetricsMinimums(
-        testHarness.get().cluster, CallbackAuditLoggerPlugin.class.getSimpleName(), 3, 0);
+    assertAuditMetricsMinimums(3, 0);
+    Labels labels = getDefaultAuditLoggerMetricsLabelsBuilder().build();
+
+    assertGaugeMetricValue("solr_auditlogger_async_enabled", labels, 0);
+
+    var snapshot =
+        SolrMetricTestUtils.getHistogramDatapoint(
+            metricsReader, "solr_auditlogger_request_times_nanoseconds", labels);
+    assertNotNull(snapshot);
+    assertTrue("Expected at least 3 measurements", snapshot.getCount() >= 3);
   }
 
   @Test
@@ -112,8 +128,26 @@ public class AuditLoggerIntegrationTest extends SolrCloudAuthTestCase {
     setupCluster(true, null, false);
     runThreeTestAdminCommands();
     assertThreeTestAdminEvents();
-    assertAuditMetricsMinimums(
-        testHarness.get().cluster, CallbackAuditLoggerPlugin.class.getSimpleName(), 3, 0);
+    assertAuditMetricsMinimums(3, 0);
+    Labels labels = getDefaultAuditLoggerMetricsLabelsBuilder().build();
+
+    assertGaugeMetricValue("solr_auditlogger_async_enabled", labels, 1);
+
+    // Verify queue capacity matches default size
+    Labels capacityLabels =
+        getDefaultAuditLoggerMetricsLabelsBuilder().label("type", "capacity").build();
+    assertGaugeMetricValue("solr_auditlogger_queue", capacityLabels, 4096);
+
+    // Verify queue is empty after processing events
+    Labels sizeLabels = getDefaultAuditLoggerMetricsLabelsBuilder().label("type", "size").build();
+    assertGaugeMetricValue("solr_auditlogger_queue", sizeLabels, 0);
+
+    var snapshot =
+        SolrMetricTestUtils.getHistogramDatapoint(
+            metricsReader, "solr_auditlogger_request_times_nanoseconds", labels);
+    assertNotNull(snapshot);
+    assertTrue("Expected at least 3 measurements", snapshot.getCount() >= 3);
+    assertTrue("Expected a positive sum for request times", snapshot.getSum() >= 0);
   }
 
   @Test
@@ -133,20 +167,21 @@ public class AuditLoggerIntegrationTest extends SolrCloudAuthTestCase {
     gate.release(3);
 
     assertThreeTestAdminEvents();
-    assertAuditMetricsMinimums(
-        testHarness.get().cluster, CallbackAuditLoggerPlugin.class.getSimpleName(), 3, 0);
-    ArrayList<MetricRegistry> registries = getMetricsRegistries(testHarness.get().cluster);
-    Timer timer =
-        ((Timer)
-            registries
-                .get(0)
-                .getMetrics()
-                .get("SECURITY./auditlogging.CallbackAuditLoggerPlugin.queuedTime"));
-    double meanTimeOnQueue = timer.getSnapshot().getMean();
-    double meanTimeExpected = (start - end) / 3.0D;
+    assertAuditMetricsMinimums(3, 0);
+
+    Labels labels = getDefaultAuditLoggerMetricsLabelsBuilder().build();
+    HistogramSnapshot.HistogramDataPointSnapshot snapshot =
+        SolrMetricTestUtils.getHistogramDatapoint(
+            metricsReader, "solr_auditlogger_queued_time_nanoseconds", labels);
+    assertNotNull(snapshot);
+    assertTrue("Expected at least 3 measurements", snapshot.getCount() >= 3);
+
+    // Calculate mean from sum and count
+    double mean = snapshot.getSum() / snapshot.getCount();
+    double minExpectedTimeNanos = (end - start) / 3.0;
     assertTrue(
-        "Expecting mean time on queue > " + meanTimeExpected + ", got " + meanTimeOnQueue,
-        meanTimeOnQueue > meanTimeExpected);
+        "Expecting mean time on queue > " + minExpectedTimeNanos + " ns, got " + mean + " ns",
+        mean > minExpectedTimeNanos);
   }
 
   @Test
@@ -542,6 +577,61 @@ public class AuditLoggerIntegrationTest extends SolrCloudAuthTestCase {
 
     myCluster.waitForAllNodes(10);
     testHarness.get().setCluster(myCluster);
+
+    CoreContainer coreContainer = myCluster.getJettySolrRunner(0).getCoreContainer();
+    assertNotNull(coreContainer);
+    metricsReader = SolrMetricTestUtils.getPrometheusMetricReader(coreContainer, "solr.node");
+  }
+
+  private Labels.Builder getDefaultAuditLoggerMetricsLabelsBuilder() {
+    return Labels.builder()
+        .label("category", "SECURITY")
+        .label("plugin_name", "CallbackAuditLoggerPlugin")
+        .label("handler", "/auditlogging")
+        .label("otel_scope_name", "org.apache.solr");
+  }
+
+  private Labels getDefaultAuditLoggerMetricsLabels() {
+    return getDefaultAuditLoggerMetricsLabelsBuilder().build();
+  }
+
+  private void assertGaugeMetricValue(String metricName, Labels labels, long expectedValue) {
+    GaugeSnapshot.GaugeDataPointSnapshot dataPointSnapshot =
+        (GaugeSnapshot.GaugeDataPointSnapshot)
+            SolrMetricTestUtils.getDataPointSnapshot(metricsReader, metricName, labels);
+    assertNotNull(dataPointSnapshot);
+    assertEquals(expectedValue, dataPointSnapshot.getValue(), 0.0);
+  }
+
+  private void assertAuditMetricsMinimums(int count, int errors) throws InterruptedException {
+    Labels labels = getDefaultAuditLoggerMetricsLabels();
+    assertCounterMinimumWithRetry("solr_auditlogger_count", labels, count);
+
+    if (errors > 0) {
+      assertCounterMinimumWithRetry("solr_auditlogger_errors", labels, errors);
+    }
+  }
+
+  private void assertCounterMinimumWithRetry(String metricName, Labels labels, int expectedMinimum)
+      throws InterruptedException {
+    boolean success = checkCounterMinimum(metricName, labels, expectedMinimum);
+
+    if (!success && expectedMinimum > 0) {
+      log.info("First {} metric check failed, pausing 2s before re-attempt", metricName);
+      Thread.sleep(2000);
+      success = checkCounterMinimum(metricName, labels, expectedMinimum);
+    }
+
+    assertTrue(
+        String.format(
+            "Counter metric '%s' did not meet minimum %d after retry", metricName, expectedMinimum),
+        success);
+  }
+
+  private boolean checkCounterMinimum(String metricName, Labels labels, int expectedMinimum) {
+    CounterSnapshot.CounterDataPointSnapshot snapshot =
+        SolrMetricTestUtils.getCounterDatapoint(metricsReader, metricName, labels);
+    return snapshot != null && snapshot.getValue() >= expectedMinimum;
   }
 
   /**
diff --git a/solr/core/src/test/org/apache/solr/security/AuditLoggerPluginTest.java b/solr/core/src/test/org/apache/solr/security/AuditLoggerPluginTest.java
index 9b82e6d0585..1de35430800 100644
--- a/solr/core/src/test/org/apache/solr/security/AuditLoggerPluginTest.java
+++ b/solr/core/src/test/org/apache/solr/security/AuditLoggerPluginTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.solr.security;
 
+import io.opentelemetry.api.common.Attributes;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -26,6 +27,7 @@ import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.SolrException;
+import org.apache.solr.metrics.SolrMetricsContext;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -133,6 +135,8 @@ public class AuditLoggerPluginTest extends SolrTestCaseJ4 {
     config = new HashMap<>();
     config.put("async", false);
     plugin.init(config);
+    SolrMetricsContext mockSolrMetricsContext = MockSolrMetricsContextFactory.create();
+    plugin.initializeMetrics(mockSolrMetricsContext, Attributes.empty(), "solr.test");
   }
 
   @Override
diff --git a/solr/core/src/test/org/apache/solr/security/MockSolrMetricsContextFactory.java b/solr/core/src/test/org/apache/solr/security/MockSolrMetricsContextFactory.java
new file mode 100644
index 00000000000..679e14af67d
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/security/MockSolrMetricsContextFactory.java
@@ -0,0 +1,49 @@
+package org.apache.solr.security;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Timer;
+import io.opentelemetry.api.metrics.LongCounter;
+import io.opentelemetry.api.metrics.LongGauge;
+import io.opentelemetry.api.metrics.LongHistogram;
+import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.OtelUnit;
+
+public final class MockSolrMetricsContextFactory {
+
+  public static SolrMetricsContext create() {
+    SolrMetricsContext mockParentContext = mock(SolrMetricsContext.class);
+    SolrMetricsContext mockChildContext = mock(SolrMetricsContext.class);
+
+    when(mockParentContext.getChildContext(any())).thenReturn(mockChildContext);
+
+    LongCounter mockOtelLongCounter = mock(LongCounter.class);
+    when(mockChildContext.longCounter(anyString(), any())).thenReturn(mockOtelLongCounter);
+
+    Timer mockTimer = mock(Timer.class);
+    Timer.Context mockTimerContext = mock(Timer.Context.class);
+    when(mockTimer.time()).thenReturn(mockTimerContext);
+    when(mockChildContext.timer(anyString(), anyString(), anyString(), anyString()))
+        .thenReturn(mockTimer);
+
+    Counter mockCounter = mock(Counter.class);
+    when(mockChildContext.counter(anyString(), anyString(), anyString(), anyString()))
+        .thenReturn(mockCounter);
+
+    LongHistogram mockLongHistogram = mock(LongHistogram.class);
+    when(mockChildContext.longHistogram(anyString(), anyString(), any(OtelUnit.class)))
+        .thenReturn(mockLongHistogram);
+
+    when(mockChildContext.observableLongGauge(anyString(), anyString(), any())).thenReturn(null);
+    when(mockChildContext.observableLongCounter(anyString(), anyString(), any())).thenReturn(null);
+
+    LongGauge mockLongGauge = mock(LongGauge.class);
+    when(mockChildContext.longGauge(anyString(), anyString())).thenReturn(mockLongGauge);
+
+    return mockParentContext;
+  }
+}
diff --git a/solr/core/src/test/org/apache/solr/security/MultiDestinationAuditLoggerTest.java b/solr/core/src/test/org/apache/solr/security/MultiDestinationAuditLoggerTest.java
index 3b1ba2c4827..fe6c30070b0 100644
--- a/solr/core/src/test/org/apache/solr/security/MultiDestinationAuditLoggerTest.java
+++ b/solr/core/src/test/org/apache/solr/security/MultiDestinationAuditLoggerTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr.security;
 
+import io.opentelemetry.api.common.Attributes;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -24,6 +25,7 @@ import java.util.HashMap;
 import java.util.Map;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.core.SolrResourceLoader;
+import org.apache.solr.metrics.SolrMetricsContext;
 import org.junit.Test;
 
 public class MultiDestinationAuditLoggerTest extends SolrTestCaseJ4 {
@@ -52,6 +54,9 @@ public class MultiDestinationAuditLoggerTest extends SolrTestCaseJ4 {
     al.inform(loader);
     al.init(config);
 
+    SolrMetricsContext mockSolrMetricsContext = MockSolrMetricsContextFactory.create();
+    al.initializeMetrics(mockSolrMetricsContext, Attributes.empty(), "solr.test");
+
     al.doAudit(new AuditEvent(AuditEvent.EventType.ANONYMOUS).setUsername("me"));
     assertEquals(
         0,
diff --git a/solr/core/src/test/org/apache/solr/security/SolrLogAuditLoggerPluginTest.java b/solr/core/src/test/org/apache/solr/security/SolrLogAuditLoggerPluginTest.java
index 6685b25f340..15242a166ec 100644
--- a/solr/core/src/test/org/apache/solr/security/SolrLogAuditLoggerPluginTest.java
+++ b/solr/core/src/test/org/apache/solr/security/SolrLogAuditLoggerPluginTest.java
@@ -20,9 +20,11 @@ package org.apache.solr.security;
 import static org.apache.solr.security.AuditLoggerPluginTest.EVENT_ANONYMOUS;
 import static org.apache.solr.security.AuditLoggerPluginTest.EVENT_AUTHENTICATED;
 
+import io.opentelemetry.api.common.Attributes;
 import java.util.HashMap;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.SolrException;
+import org.apache.solr.metrics.SolrMetricsContext;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -38,6 +40,8 @@ public class SolrLogAuditLoggerPluginTest extends SolrTestCaseJ4 {
     plugin = new SolrLogAuditLoggerPlugin();
     config = new HashMap<>();
     config.put("async", false);
+    SolrMetricsContext mockSolrMetricsContext = MockSolrMetricsContextFactory.create();
+    plugin.initializeMetrics(mockSolrMetricsContext, Attributes.empty(), "solr.test");
   }
 
   @Test(expected = SolrException.class)
diff --git a/solr/solr-ref-guide/modules/deployment-guide/pages/audit-logging.adoc b/solr/solr-ref-guide/modules/deployment-guide/pages/audit-logging.adoc
index 44efcd7f4c5..20c94bb71a1 100644
--- a/solr/solr-ref-guide/modules/deployment-guide/pages/audit-logging.adoc
+++ b/solr/solr-ref-guide/modules/deployment-guide/pages/audit-logging.adoc
@@ -230,20 +230,17 @@ See the javadocs for the base class at {solr-javadocs}/core/org/apache/solr/secu
 
 == Metrics
 
-Audit logging plugins record metrics about count and timing of log requests, as well as queue size for async loggers.
-The metrics keys are all recorded on the `SECURITY` category, and each metric name are prefixed with a scope of `/auditlogging` and the class name of the logger, e.g., `SolrLogAuditLoggerPlugin`.
+The metrics keys are all recorded on the `SECURITY` category, and each metric includes attributes for the handler (e.g., `/auditlogging`) and the class name of the logger (e.g., `SolrLogAuditLoggerPlugin`).
 The individual metrics are:
 
-* `count`: (_meter_) Records number and rate of audit logs written.
-* `errors`: (_meter_) Records number and rate of errors.
-* `lost`: (_meter_) Records number and rate of events lost if the queue is full and `blockAsync=false`.
-* `requestTimes`: (_timer_) Records latency and percentiles for audit logging performance.
-* `totalTime`: (_counter_) Records total time spent logging.
-* `queueCapacity`: (_gauge_) Records the maximum size of the async logging queue.
-* `queueSize`: (_gauge_) Records the number of events currently waiting in the queue.
-* `queuedTime`: (_timer_) Records the amount of time events waited in queue.
-Adding this with the `requestTimes` metric will show the total time from event to logging complete.
-* `async`: (_gauge_) Tells whether this logger is in async mode.
-
-TIP: If you experience a very high request rate and have a slow audit logger plugin, you may see the `queueSize` and `queuedTime` metrics increase, and possibly start dropping events (shown by an increase in `lost` count).
+* `solr_auditlogger_count_total`: (_counter_) Records number of audit logs written.
+* `solr_auditlogger_errors_total`: (_counter_) Records number of errors.
+* `solr_auditlogger_lost_total`: (_counter_) Records number of events lost if the queue is full and `blockAsync=false`.
+* `solr_auditlogger_request_times_nanoseconds`: (_histogram_) Records latency and percentiles for audit logging performance.
+* `solr_auditlogger_queue`: (_gauge_) Records the maximum size (`type=capacity`) and current size (`type=size`) of the async logging queue.
+* `solr_auditlogger_queued_time_nanoseconds`: (_histogram_) Records the amount of time events waited in queue.
+Adding this with the `solr_auditlogger_request_times_nanoseconds` metric will show the total time from event to logging complete.
+* `solr_auditlogger_async_enabled`: (_gauge_) Tells whether this logger is in async mode (1) or not (0).
+
+TIP: If you experience a very high request rate and have a slow audit logger plugin, you may see the `solr_auditlogger_queue` size metric increase, and possibly start dropping events (shown by an increase in `solr_auditlogger_lost_total` count).
 In this case you may want to increase the `numThreads` setting.
diff --git a/solr/test-framework/src/java/org/apache/solr/cloud/SolrCloudAuthTestCase.java b/solr/test-framework/src/java/org/apache/solr/cloud/SolrCloudAuthTestCase.java
index c9ed378aa27..7fbf26ab5cf 100644
--- a/solr/test-framework/src/java/org/apache/solr/cloud/SolrCloudAuthTestCase.java
+++ b/solr/test-framework/src/java/org/apache/solr/cloud/SolrCloudAuthTestCase.java
@@ -337,46 +337,6 @@ public class SolrCloudAuthTestCase extends SolrCloudTestCase {
     return metrics;
   }
 
-  /**
-   * Common test method to be able to check audit metrics
-   *
-   * @param className the class name to be used for composing prefix, e.g.
-   *     "SECURITY./auditlogging/SolrLogAuditLoggerPlugin"
-   */
-  protected void assertAuditMetricsMinimums(
-      MiniSolrCloudCluster cluster, String className, int count, int errors)
-      throws InterruptedException {
-    String prefix = "SECURITY./auditlogging." + className + ".";
-    Map<String, Long> expectedCounts = new HashMap<>();
-    expectedCounts.put("count", (long) count);
-
-    Map<String, Long> counts = countSecurityMetrics(cluster, prefix, AUDIT_METRICS_KEYS);
-    boolean success = isMetricsEqualOrLarger(AUDIT_METRICS_TO_COMPARE, expectedCounts, counts);
-    if (!success) {
-      log.info("First metrics count assert failed, pausing 2s before re-attempt");
-      Thread.sleep(2000);
-      counts = countSecurityMetrics(cluster, prefix, AUDIT_METRICS_KEYS);
-      success = isMetricsEqualOrLarger(AUDIT_METRICS_TO_COMPARE, expectedCounts, counts);
-    }
-
-    assertTrue(
-        "Expected metric minimums for prefix "
-            + prefix
-            + ": "
-            + expectedCounts
-            + ", but got: "
-            + counts,
-        success);
-  }
-
-  private boolean isMetricsEqualOrLarger(
-      List<String> metricsToCompare,
-      Map<String, Long> expectedCounts,
-      Map<String, Long> actualCounts) {
-    return metricsToCompare.stream()
-        .allMatch(k -> actualCounts.get(k).intValue() >= expectedCounts.get(k).intValue());
-  }
-
   // Have to sum the metrics from all three shards/nodes
   private long sumCount(String prefix, String key, List<Map<String, Metric>> metrics) {
     assertTrue(


(solr) 15/33: Build failure from zkContainer

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 0832659324d7ee8802d37ae3cd2d640db68b2248
Author: Matthew Biscocho <mb...@bloomberg.net>
AuthorDate: Mon Aug 25 09:35:10 2025 -0400

    Build failure from zkContainer
---
 solr/core/src/java/org/apache/solr/core/ZkContainer.java | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/core/ZkContainer.java b/solr/core/src/java/org/apache/solr/core/ZkContainer.java
index bd675378a1a..3953a8f1d27 100644
--- a/solr/core/src/java/org/apache/solr/core/ZkContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/ZkContainer.java
@@ -41,6 +41,7 @@ import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.logging.MDCLoggingContext;
 import org.apache.solr.metrics.SolrMetricProducer;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.OtelUnit;
 import org.apache.zookeeper.KeeperException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -179,7 +180,7 @@ public class ZkContainer {
                     measurement -> {
                       measurement.record(metricsListener.getBytesRead(), attributes);
                     },
-                    "By");
+                    OtelUnit.BYTES);
 
                 ctx.observableLongCounter(
                     "solr_zk_watches_fired",
@@ -194,7 +195,7 @@ public class ZkContainer {
                     measurement -> {
                       measurement.record(metricsListener.getBytesWritten());
                     },
-                    "By");
+                    OtelUnit.BYTES);
 
                 ctx.observableLongCounter(
                     "solr_zk_cumulative_multi_ops_total",


(solr) 01/33: SOLR-17774: Refactor initialization of GlobalOpenTelemetry (#3379)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 6a62ea4d31dc08d1cdc56b454da52472400b7b48
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Wed Jun 11 15:20:24 2025 -0400

    SOLR-17774: Refactor initialization of GlobalOpenTelemetry (#3379)
    
    * Refactor initialization of open telemetry
    
    * Use OpenTelemetryConfigurator.loadOpenTelemetrySdk
    
    * Remove dependencies
    
    * Typos
    
    * Changes from review comments
    
    * Fix TestTracerConfigurator
    
    * Create a non-recording tracerProvider as a noop
    
    * Move OTEL out of coreContainer
---
 gradle/libs.versions.toml                          |   2 +
 solr/api/gradle.lockfile                           |   8 ++
 solr/benchmark/gradle.lockfile                     |   8 ++
 solr/core/build.gradle                             |  11 ++-
 solr/core/gradle.lockfile                          |   8 ++
 .../java/org/apache/solr/core/CoreContainer.java   |  15 ++-
 ...gurator.java => OpenTelemetryConfigurator.java} | 103 +++++++++++++++------
 .../apache/solr/handler/RequestHandlerBase.java    |   2 +-
 .../org/apache/solr/metrics/SolrMetricManager.java |  15 ++-
 .../org/apache/solr/util/stats/MetricUtils.java    |   6 ++
 .../apache/solr/util/tracing/SimplePropagator.java |  17 ----
 .../org/apache/solr/util/tracing/TraceUtils.java   |   6 +-
 .../apache/solr/core/TestTracerConfigurator.java   |   6 +-
 .../org/apache/solr/metrics/MetricsConfigTest.java |  10 +-
 .../TestSimplePropagatorDistributedTracing.java    |  11 +--
 solr/cross-dc-manager/gradle.lockfile              |   8 ++
 solr/licenses/opentelemetry-api-1.50.0.jar.sha1    |   1 +
 .../licenses/opentelemetry-context-1.50.0.jar.sha1 |   1 +
 .../opentelemetry-exporter-common-1.50.0.jar.sha1  |   1 +
 .../opentelemetry-exporter-otlp-1.50.0.jar.sha1    |   1 +
 ...ntelemetry-exporter-otlp-common-1.50.0.jar.sha1 |   1 +
 ...metry-exporter-prometheus-1.50.0-alpha.jar.sha1 |   1 +
 ...elemetry-exporter-sender-okhttp-1.50.0.jar.sha1 |   1 +
 solr/licenses/opentelemetry-sdk-1.50.0.jar.sha1    |   1 +
 .../opentelemetry-sdk-common-1.50.0.jar.sha1       |   1 +
 ...try-sdk-extension-autoconfigure-1.50.0.jar.sha1 |   1 +
 ...sdk-extension-autoconfigure-spi-1.50.0.jar.sha1 |   1 +
 .../opentelemetry-sdk-logs-1.50.0.jar.sha1         |   1 +
 .../opentelemetry-sdk-metrics-1.50.0.jar.sha1      |   1 +
 .../opentelemetry-sdk-testing-1.50.0.jar.sha1      |   1 +
 .../opentelemetry-sdk-trace-1.50.0.jar.sha1        |   1 +
 solr/modules/analysis-extras/gradle.lockfile       |   8 ++
 solr/modules/clustering/gradle.lockfile            |   8 ++
 solr/modules/cross-dc/gradle.lockfile              |   8 ++
 solr/modules/extraction/gradle.lockfile            |   8 ++
 solr/modules/gcs-repository/gradle.lockfile        |   8 ++
 solr/modules/jwt-auth/gradle.lockfile              |   8 ++
 solr/modules/langid/gradle.lockfile                |   8 ++
 solr/modules/llm/gradle.lockfile                   |   8 ++
 solr/modules/ltr/gradle.lockfile                   |   8 ++
 solr/modules/opentelemetry/gradle.lockfile         |  13 ++-
 .../solr/opentelemetry/OtelTracerConfigurator.java |  14 ++-
 solr/modules/s3-repository/gradle.lockfile         |   8 ++
 solr/modules/scripting/gradle.lockfile             |   8 ++
 solr/modules/sql/gradle.lockfile                   |   8 ++
 solr/prometheus-exporter/gradle.lockfile           |   8 ++
 solr/server/gradle.lockfile                        |   8 ++
 solr/solr-ref-guide/gradle.lockfile                |   8 ++
 solr/solrj-streaming/gradle.lockfile               |   8 ++
 solr/solrj-zookeeper/gradle.lockfile               |   8 ++
 solr/solrj/gradle.lockfile                         |   8 ++
 solr/test-framework/gradle.lockfile                |   8 ++
 .../apache/solr/cloud/MiniSolrCloudCluster.java    |   7 +-
 solr/webapp/gradle.lockfile                        |   8 ++
 54 files changed, 361 insertions(+), 84 deletions(-)

diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index a3783feaac1..7010e88aefb 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -175,6 +175,7 @@ onnx = "1.22.0"
 openapi = "7.14.0"
 openjdk-jmh = "1.37"
 opentelemetry = "1.53.0"
+opentelemetry-prometheus = "1.50.0-alpha"
 osgi-annotation = "8.1.0"
 oshai-logging = "7.0.13"
 # @keep for version alignment
@@ -482,6 +483,7 @@ opentelemetry-api = { module = "io.opentelemetry:opentelemetry-api", version.ref
 opentelemetry-bom = { module = "io.opentelemetry:opentelemetry-bom", version.ref = "opentelemetry" }
 opentelemetry-context = { module = "io.opentelemetry:opentelemetry-context", version.ref = "opentelemetry" }
 opentelemetry-exporter-otlp = { module = "io.opentelemetry:opentelemetry-exporter-otlp", version.ref = "opentelemetry" }
+opentelemetry-exporter-prometheus = { module = "io.opentelemetry:opentelemetry-exporter-prometheus", version.ref = "opentelemetry-prometheus" }
 opentelemetry-sdk = { module = "io.opentelemetry:opentelemetry-sdk", version.ref = "opentelemetry" }
 opentelemetry-sdkextension-autoconfigure = { module = "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure", version.ref = "opentelemetry" }
 opentelemetry-sdktesting = { module = "io.opentelemetry:opentelemetry-sdk-testing", version.ref = "opentelemetry" }
diff --git a/solr/api/gradle.lockfile b/solr/api/gradle.lockfile
index e336621b4f6..69b2fdd9e91 100644
--- a/solr/api/gradle.lockfile
+++ b/solr/api/gradle.lockfile
@@ -61,6 +61,14 @@ io.netty:netty-transport:4.2.6.Final=jarValidation,testCompileClasspath,testRunt
 io.opentelemetry:opentelemetry-api:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,testRuntimeClasspath
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,testRuntimeClasspath
 io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,testRuntimeClasspath
diff --git a/solr/benchmark/gradle.lockfile b/solr/benchmark/gradle.lockfile
index 2b37c13583d..1e2a048bb5b 100644
--- a/solr/benchmark/gradle.lockfile
+++ b/solr/benchmark/gradle.lockfile
@@ -56,6 +56,14 @@ io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,runtimeClass
 io.opentelemetry:opentelemetry-api:1.53.0=compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,runtimeClasspath,testRuntimeClasspath
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,runtimeClasspath,testRuntimeClasspath
 io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,runtimeClasspath,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,runtimeClasspath,testRuntimeClasspath
diff --git a/solr/core/build.gradle b/solr/core/build.gradle
index 24fd610fc68..75fe8b62f2e 100644
--- a/solr/core/build.gradle
+++ b/solr/core/build.gradle
@@ -154,9 +154,18 @@ dependencies {
   implementation libs.tdunning.tdigest
 
   // Distributed Tracing
-  api libs.opentelemetry.api // Tracer is exposed on some methods
+  api libs.opentelemetry.api
   implementation libs.opentelemetry.context
 
+  implementation(libs.opentelemetry.exporter.prometheus, {
+    exclude group: "io.prometheus", module: "prometheus-metrics-exporter-httpserver"
+    exclude group: "io.prometheus", module: "prometheus-metrics-model"
+  })
+
+  implementation(libs.opentelemetry.sdk, {
+    exclude group: "io.opentelemetry", module: "opentelemetry-sdk-logs"
+  })
+
   implementation libs.apache.commons.exec
 
   implementation libs.apache.log4j.api
diff --git a/solr/core/gradle.lockfile b/solr/core/gradle.lockfile
index 24f951cdc09..5cb899ad1b5 100644
--- a/solr/core/gradle.lockfile
+++ b/solr/core/gradle.lockfile
@@ -61,6 +61,14 @@ io.netty:netty-transport:4.2.6.Final=apiHelper,compileClasspath,jarValidation,ru
 io.opentelemetry:opentelemetry-api:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 io.prometheus:prometheus-metrics-model:1.1.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index f79148804b4..0c45e84dd6e 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -32,6 +32,7 @@ import static org.apache.solr.security.AuthenticationPlugin.AUTHENTICATION_PLUGI
 import com.github.benmanes.caffeine.cache.Interner;
 import com.google.common.annotations.VisibleForTesting;
 import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
 import jakarta.inject.Singleton;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
@@ -150,6 +151,7 @@ import org.apache.solr.update.UpdateShardHandler;
 import org.apache.solr.util.OrderedExecutor;
 import org.apache.solr.util.StartupLoggingUtils;
 import org.apache.solr.util.stats.MetricUtils;
+import org.apache.solr.util.tracing.TraceUtils;
 import org.apache.zookeeper.KeeperException;
 import org.glassfish.hk2.utilities.binding.AbstractBinder;
 import org.glassfish.jersey.server.ApplicationHandler;
@@ -401,6 +403,11 @@ public class CoreContainer {
     this.solrHome = config.getSolrHome();
     this.solrCores = SolrCores.newSolrCores(this);
     this.nodeKeyPair = new SolrNodeKeyPair(cfg.getCloudConfig());
+    PrometheusMetricReader metricReader = new PrometheusMetricReader(true, null);
+    OpenTelemetryConfigurator.initializeOpenTelemetrySdk(cfg, loader, metricReader);
+    this.metricManager = new SolrMetricManager(loader, cfg.getMetricsConfig(), metricReader);
+    this.tracer = TraceUtils.getGlobalTracer();
+
     containerHandlers.put(PublicKeyHandler.PATH, new PublicKeyHandler(nodeKeyPair));
     if (null != this.cfg.getBooleanQueryMaxClauseCount()) {
       IndexSearcher.setMaxClauseCount(this.cfg.getBooleanQueryMaxClauseCount());
@@ -762,11 +769,9 @@ public class CoreContainer {
     containerPluginsRegistry.registerListener(
         clusterEventProducerFactory.getPluginRegistryListener());
 
-    metricManager = new SolrMetricManager(loader, cfg.getMetricsConfig());
-    String registryName = SolrMetricManager.getRegistryName(SolrInfoBean.Group.node);
-    solrMetricsContext = new SolrMetricsContext(metricManager, registryName, metricTag);
-
-    tracer = TracerConfigurator.loadTracer(loader, cfg.getTracerConfiguratorPluginInfo());
+    solrMetricsContext =
+        new SolrMetricsContext(
+            metricManager, SolrMetricManager.getRegistryName(SolrInfoBean.Group.node), metricTag);
 
     shardHandlerFactory =
         ShardHandlerFactory.newInstance(cfg.getShardHandlerFactoryPluginInfo(), loader);
diff --git a/solr/core/src/java/org/apache/solr/core/TracerConfigurator.java b/solr/core/src/java/org/apache/solr/core/OpenTelemetryConfigurator.java
similarity index 58%
rename from solr/core/src/java/org/apache/solr/core/TracerConfigurator.java
rename to solr/core/src/java/org/apache/solr/core/OpenTelemetryConfigurator.java
index 5d386701c6f..90170866dfe 100644
--- a/solr/core/src/java/org/apache/solr/core/TracerConfigurator.java
+++ b/solr/core/src/java/org/apache/solr/core/OpenTelemetryConfigurator.java
@@ -17,9 +17,17 @@
 
 package org.apache.solr.core;
 
+import io.opentelemetry.api.GlobalOpenTelemetry;
 import io.opentelemetry.api.trace.Tracer;
 import io.opentelemetry.context.Context;
 import io.opentelemetry.context.Scope;
+import io.opentelemetry.context.propagation.ContextPropagators;
+import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import io.opentelemetry.sdk.OpenTelemetrySdkBuilder;
+import io.opentelemetry.sdk.metrics.SdkMeterProvider;
+import io.opentelemetry.sdk.trace.SdkTracerProvider;
+import io.opentelemetry.sdk.trace.samplers.Sampler;
 import java.lang.invoke.MethodHandles;
 import java.util.Locale;
 import java.util.Map;
@@ -30,12 +38,11 @@ import org.apache.solr.common.util.ExecutorUtil;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.util.plugin.NamedListInitializedPlugin;
 import org.apache.solr.util.tracing.SimplePropagator;
-import org.apache.solr.util.tracing.TraceUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-/** Produces a {@link Tracer} from configuration. */
-public abstract class TracerConfigurator implements NamedListInitializedPlugin {
+/** Configures and loads/sets {@link GlobalOpenTelemetry} from a {@link OpenTelemetrySdk}. */
+public abstract class OpenTelemetryConfigurator implements NamedListInitializedPlugin {
 
   public static final boolean TRACE_ID_GEN_ENABLED =
       EnvUtils.getPropertyAsBool("solr.tracing.always.on.enabled", true);
@@ -46,26 +53,78 @@ public abstract class TracerConfigurator implements NamedListInitializedPlugin {
 
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
-  public static Tracer loadTracer(SolrResourceLoader loader, PluginInfo info) {
+  private static volatile boolean loaded = false;
+
+  /**
+   * Initializes the {@link io.opentelemetry.api.GlobalOpenTelemetry} instance by configuring the
+   * {@link io.opentelemetry.sdk.OpenTelemetrySdk} through custom plugin, auto-configure or default
+   * SDK.
+   */
+  public static synchronized void initializeOpenTelemetrySdk(
+      NodeConfig cfg, SolrResourceLoader loader, PrometheusMetricReader prometheusMetricReader) {
+    PluginInfo info = (cfg != null) ? cfg.getTracerConfiguratorPluginInfo() : null;
+
     if (info != null && info.isEnabled()) {
-      TracerConfigurator configurator =
-          loader.newInstance(info.className, TracerConfigurator.class);
-      configurator.init(info.initArgs);
-      ExecutorUtil.addThreadLocalProvider(new ContextThreadLocalProvider());
-      return configurator.getTracer();
-    }
-    if (shouldAutoConfigOTEL()) {
-      return autoConfigOTEL(loader);
+      OpenTelemetryConfigurator.configureCustomOpenTelemetrySdk(
+          loader, cfg.getTracerConfiguratorPluginInfo());
+    } else if (OpenTelemetryConfigurator.shouldAutoConfigOTEL()) {
+      OpenTelemetryConfigurator.autoConfigureOpenTelemetrySdk(loader);
+    } else {
+      // Initializing sampler as always off to replicate no-op Tracer provider
+      OpenTelemetryConfigurator.configureOpenTelemetrySdk(
+          SdkMeterProvider.builder().registerMetricReader(prometheusMetricReader).build(),
+          SdkTracerProvider.builder().setSampler(Sampler.alwaysOff()).build());
     }
+  }
+
+  private static void configureOpenTelemetrySdk(
+      SdkMeterProvider sdkMeterProvider, SdkTracerProvider sdkTracerProvider) {
+    if (loaded) return;
+
+    OpenTelemetrySdkBuilder builder = OpenTelemetrySdk.builder();
     if (TRACE_ID_GEN_ENABLED) {
+      log.info("OpenTelemetry tracer enabled with simple propagation only.");
       ExecutorUtil.addThreadLocalProvider(new ContextThreadLocalProvider());
-      return SimplePropagator.load();
+      builder.setPropagators(ContextPropagators.create(SimplePropagator.getInstance()));
     }
-    return TraceUtils.getGlobalTracer();
+
+    if (sdkMeterProvider != null) builder.setMeterProvider(sdkMeterProvider);
+    if (sdkTracerProvider != null) builder.setTracerProvider(sdkTracerProvider);
+
+    GlobalOpenTelemetry.set(builder.build());
+    loaded = true;
+  }
+
+  private static void autoConfigureOpenTelemetrySdk(SolrResourceLoader loader) {
+    if (loaded) return;
+    try {
+      OpenTelemetryConfigurator configurator =
+          loader.newInstance(DEFAULT_CLASS_NAME, OpenTelemetryConfigurator.class);
+      configurator.init(new NamedList<>());
+      ExecutorUtil.addThreadLocalProvider(new ContextThreadLocalProvider());
+      loaded = true;
+    } catch (SolrException e) {
+      log.error(
+          "Unable to auto-config OpenTelemetry with class {}. Make sure you have enabled the 'opentelemetry' module",
+          DEFAULT_CLASS_NAME,
+          e);
+    }
+  }
+
+  private static void configureCustomOpenTelemetrySdk(SolrResourceLoader loader, PluginInfo info) {
+    if (loaded) return;
+
+    OpenTelemetryConfigurator configurator =
+        loader.newInstance(info.className, OpenTelemetryConfigurator.class);
+    configurator.init(info.initArgs);
+    ExecutorUtil.addThreadLocalProvider(new ContextThreadLocalProvider());
+    loaded = true;
   }
 
   protected abstract Tracer getTracer();
 
+  protected abstract OpenTelemetrySdk getOpenTelemetrySdk();
+
   private static class ContextThreadLocalProvider
       implements ExecutorUtil.InheritableThreadLocalProvider {
 
@@ -88,22 +147,6 @@ public abstract class TracerConfigurator implements NamedListInitializedPlugin {
     }
   }
 
-  private static Tracer autoConfigOTEL(SolrResourceLoader loader) {
-    try {
-      TracerConfigurator configurator =
-          loader.newInstance(DEFAULT_CLASS_NAME, TracerConfigurator.class);
-      configurator.init(new NamedList<>());
-      ExecutorUtil.addThreadLocalProvider(new ContextThreadLocalProvider());
-      return configurator.getTracer();
-    } catch (SolrException e) {
-      log.error(
-          "Unable to auto-config OpenTelemetry with class {}. Make sure you have enabled the 'opentelemetry' module",
-          DEFAULT_CLASS_NAME,
-          e);
-    }
-    return TraceUtils.getGlobalTracer();
-  }
-
   /**
    * Best effort way to determine if we should attempt to init OTEL from system properties.
    *
diff --git a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
index 0f62f31aaab..bc5331ef556 100644
--- a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
+++ b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
@@ -184,7 +184,7 @@ public abstract class RequestHandlerBase
         new HandlerMetrics(
             new SolrMetricsContext(
                 new SolrMetricManager(
-                    null, new MetricsConfig.MetricsConfigBuilder().setEnabled(false).build()),
+                    null, new MetricsConfig.MetricsConfigBuilder().setEnabled(false).build(), null),
                 "NO_OP",
                 "NO_OP"));
 
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
index fa05c2797dc..b8700d3a412 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
@@ -26,6 +26,8 @@ import com.codahale.metrics.MetricRegistry;
 import com.codahale.metrics.MetricSet;
 import com.codahale.metrics.SharedMetricRegistries;
 import com.codahale.metrics.Timer;
+import io.opentelemetry.api.metrics.MeterProvider;
+import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
@@ -55,6 +57,7 @@ import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.core.SolrResourceLoader;
 import org.apache.solr.logging.MDCLoggingContext;
+import org.apache.solr.util.stats.MetricUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.MDC;
@@ -120,16 +123,26 @@ public class SolrMetricManager {
   private final MetricRegistry.MetricSupplier<Timer> timerSupplier;
   private final MetricRegistry.MetricSupplier<Histogram> histogramSupplier;
 
+  private final MeterProvider meterProvider;
+  private final PrometheusMetricReader prometheusMetricReader;
+
   public SolrMetricManager() {
     metricsConfig = new MetricsConfig.MetricsConfigBuilder().build();
     counterSupplier = MetricSuppliers.counterSupplier(null, null);
     meterSupplier = MetricSuppliers.meterSupplier(null, null);
     timerSupplier = MetricSuppliers.timerSupplier(null, null);
     histogramSupplier = MetricSuppliers.histogramSupplier(null, null);
+    meterProvider = MetricUtils.getMeterProvider();
+    prometheusMetricReader = null;
   }
 
-  public SolrMetricManager(SolrResourceLoader loader, MetricsConfig metricsConfig) {
+  public SolrMetricManager(
+      SolrResourceLoader loader,
+      MetricsConfig metricsConfig,
+      PrometheusMetricReader prometheusMetricReader) {
     this.metricsConfig = metricsConfig;
+    this.prometheusMetricReader = prometheusMetricReader;
+    this.meterProvider = MetricUtils.getMeterProvider();
     counterSupplier = MetricSuppliers.counterSupplier(loader, metricsConfig.getCounterSupplier());
     meterSupplier = MetricSuppliers.meterSupplier(loader, metricsConfig.getMeterSupplier());
     timerSupplier = MetricSuppliers.timerSupplier(loader, metricsConfig.getTimerSupplier());
diff --git a/solr/core/src/java/org/apache/solr/util/stats/MetricUtils.java b/solr/core/src/java/org/apache/solr/util/stats/MetricUtils.java
index 9cff065c06f..d0f37de403f 100644
--- a/solr/core/src/java/org/apache/solr/util/stats/MetricUtils.java
+++ b/solr/core/src/java/org/apache/solr/util/stats/MetricUtils.java
@@ -26,6 +26,8 @@ import com.codahale.metrics.MetricFilter;
 import com.codahale.metrics.MetricRegistry;
 import com.codahale.metrics.Snapshot;
 import com.codahale.metrics.Timer;
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.metrics.MeterProvider;
 import java.beans.BeanInfo;
 import java.beans.IntrospectionException;
 import java.beans.Introspector;
@@ -852,4 +854,8 @@ public class MetricUtils {
       }
     }
   }
+
+  public static MeterProvider getMeterProvider() {
+    return GlobalOpenTelemetry.getMeterProvider();
+  }
 }
diff --git a/solr/core/src/java/org/apache/solr/util/tracing/SimplePropagator.java b/solr/core/src/java/org/apache/solr/util/tracing/SimplePropagator.java
index e81657ca2ae..51944b08703 100644
--- a/solr/core/src/java/org/apache/solr/util/tracing/SimplePropagator.java
+++ b/solr/core/src/java/org/apache/solr/util/tracing/SimplePropagator.java
@@ -16,12 +16,8 @@
  */
 package org.apache.solr.util.tracing;
 
-import io.opentelemetry.api.GlobalOpenTelemetry;
-import io.opentelemetry.api.OpenTelemetry;
-import io.opentelemetry.api.trace.Tracer;
 import io.opentelemetry.context.Context;
 import io.opentelemetry.context.ContextKey;
-import io.opentelemetry.context.propagation.ContextPropagators;
 import io.opentelemetry.context.propagation.TextMapGetter;
 import io.opentelemetry.context.propagation.TextMapPropagator;
 import io.opentelemetry.context.propagation.TextMapSetter;
@@ -54,19 +50,6 @@ public class SimplePropagator implements TextMapPropagator {
 
   private static final AtomicLong traceCounter = new AtomicLong(0);
 
-  private static volatile boolean loaded = false;
-
-  public static synchronized Tracer load() {
-    if (!loaded) {
-      log.info("OpenTelemetry tracer enabled with simple propagation only.");
-      OpenTelemetry otel =
-          OpenTelemetry.propagating(ContextPropagators.create(SimplePropagator.getInstance()));
-      GlobalOpenTelemetry.set(otel);
-      loaded = true;
-    }
-    return TraceUtils.getGlobalTracer();
-  }
-
   public static TextMapPropagator getInstance() {
     return INSTANCE;
   }
diff --git a/solr/core/src/java/org/apache/solr/util/tracing/TraceUtils.java b/solr/core/src/java/org/apache/solr/util/tracing/TraceUtils.java
index 11adf3f0eca..645a412f6c1 100644
--- a/solr/core/src/java/org/apache/solr/util/tracing/TraceUtils.java
+++ b/solr/core/src/java/org/apache/solr/util/tracing/TraceUtils.java
@@ -119,8 +119,10 @@ public class TraceUtils {
    * @param consumer consumer to be called
    */
   public static void ifValidTraceId(Span span, Consumer<Span> consumer) {
-    if (TraceId.isValid(span.getSpanContext().getTraceId())) {
-      consumer.accept(span);
+    if (span.isRecording()) {
+      if (TraceId.isValid(span.getSpanContext().getTraceId())) {
+        consumer.accept(span);
+      }
     }
   }
 
diff --git a/solr/core/src/test/org/apache/solr/core/TestTracerConfigurator.java b/solr/core/src/test/org/apache/solr/core/TestTracerConfigurator.java
index bb30a6e8f2d..ba453efc5d4 100644
--- a/solr/core/src/test/org/apache/solr/core/TestTracerConfigurator.java
+++ b/solr/core/src/test/org/apache/solr/core/TestTracerConfigurator.java
@@ -39,9 +39,9 @@ public class TestTracerConfigurator extends SolrTestCaseJ4 {
 
   @Test
   public void configuratorClassDoesNotExistTest() {
-    assertTrue(TracerConfigurator.shouldAutoConfigOTEL());
+    assertTrue(OpenTelemetryConfigurator.shouldAutoConfigOTEL());
     SolrResourceLoader loader = new SolrResourceLoader(TEST_PATH().resolve("collection1"));
-    TracerConfigurator.loadTracer(loader, null);
+    OpenTelemetryConfigurator.initializeOpenTelemetrySdk(null, loader, null);
     assertEquals(
         "Expecting noop otel after failure to auto-init",
         TracerProvider.noop().get(null),
@@ -52,7 +52,7 @@ public class TestTracerConfigurator extends SolrTestCaseJ4 {
   public void otelDisabledByProperty() {
     System.setProperty("otel.sdk.disabled", "true");
     try {
-      assertFalse(TracerConfigurator.shouldAutoConfigOTEL());
+      assertFalse(OpenTelemetryConfigurator.shouldAutoConfigOTEL());
     } finally {
       System.clearProperty("otel.sdk.disabled");
     }
diff --git a/solr/core/src/test/org/apache/solr/metrics/MetricsConfigTest.java b/solr/core/src/test/org/apache/solr/metrics/MetricsConfigTest.java
index eff63f4db72..32245ffb4c6 100644
--- a/solr/core/src/test/org/apache/solr/metrics/MetricsConfigTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/MetricsConfigTest.java
@@ -35,7 +35,7 @@ public class MetricsConfigTest extends SolrTestCaseJ4 {
   public void testDefaults() {
     NodeConfig cfg = loadNodeConfig("solr-metricsconfig.xml");
     SolrMetricManager mgr =
-        new SolrMetricManager(cfg.getSolrResourceLoader(), cfg.getMetricsConfig());
+        new SolrMetricManager(cfg.getSolrResourceLoader(), cfg.getMetricsConfig(), null);
     assertTrue(mgr.getCounterSupplier() instanceof MetricSuppliers.DefaultCounterSupplier);
     assertTrue(mgr.getMeterSupplier() instanceof MetricSuppliers.DefaultMeterSupplier);
     assertTrue(mgr.getTimerSupplier() instanceof MetricSuppliers.DefaultTimerSupplier);
@@ -54,7 +54,7 @@ public class MetricsConfigTest extends SolrTestCaseJ4 {
     System.setProperty("histogram.reservoir", SlidingTimeWindowReservoir.class.getName());
     NodeConfig cfg = loadNodeConfig("solr-metricsconfig.xml");
     SolrMetricManager mgr =
-        new SolrMetricManager(cfg.getSolrResourceLoader(), cfg.getMetricsConfig());
+        new SolrMetricManager(cfg.getSolrResourceLoader(), cfg.getMetricsConfig(), null);
     assertTrue(mgr.getCounterSupplier() instanceof MetricSuppliers.DefaultCounterSupplier);
     assertTrue(mgr.getMeterSupplier() instanceof MetricSuppliers.DefaultMeterSupplier);
     assertTrue(mgr.getTimerSupplier() instanceof MetricSuppliers.DefaultTimerSupplier);
@@ -73,7 +73,7 @@ public class MetricsConfigTest extends SolrTestCaseJ4 {
     System.setProperty("histogram.class", MockHistogramSupplier.class.getName());
     NodeConfig cfg = loadNodeConfig("solr-metricsconfig.xml");
     SolrMetricManager mgr =
-        new SolrMetricManager(cfg.getSolrResourceLoader(), cfg.getMetricsConfig());
+        new SolrMetricManager(cfg.getSolrResourceLoader(), cfg.getMetricsConfig(), null);
     assertTrue(mgr.getCounterSupplier() instanceof MockCounterSupplier);
     assertTrue(mgr.getMeterSupplier() instanceof MockMeterSupplier);
     assertTrue(mgr.getTimerSupplier() instanceof MockTimerSupplier);
@@ -100,7 +100,7 @@ public class MetricsConfigTest extends SolrTestCaseJ4 {
     System.setProperty("metricsEnabled", "false");
     NodeConfig cfg = loadNodeConfig("solr-metricsconfig.xml");
     SolrMetricManager mgr =
-        new SolrMetricManager(cfg.getSolrResourceLoader(), cfg.getMetricsConfig());
+        new SolrMetricManager(cfg.getSolrResourceLoader(), cfg.getMetricsConfig(), null);
     assertTrue(mgr.getCounterSupplier() instanceof MetricSuppliers.NoOpCounterSupplier);
     assertTrue(mgr.getMeterSupplier() instanceof MetricSuppliers.NoOpMeterSupplier);
     assertTrue(mgr.getTimerSupplier() instanceof MetricSuppliers.NoOpTimerSupplier);
@@ -111,7 +111,7 @@ public class MetricsConfigTest extends SolrTestCaseJ4 {
   public void testMissingValuesConfig() {
     NodeConfig cfg = loadNodeConfig("solr-metricsconfig1.xml");
     SolrMetricManager mgr =
-        new SolrMetricManager(cfg.getSolrResourceLoader(), cfg.getMetricsConfig());
+        new SolrMetricManager(cfg.getSolrResourceLoader(), cfg.getMetricsConfig(), null);
     assertNull("nullNumber", mgr.nullNumber());
     assertEquals("notANumber", -1, mgr.notANumber());
     assertEquals("nullNumber", "", mgr.nullString());
diff --git a/solr/core/src/test/org/apache/solr/util/tracing/TestSimplePropagatorDistributedTracing.java b/solr/core/src/test/org/apache/solr/util/tracing/TestSimplePropagatorDistributedTracing.java
index 8b1af81c59e..270c53c0471 100644
--- a/solr/core/src/test/org/apache/solr/util/tracing/TestSimplePropagatorDistributedTracing.java
+++ b/solr/core/src/test/org/apache/solr/util/tracing/TestSimplePropagatorDistributedTracing.java
@@ -18,7 +18,8 @@
 package org.apache.solr.util.tracing;
 
 import io.opentelemetry.api.GlobalOpenTelemetry;
-import io.opentelemetry.api.trace.TracerProvider;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.Tracer;
 import java.io.IOException;
 import java.util.List;
 import java.util.Optional;
@@ -51,11 +52,9 @@ public class TestSimplePropagatorDistributedTracing extends SolrCloudTestCase {
   public static void setupCluster() throws Exception {
     configureCluster(4).addConfig("conf", configset("cloud-minimal")).configure();
 
-    // tracer should be disabled
-    assertEquals(
-        "Expecting noop otel (propagating only)",
-        TracerProvider.noop(),
-        GlobalOpenTelemetry.get().getTracerProvider());
+    Tracer tracer = GlobalOpenTelemetry.get().getTracer("solr");
+    Span span = tracer.spanBuilder("testSpan").startSpan();
+    assertFalse("Expected a no-op/non-recording tracer (propagating-only)", span.isRecording());
 
     CollectionAdminRequest.createCollection(COLLECTION, "conf", 2, 2)
         .process(cluster.getSolrClient());
diff --git a/solr/cross-dc-manager/gradle.lockfile b/solr/cross-dc-manager/gradle.lockfile
index 4c08c73b9b9..ad7df219e1d 100644
--- a/solr/cross-dc-manager/gradle.lockfile
+++ b/solr/cross-dc-manager/gradle.lockfile
@@ -73,6 +73,14 @@ io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,runtimeClass
 io.opentelemetry:opentelemetry-api:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/licenses/opentelemetry-api-1.50.0.jar.sha1 b/solr/licenses/opentelemetry-api-1.50.0.jar.sha1
new file mode 100644
index 00000000000..3c7f32b2643
--- /dev/null
+++ b/solr/licenses/opentelemetry-api-1.50.0.jar.sha1
@@ -0,0 +1 @@
+83a69114c56ff2664d21906c62431bceb1c294ed
diff --git a/solr/licenses/opentelemetry-context-1.50.0.jar.sha1 b/solr/licenses/opentelemetry-context-1.50.0.jar.sha1
new file mode 100644
index 00000000000..e6d67b0652e
--- /dev/null
+++ b/solr/licenses/opentelemetry-context-1.50.0.jar.sha1
@@ -0,0 +1 @@
+dcdc20a193e82d66ecf5a1a978a799bc19e9b210
diff --git a/solr/licenses/opentelemetry-exporter-common-1.50.0.jar.sha1 b/solr/licenses/opentelemetry-exporter-common-1.50.0.jar.sha1
new file mode 100644
index 00000000000..827f7773e3f
--- /dev/null
+++ b/solr/licenses/opentelemetry-exporter-common-1.50.0.jar.sha1
@@ -0,0 +1 @@
+73bc444d344995bfcd4705a0261662c27d58c9c9
diff --git a/solr/licenses/opentelemetry-exporter-otlp-1.50.0.jar.sha1 b/solr/licenses/opentelemetry-exporter-otlp-1.50.0.jar.sha1
new file mode 100644
index 00000000000..e5cffae0071
--- /dev/null
+++ b/solr/licenses/opentelemetry-exporter-otlp-1.50.0.jar.sha1
@@ -0,0 +1 @@
+8eb4ce42340ae0443793e9fa41ba9e4f7743e66e
diff --git a/solr/licenses/opentelemetry-exporter-otlp-common-1.50.0.jar.sha1 b/solr/licenses/opentelemetry-exporter-otlp-common-1.50.0.jar.sha1
new file mode 100644
index 00000000000..9f5ed208526
--- /dev/null
+++ b/solr/licenses/opentelemetry-exporter-otlp-common-1.50.0.jar.sha1
@@ -0,0 +1 @@
+3b5cf0eeffa6087324f7c57b5d9e48c62b8e2e8c
diff --git a/solr/licenses/opentelemetry-exporter-prometheus-1.50.0-alpha.jar.sha1 b/solr/licenses/opentelemetry-exporter-prometheus-1.50.0-alpha.jar.sha1
new file mode 100644
index 00000000000..6fb5fa25db3
--- /dev/null
+++ b/solr/licenses/opentelemetry-exporter-prometheus-1.50.0-alpha.jar.sha1
@@ -0,0 +1 @@
+087b606101ac171a9d9ba927a2fd8718a0fcc819
diff --git a/solr/licenses/opentelemetry-exporter-sender-okhttp-1.50.0.jar.sha1 b/solr/licenses/opentelemetry-exporter-sender-okhttp-1.50.0.jar.sha1
new file mode 100644
index 00000000000..30283f96545
--- /dev/null
+++ b/solr/licenses/opentelemetry-exporter-sender-okhttp-1.50.0.jar.sha1
@@ -0,0 +1 @@
+83ee66ff7ff5babc1884533a8c3a6a99fd64d643
diff --git a/solr/licenses/opentelemetry-sdk-1.50.0.jar.sha1 b/solr/licenses/opentelemetry-sdk-1.50.0.jar.sha1
new file mode 100644
index 00000000000..ab4123d5478
--- /dev/null
+++ b/solr/licenses/opentelemetry-sdk-1.50.0.jar.sha1
@@ -0,0 +1 @@
+1c6ffdd0bf5a3e42154912f0ab1b1bbd73499af1
diff --git a/solr/licenses/opentelemetry-sdk-common-1.50.0.jar.sha1 b/solr/licenses/opentelemetry-sdk-common-1.50.0.jar.sha1
new file mode 100644
index 00000000000..6739b9258fc
--- /dev/null
+++ b/solr/licenses/opentelemetry-sdk-common-1.50.0.jar.sha1
@@ -0,0 +1 @@
+928c43d75cd0675070d38d041973370823d6face
diff --git a/solr/licenses/opentelemetry-sdk-extension-autoconfigure-1.50.0.jar.sha1 b/solr/licenses/opentelemetry-sdk-extension-autoconfigure-1.50.0.jar.sha1
new file mode 100644
index 00000000000..4190d963d8f
--- /dev/null
+++ b/solr/licenses/opentelemetry-sdk-extension-autoconfigure-1.50.0.jar.sha1
@@ -0,0 +1 @@
+ad69f784c48db4bdc216f90fddead6c7e26b0727
diff --git a/solr/licenses/opentelemetry-sdk-extension-autoconfigure-spi-1.50.0.jar.sha1 b/solr/licenses/opentelemetry-sdk-extension-autoconfigure-spi-1.50.0.jar.sha1
new file mode 100644
index 00000000000..3112fe841ec
--- /dev/null
+++ b/solr/licenses/opentelemetry-sdk-extension-autoconfigure-spi-1.50.0.jar.sha1
@@ -0,0 +1 @@
+47676b3c16db83a30b002d0e9cd9e6405236dd21
diff --git a/solr/licenses/opentelemetry-sdk-logs-1.50.0.jar.sha1 b/solr/licenses/opentelemetry-sdk-logs-1.50.0.jar.sha1
new file mode 100644
index 00000000000..b2e86ef3a8e
--- /dev/null
+++ b/solr/licenses/opentelemetry-sdk-logs-1.50.0.jar.sha1
@@ -0,0 +1 @@
+fb7ace15eca8878a2c3cf86320ed1d2fd1c0f206
diff --git a/solr/licenses/opentelemetry-sdk-metrics-1.50.0.jar.sha1 b/solr/licenses/opentelemetry-sdk-metrics-1.50.0.jar.sha1
new file mode 100644
index 00000000000..cbaa77257d3
--- /dev/null
+++ b/solr/licenses/opentelemetry-sdk-metrics-1.50.0.jar.sha1
@@ -0,0 +1 @@
+49432de0171798f671bda82bf7eef6ae283b3b7d
diff --git a/solr/licenses/opentelemetry-sdk-testing-1.50.0.jar.sha1 b/solr/licenses/opentelemetry-sdk-testing-1.50.0.jar.sha1
new file mode 100644
index 00000000000..2f3100622bb
--- /dev/null
+++ b/solr/licenses/opentelemetry-sdk-testing-1.50.0.jar.sha1
@@ -0,0 +1 @@
+2972ea619333a9c39f5e6104884278ab9d439f69
diff --git a/solr/licenses/opentelemetry-sdk-trace-1.50.0.jar.sha1 b/solr/licenses/opentelemetry-sdk-trace-1.50.0.jar.sha1
new file mode 100644
index 00000000000..3255b03451f
--- /dev/null
+++ b/solr/licenses/opentelemetry-sdk-trace-1.50.0.jar.sha1
@@ -0,0 +1 @@
+19d60d3b72aa2d0d89a02361eedf7157ecc1fc7d
diff --git a/solr/modules/analysis-extras/gradle.lockfile b/solr/modules/analysis-extras/gradle.lockfile
index 1d7b0cb3705..0e367394f21 100644
--- a/solr/modules/analysis-extras/gradle.lockfile
+++ b/solr/modules/analysis-extras/gradle.lockfile
@@ -58,6 +58,14 @@ io.netty:netty-transport:4.2.6.Final=apiHelper,compileClasspath,jarValidation,ru
 io.opentelemetry:opentelemetry-api:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-model:1.1.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/clustering/gradle.lockfile b/solr/modules/clustering/gradle.lockfile
index 8755114aba2..373d5ebb9fa 100644
--- a/solr/modules/clustering/gradle.lockfile
+++ b/solr/modules/clustering/gradle.lockfile
@@ -56,6 +56,14 @@ io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,runtimeClass
 io.opentelemetry:opentelemetry-api:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/cross-dc/gradle.lockfile b/solr/modules/cross-dc/gradle.lockfile
index afeb2262a96..631a766e08e 100644
--- a/solr/modules/cross-dc/gradle.lockfile
+++ b/solr/modules/cross-dc/gradle.lockfile
@@ -57,6 +57,14 @@ io.netty:netty-transport:4.2.6.Final=apiHelper,compileClasspath,jarValidation,ru
 io.opentelemetry:opentelemetry-api:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-model:1.1.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/extraction/gradle.lockfile b/solr/modules/extraction/gradle.lockfile
index abff70b0d7c..27722aff9a7 100644
--- a/solr/modules/extraction/gradle.lockfile
+++ b/solr/modules/extraction/gradle.lockfile
@@ -84,6 +84,14 @@ io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,runtimeClass
 io.opentelemetry:opentelemetry-api:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/gcs-repository/gradle.lockfile b/solr/modules/gcs-repository/gradle.lockfile
index a342546f048..1ab934dabc1 100644
--- a/solr/modules/gcs-repository/gradle.lockfile
+++ b/solr/modules/gcs-repository/gradle.lockfile
@@ -106,6 +106,14 @@ io.opencensus:opencensus-proto:0.2.0=jarValidation,runtimeClasspath,runtimeLibs,
 io.opentelemetry:opentelemetry-api:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.perfmark:perfmark-api:0.27.0=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-model:1.1.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/jwt-auth/gradle.lockfile b/solr/modules/jwt-auth/gradle.lockfile
index 0197986d4d9..dd6f9dc774e 100644
--- a/solr/modules/jwt-auth/gradle.lockfile
+++ b/solr/modules/jwt-auth/gradle.lockfile
@@ -70,6 +70,14 @@ io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,runtimeClass
 io.opentelemetry:opentelemetry-api:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/langid/gradle.lockfile b/solr/modules/langid/gradle.lockfile
index c5ef6c13c02..8144d8517fb 100644
--- a/solr/modules/langid/gradle.lockfile
+++ b/solr/modules/langid/gradle.lockfile
@@ -57,6 +57,14 @@ io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,runtimeClass
 io.opentelemetry:opentelemetry-api:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/llm/gradle.lockfile b/solr/modules/llm/gradle.lockfile
index f02cf3adf47..74861b226f9 100644
--- a/solr/modules/llm/gradle.lockfile
+++ b/solr/modules/llm/gradle.lockfile
@@ -71,6 +71,14 @@ io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,runtimeClass
 io.opentelemetry:opentelemetry-api:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/ltr/gradle.lockfile b/solr/modules/ltr/gradle.lockfile
index 96e07a47c84..6fa8ed49485 100644
--- a/solr/modules/ltr/gradle.lockfile
+++ b/solr/modules/ltr/gradle.lockfile
@@ -56,6 +56,14 @@ io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,runtimeClass
 io.opentelemetry:opentelemetry-api:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/opentelemetry/gradle.lockfile b/solr/modules/opentelemetry/gradle.lockfile
index e3bb85e3910..ca7e5eb6f9f 100644
--- a/solr/modules/opentelemetry/gradle.lockfile
+++ b/solr/modules/opentelemetry/gradle.lockfile
@@ -79,18 +79,21 @@ io.opentelemetry:opentelemetry-api:1.53.0=compileClasspath,jarValidation,runtime
 io.opentelemetry:opentelemetry-bom:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=solrPlatformLibs
 io.opentelemetry:opentelemetry-exporter-common:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-exporter-otlp-common:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-exporter-otlp:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-exporter-sender-okhttp:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
-io.opentelemetry:opentelemetry-sdk-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=solrPlatformLibs
 io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
-io.opentelemetry:opentelemetry-sdk-logs:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
-io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-testing:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
-io.opentelemetry:opentelemetry-sdk-trace:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
-io.opentelemetry:opentelemetry-sdk:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.perfmark:perfmark-api:0.27.0=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/opentelemetry/src/java/org/apache/solr/opentelemetry/OtelTracerConfigurator.java b/solr/modules/opentelemetry/src/java/org/apache/solr/opentelemetry/OtelTracerConfigurator.java
index 0d2dd5df9de..c70acee63c2 100644
--- a/solr/modules/opentelemetry/src/java/org/apache/solr/opentelemetry/OtelTracerConfigurator.java
+++ b/solr/modules/opentelemetry/src/java/org/apache/solr/opentelemetry/OtelTracerConfigurator.java
@@ -17,6 +17,7 @@
 package org.apache.solr.opentelemetry;
 
 import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
 import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
 import java.lang.invoke.MethodHandles;
 import java.util.Arrays;
@@ -27,7 +28,7 @@ import java.util.Objects;
 import java.util.stream.Collectors;
 import org.apache.solr.common.util.EnvUtils;
 import org.apache.solr.common.util.NamedList;
-import org.apache.solr.core.TracerConfigurator;
+import org.apache.solr.core.OpenTelemetryConfigurator;
 import org.apache.solr.util.tracing.TraceUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -35,11 +36,13 @@ import org.slf4j.LoggerFactory;
 /**
  * Tracing TracerConfigurator implementation which exports spans to OpenTelemetry in OTLP format.
  */
-public class OtelTracerConfigurator extends TracerConfigurator {
+public class OtelTracerConfigurator extends OpenTelemetryConfigurator {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   private final Map<String, String> currentEnv;
 
+  private OpenTelemetrySdk openTelemetrySdk;
+
   public OtelTracerConfigurator() {
     this(System.getenv());
   }
@@ -53,10 +56,15 @@ public class OtelTracerConfigurator extends TracerConfigurator {
     return TraceUtils.getGlobalTracer();
   }
 
+  @Override
+  public OpenTelemetrySdk getOpenTelemetrySdk() {
+    return this.openTelemetrySdk;
+  }
+
   @Override
   public void init(NamedList<?> args) {
     prepareConfiguration(args);
-    AutoConfiguredOpenTelemetrySdk.initialize();
+    this.openTelemetrySdk = AutoConfiguredOpenTelemetrySdk.initialize().getOpenTelemetrySdk();
   }
 
   void prepareConfiguration(NamedList<?> args) {
diff --git a/solr/modules/s3-repository/gradle.lockfile b/solr/modules/s3-repository/gradle.lockfile
index eae8773a29b..714f2e35b96 100644
--- a/solr/modules/s3-repository/gradle.lockfile
+++ b/solr/modules/s3-repository/gradle.lockfile
@@ -73,6 +73,14 @@ io.netty:netty-transport:4.2.6.Final=apiHelper,compileClasspath,jarValidation,ru
 io.opentelemetry:opentelemetry-api:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-model:1.1.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/scripting/gradle.lockfile b/solr/modules/scripting/gradle.lockfile
index c6b9ec9b326..55897192931 100644
--- a/solr/modules/scripting/gradle.lockfile
+++ b/solr/modules/scripting/gradle.lockfile
@@ -56,6 +56,14 @@ io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,runtimeClass
 io.opentelemetry:opentelemetry-api:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/sql/gradle.lockfile b/solr/modules/sql/gradle.lockfile
index 3eaef7ee425..baf17e02dd0 100644
--- a/solr/modules/sql/gradle.lockfile
+++ b/solr/modules/sql/gradle.lockfile
@@ -59,6 +59,14 @@ io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,runtimeClass
 io.opentelemetry:opentelemetry-api:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/prometheus-exporter/gradle.lockfile b/solr/prometheus-exporter/gradle.lockfile
index e2bc0266ba5..0fd347ff573 100644
--- a/solr/prometheus-exporter/gradle.lockfile
+++ b/solr/prometheus-exporter/gradle.lockfile
@@ -56,6 +56,14 @@ io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,runtimeClass
 io.opentelemetry:opentelemetry-api:1.53.0=jarValidation,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=jarValidation,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=jarValidation,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=jarValidation,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=jarValidation,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=jarValidation,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=jarValidation,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,solrPlatformLibs,testRuntimeClasspath
 io.prometheus:simpleclient:0.16.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/server/gradle.lockfile b/solr/server/gradle.lockfile
index 30301aa9a88..64ba9349292 100644
--- a/solr/server/gradle.lockfile
+++ b/solr/server/gradle.lockfile
@@ -55,6 +55,14 @@ io.netty:netty-transport:4.2.6.Final=jarValidation,runtimeClasspath,solrCore
 io.opentelemetry:opentelemetry-api:1.53.0=jarValidation,runtimeClasspath,solrCore
 io.opentelemetry:opentelemetry-common:1.53.0=jarValidation,runtimeClasspath,solrCore
 io.opentelemetry:opentelemetry-context:1.53.0=jarValidation,runtimeClasspath,solrCore
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=jarValidation,runtimeClasspath,solrCore
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=jarValidation,runtimeClasspath,solrCore
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=jarValidation,runtimeClasspath,solrCore
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=jarValidation,runtimeClasspath,solrCore
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,runtimeClasspath,solrCore
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,runtimeClasspath,solrCore
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,runtimeClasspath,solrCore
+io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,runtimeClasspath,solrCore
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,runtimeClasspath,solrCore
 io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,runtimeClasspath,solrCore
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,runtimeClasspath,solrCore
diff --git a/solr/solr-ref-guide/gradle.lockfile b/solr/solr-ref-guide/gradle.lockfile
index da72ace20fd..633346d95ec 100644
--- a/solr/solr-ref-guide/gradle.lockfile
+++ b/solr/solr-ref-guide/gradle.lockfile
@@ -55,6 +55,14 @@ io.netty:netty-transport:4.2.6.Final=testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-api:1.53.0=testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk:1.53.0=testRuntimeClasspath
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=testRuntimeClasspath
 io.prometheus:prometheus-metrics-model:1.1.0=testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=testRuntimeClasspath
diff --git a/solr/solrj-streaming/gradle.lockfile b/solr/solrj-streaming/gradle.lockfile
index f06a94b212f..4e558d1d9b8 100644
--- a/solr/solrj-streaming/gradle.lockfile
+++ b/solr/solrj-streaming/gradle.lockfile
@@ -65,6 +65,14 @@ io.netty:netty-transport:4.2.6.Final=jarValidation,testCompileClasspath,testRunt
 io.opentelemetry:opentelemetry-api:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,testRuntimeClasspath
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,testRuntimeClasspath
 io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,testRuntimeClasspath
diff --git a/solr/solrj-zookeeper/gradle.lockfile b/solr/solrj-zookeeper/gradle.lockfile
index 8670d8252a7..1d8f75cde5a 100644
--- a/solr/solrj-zookeeper/gradle.lockfile
+++ b/solr/solrj-zookeeper/gradle.lockfile
@@ -70,6 +70,14 @@ io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,permitTestUs
 io.opentelemetry:opentelemetry-api:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,testRuntimeClasspath
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,testRuntimeClasspath
 io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,testRuntimeClasspath
diff --git a/solr/solrj/gradle.lockfile b/solr/solrj/gradle.lockfile
index 55b9900e353..904c15c7034 100644
--- a/solr/solrj/gradle.lockfile
+++ b/solr/solrj/gradle.lockfile
@@ -65,6 +65,14 @@ io.netty:netty-transport:4.2.6.Final=jarValidation,testCompileClasspath,testRunt
 io.opentelemetry:opentelemetry-api:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,testRuntimeClasspath
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,testRuntimeClasspath
 io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,testRuntimeClasspath
diff --git a/solr/test-framework/gradle.lockfile b/solr/test-framework/gradle.lockfile
index c665cd9707b..7c581367523 100644
--- a/solr/test-framework/gradle.lockfile
+++ b/solr/test-framework/gradle.lockfile
@@ -57,6 +57,14 @@ io.netty:netty-transport:4.2.6.Final=apiHelper,compileClasspath,jarValidation,ru
 io.opentelemetry:opentelemetry-api:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk:1.53.0=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
 io.prometheus:prometheus-metrics-model:1.1.0=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
diff --git a/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java b/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java
index c15e4245f0f..4a894521e24 100644
--- a/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java
+++ b/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java
@@ -78,11 +78,10 @@ import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.common.util.SolrNamedThreadFactory;
 import org.apache.solr.common.util.TimeSource;
 import org.apache.solr.core.CoreContainer;
-import org.apache.solr.core.TracerConfigurator;
+import org.apache.solr.core.OpenTelemetryConfigurator;
 import org.apache.solr.embedded.JettyConfig;
 import org.apache.solr.embedded.JettySolrRunner;
 import org.apache.solr.util.TimeOut;
-import org.apache.solr.util.tracing.SimplePropagator;
 import org.apache.solr.util.tracing.TraceUtils;
 import org.apache.zookeeper.KeeperException;
 import org.eclipse.jetty.ee10.servlet.ServletHolder;
@@ -1169,9 +1168,7 @@ public class MiniSolrCloudCluster {
     public MiniSolrCloudCluster build() throws Exception {
       System.setProperty("solr.cloud.overseer.enabled", Boolean.toString(overseerEnabled));
 
-      // eager init to prevent OTEL init races caused by test setup
-      if (!disableTraceIdGeneration && TracerConfigurator.TRACE_ID_GEN_ENABLED) {
-        SimplePropagator.load();
+      if (!disableTraceIdGeneration && OpenTelemetryConfigurator.TRACE_ID_GEN_ENABLED) {
         injectRandomRecordingFlag();
       }
 
diff --git a/solr/webapp/gradle.lockfile b/solr/webapp/gradle.lockfile
index 7fd09d19e3c..5c45b55f9c7 100644
--- a/solr/webapp/gradle.lockfile
+++ b/solr/webapp/gradle.lockfile
@@ -55,6 +55,14 @@ io.netty:netty-transport:4.2.6.Final=permitUnusedDeclared,solrCore
 io.opentelemetry:opentelemetry-api:1.53.0=permitUnusedDeclared,solrCore
 io.opentelemetry:opentelemetry-common:1.53.0=permitUnusedDeclared,solrCore
 io.opentelemetry:opentelemetry-context:1.53.0=permitUnusedDeclared,solrCore
+io.opentelemetry:opentelemetry-exporter-common:1.50.0=permitUnusedDeclared,solrCore
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=permitUnusedDeclared,solrCore
+io.opentelemetry:opentelemetry-sdk-common:1.53.0=permitUnusedDeclared,solrCore
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=permitUnusedDeclared,solrCore
+io.opentelemetry:opentelemetry-sdk-logs:1.53.0=permitUnusedDeclared,solrCore
+io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=permitUnusedDeclared,solrCore
+io.opentelemetry:opentelemetry-sdk-trace:1.53.0=permitUnusedDeclared,solrCore
+io.opentelemetry:opentelemetry-sdk:1.53.0=permitUnusedDeclared,solrCore
 io.prometheus:prometheus-metrics-exposition-formats:1.1.0=permitUnusedDeclared,solrCore
 io.prometheus:prometheus-metrics-model:1.1.0=permitUnusedDeclared,solrCore
 io.sgr:s2-geometry-library-java:1.0.0=permitUnusedDeclared,solrCore


(solr) 26/33: SOLR-17806: Migrate thread pool metrics to OTEL (#3519)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 63f8fccf2599f1f637c66d116a66f92aaee9aadb
Author: Jude Muriithi <90...@users.noreply.github.com>
AuthorDate: Wed Sep 17 16:16:33 2025 -0400

    SOLR-17806: Migrate thread pool metrics to OTEL (#3519)
    
    * Stub out OtelInstrumentedeExecutorService
    
    * Finish OTEL ExecutorService implementation and replace MetricsUtil calls
    
    * Add OtelUnits to timers
    
    * spotlessApply
    
    * feedback
    
    * Use IOUtils.closeQuietly
    
    * Check in partial tests
    
    * Add pool metrics testing
    
    * use utility method in tests
    
    ---------
    
    Co-authored-by: jmuriithi3 <jm...@bloomberg.net>
---
 .../java/org/apache/solr/core/CoreContainer.java   |   7 +-
 .../solr/handler/admin/CoreAdminHandler.java       |  16 +-
 .../handler/component/HttpShardHandlerFactory.java |   6 +-
 .../org/apache/solr/update/UpdateShardHandler.java |  12 +-
 .../org/apache/solr/util/stats/MetricUtils.java    |  23 +-
 .../stats/OtelInstrumentedExecutorService.java     | 302 +++++++++++++++++++++
 .../stats/OtelInstrumentedExecutorServiceTest.java | 238 ++++++++++++++++
 .../org/apache/solr/util/SolrMetricTestUtils.java  |  10 +
 8 files changed, 572 insertions(+), 42 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index 808ee59f6bb..2da7fc9ef91 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -1040,10 +1040,9 @@ public class CoreContainer {
             ExecutorUtil.newMDCAwareFixedThreadPool(
                 cfg.getCoreLoadThreadCount(isZooKeeperAware()),
                 new SolrNamedThreadFactory("coreLoadExecutor")),
-            null,
-            metricManager.registry(SolrMetricManager.getRegistryName(SolrInfoBean.Group.node)),
-            SolrMetricManager.mkName(
-                "coreLoadExecutor", SolrInfoBean.Category.CONTAINER.toString(), "threadPool"));
+            solrMetricsContext,
+            SolrInfoBean.Category.CONTAINER,
+            "coreLoadExecutor");
 
     coreSorter =
         loader.newInstance(
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java
index b0237978593..41306919693 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java
@@ -74,7 +74,6 @@ import org.apache.solr.handler.admin.api.SplitCoreAPI;
 import org.apache.solr.handler.admin.api.SwapCores;
 import org.apache.solr.handler.admin.api.UnloadCore;
 import org.apache.solr.logging.MDCLoggingContext;
-import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.metrics.SolrMetricsContext;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
@@ -128,7 +127,6 @@ public class CoreAdminHandler extends RequestHandlerBase implements PermissionNa
             + "it is a special Handler configured directly by the RequestDispatcher");
   }
 
-  // TODO SOLR-17458: Migrate to Otel
   @Override
   public void initializeMetrics(
       SolrMetricsContext parentContext, Attributes attributes, String scope) {
@@ -136,18 +134,16 @@ public class CoreAdminHandler extends RequestHandlerBase implements PermissionNa
     coreAdminAsyncTracker.standardExecutor =
         MetricUtils.instrumentedExecutorService(
             coreAdminAsyncTracker.standardExecutor,
-            this,
-            solrMetricsContext.getMetricRegistry(),
-            SolrMetricManager.mkName(
-                "parallelCoreAdminExecutor", getCategory().name(), scope, "threadPool"));
+            solrMetricsContext,
+            getCategory(),
+            "parallelCoreAdminExecutor");
 
     coreAdminAsyncTracker.expensiveExecutor =
         MetricUtils.instrumentedExecutorService(
             coreAdminAsyncTracker.expensiveExecutor,
-            this,
-            solrMetricsContext.getMetricRegistry(),
-            SolrMetricManager.mkName(
-                "parallelCoreExpensiveAdminExecutor", getCategory().name(), scope, "threadPool"));
+            solrMetricsContext,
+            getCategory(),
+            "parallelCoreExpensiveAdminExecutor");
   }
 
   @Override
diff --git a/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java b/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java
index dd3630ea203..87c18486ba7 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java
@@ -444,13 +444,9 @@ public class HttpShardHandlerFactory extends ShardHandlerFactory
       SolrMetricsContext parentContext, Attributes attributes, String scope) {
     solrMetricsContext = parentContext.getChildContext(this);
     String expandedScope = SolrMetricManager.mkName(scope, SolrInfoBean.Category.QUERY.name());
-    // TODO SOLR-17458: Add Otel
     httpListenerFactory.initializeMetrics(solrMetricsContext, Attributes.empty(), expandedScope);
     commExecutor =
         MetricUtils.instrumentedExecutorService(
-            commExecutor,
-            null,
-            solrMetricsContext.getMetricRegistry(),
-            SolrMetricManager.mkName("httpShardExecutor", expandedScope, "threadPool"));
+            commExecutor, solrMetricsContext, SolrInfoBean.Category.QUERY, "httpShardExecutor");
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/update/UpdateShardHandler.java b/solr/core/src/java/org/apache/solr/update/UpdateShardHandler.java
index 157fc1513b0..c0d4715154a 100644
--- a/solr/core/src/java/org/apache/solr/update/UpdateShardHandler.java
+++ b/solr/core/src/java/org/apache/solr/update/UpdateShardHandler.java
@@ -163,26 +163,18 @@ public class UpdateShardHandler implements SolrInfoBean {
     return this.getClass().getName();
   }
 
-  // TODO SOLR-17458: Add Otel
   @Override
   public void initializeMetrics(
       SolrMetricsContext parentContext, Attributes attributes, String scope) {
     solrMetricsContext = parentContext.getChildContext(this);
     String expandedScope = SolrMetricManager.mkName(scope, getCategory().name());
-    // TODO SOLR-17458: Add Otel
     trackHttpSolrMetrics.initializeMetrics(solrMetricsContext, Attributes.empty(), expandedScope);
     updateExecutor =
         MetricUtils.instrumentedExecutorService(
-            updateExecutor,
-            this,
-            solrMetricsContext.getMetricRegistry(),
-            SolrMetricManager.mkName("updateOnlyExecutor", expandedScope, "threadPool"));
+            updateExecutor, solrMetricsContext, getCategory(), "updateOnlyExecutor");
     recoveryExecutor =
         MetricUtils.instrumentedExecutorService(
-            recoveryExecutor,
-            this,
-            solrMetricsContext.getMetricRegistry(),
-            SolrMetricManager.mkName("recoveryExecutor", expandedScope, "threadPool"));
+            recoveryExecutor, solrMetricsContext, getCategory(), "recoveryExecutor");
   }
 
   @Override
diff --git a/solr/core/src/java/org/apache/solr/util/stats/MetricUtils.java b/solr/core/src/java/org/apache/solr/util/stats/MetricUtils.java
index 9cff065c06f..3447e3e6f0a 100644
--- a/solr/core/src/java/org/apache/solr/util/stats/MetricUtils.java
+++ b/solr/core/src/java/org/apache/solr/util/stats/MetricUtils.java
@@ -19,7 +19,6 @@ package org.apache.solr.util.stats;
 import com.codahale.metrics.Counter;
 import com.codahale.metrics.Gauge;
 import com.codahale.metrics.Histogram;
-import com.codahale.metrics.InstrumentedExecutorService;
 import com.codahale.metrics.Meter;
 import com.codahale.metrics.Metric;
 import com.codahale.metrics.MetricFilter;
@@ -56,6 +55,7 @@ import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.metrics.AggregateMetric;
 import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.metrics.SolrMetricsContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -744,18 +744,6 @@ public class MetricUtils {
     }
   }
 
-  /** Returns an instrumented wrapper over the given executor service. */
-  public static ExecutorService instrumentedExecutorService(
-      ExecutorService delegate, SolrInfoBean info, MetricRegistry metricRegistry, String scope) {
-    if (info != null && info.getSolrMetricsContext() != null) {
-      info.getSolrMetricsContext().registerMetricName(MetricRegistry.name(scope, "submitted"));
-      info.getSolrMetricsContext().registerMetricName(MetricRegistry.name(scope, "running"));
-      info.getSolrMetricsContext().registerMetricName(MetricRegistry.name(scope, "completed"));
-      info.getSolrMetricsContext().registerMetricName(MetricRegistry.name(scope, "duration"));
-    }
-    return new InstrumentedExecutorService(delegate, metricRegistry, scope);
-  }
-
   /**
    * Creates a set of metrics (gauges) that correspond to available bean properties for the provided
    * MXBean.
@@ -829,6 +817,15 @@ public class MetricUtils {
         "com.ibm.lang.management.OperatingSystemMXBean"
       };
 
+  /** Returns an instrumented wrapper over the given executor service. */
+  public static ExecutorService instrumentedExecutorService(
+      ExecutorService delegate,
+      SolrMetricsContext ctx,
+      SolrInfoBean.Category category,
+      String name) {
+    return new OtelInstrumentedExecutorService(delegate, ctx, category, name);
+  }
+
   /**
    * Creates a set of metrics (gauges) that correspond to available bean properties for the provided
    * MXBean.
diff --git a/solr/core/src/java/org/apache/solr/util/stats/OtelInstrumentedExecutorService.java b/solr/core/src/java/org/apache/solr/util/stats/OtelInstrumentedExecutorService.java
new file mode 100644
index 00000000000..2823826d4b5
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/util/stats/OtelInstrumentedExecutorService.java
@@ -0,0 +1,302 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.util.stats;
+
+import static org.apache.solr.metrics.SolrMetricProducer.CATEGORY_ATTR;
+import static org.apache.solr.metrics.SolrMetricProducer.TYPE_ATTR;
+
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.Attributes;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.apache.solr.common.util.IOUtils;
+import org.apache.solr.core.SolrInfoBean;
+import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.OtelUnit;
+import org.apache.solr.metrics.otel.instruments.AttributedLongCounter;
+import org.apache.solr.metrics.otel.instruments.AttributedLongTimer;
+import org.apache.solr.metrics.otel.instruments.AttributedLongTimer.MetricTimer;
+import org.apache.solr.metrics.otel.instruments.AttributedLongUpDownCounter;
+
+/**
+ * OTEL instrumentation wrapper around {@link ExecutorService}. Based on {@link
+ * com.codahale.metrics.InstrumentedExecutorService}.
+ */
+public class OtelInstrumentedExecutorService implements ExecutorService {
+  public static final AttributeKey<String> EXECUTOR_NAME_ATTR =
+      AttributeKey.stringKey("executor_name");
+
+  private final ExecutorService delegate;
+  private final String executorName;
+  private final List<AutoCloseable> observableMetrics;
+  private final AttributedLongCounter submitted;
+  private final AttributedLongUpDownCounter running;
+  private final AttributedLongCounter completed;
+  private final AttributedLongTimer idle;
+  private final AttributedLongTimer duration;
+
+  public OtelInstrumentedExecutorService(
+      ExecutorService delegate,
+      SolrMetricsContext ctx,
+      SolrInfoBean.Category category,
+      String executorName) {
+    this.delegate = delegate;
+    this.executorName = executorName;
+    this.observableMetrics = new ArrayList<>();
+
+    Attributes attrs =
+        Attributes.builder()
+            .put(CATEGORY_ATTR, category.toString())
+            .put(EXECUTOR_NAME_ATTR, executorName)
+            .build();
+
+    // Each metric type needs a separate name to avoid obscuring other types
+    var executorTaskCounter =
+        ctx.longCounter("solr_executor_tasks", "Number of ExecutorService tasks");
+    this.submitted =
+        new AttributedLongCounter(
+            executorTaskCounter, attrs.toBuilder().put(TYPE_ATTR, "submitted").build());
+    this.completed =
+        new AttributedLongCounter(
+            executorTaskCounter, attrs.toBuilder().put(TYPE_ATTR, "completed").build());
+    this.running =
+        new AttributedLongUpDownCounter(
+            ctx.longUpDownCounter(
+                "solr_executor_tasks_running", "Number of running ExecutorService tasks"),
+            attrs);
+    var executorTaskTimer =
+        ctx.longHistogram(
+            "solr_executor_task_times", "Timing of ExecutorService tasks", OtelUnit.MILLISECONDS);
+    this.idle =
+        new AttributedLongTimer(
+            executorTaskTimer, attrs.toBuilder().put(TYPE_ATTR, "idle").build());
+    this.duration =
+        new AttributedLongTimer(
+            executorTaskTimer, attrs.toBuilder().put(TYPE_ATTR, "duration").build());
+
+    if (delegate instanceof ThreadPoolExecutor) {
+      ThreadPoolExecutor threadPool = (ThreadPoolExecutor) delegate;
+      observableMetrics.add(
+          ctx.observableLongGauge(
+              "solr_executor_thread_pool_size",
+              "Thread pool size",
+              measurement -> {
+                measurement.record(
+                    threadPool.getPoolSize(), attrs.toBuilder().put(TYPE_ATTR, "size").build());
+                measurement.record(
+                    threadPool.getCorePoolSize(), attrs.toBuilder().put(TYPE_ATTR, "core").build());
+                measurement.record(
+                    threadPool.getMaximumPoolSize(),
+                    attrs.toBuilder().put(TYPE_ATTR, "max").build());
+              }));
+
+      final BlockingQueue<Runnable> taskQueue = threadPool.getQueue();
+      observableMetrics.add(
+          ctx.observableLongGauge(
+              "solr_executor_thread_pool_tasks",
+              "Thread pool task counts",
+              measurement -> {
+                measurement.record(
+                    threadPool.getActiveCount(),
+                    attrs.toBuilder().put(TYPE_ATTR, "active").build());
+                measurement.record(
+                    threadPool.getCompletedTaskCount(),
+                    attrs.toBuilder().put(TYPE_ATTR, "completed").build());
+                measurement.record(
+                    taskQueue.size(), attrs.toBuilder().put(TYPE_ATTR, "queued").build());
+                measurement.record(
+                    taskQueue.remainingCapacity(),
+                    attrs.toBuilder().put(TYPE_ATTR, "capacity").build());
+              }));
+    } else if (delegate instanceof ForkJoinPool) {
+      ForkJoinPool forkJoinPool = (ForkJoinPool) delegate;
+      observableMetrics.add(
+          ctx.observableLongGauge(
+              "solr_executor_fork_join_pool_tasks",
+              "Fork join pool task counts",
+              measurement -> {
+                measurement.record(
+                    forkJoinPool.getStealCount(),
+                    attrs.toBuilder().put(TYPE_ATTR, "stolen").build());
+                measurement.record(
+                    forkJoinPool.getQueuedTaskCount(),
+                    attrs.toBuilder().put(TYPE_ATTR, "queued").build());
+              }));
+      observableMetrics.add(
+          ctx.observableLongGauge(
+              "solr_executor_fork_join_pool_threads",
+              "Fork join pool thread counts",
+              measurement -> {
+                measurement.record(
+                    forkJoinPool.getActiveThreadCount(),
+                    attrs.toBuilder().put(TYPE_ATTR, "active").build());
+                measurement.record(
+                    forkJoinPool.getRunningThreadCount(),
+                    attrs.toBuilder().put(TYPE_ATTR, "running").build());
+              }));
+    }
+  }
+
+  @Override
+  public void execute(Runnable task) {
+    submitted.inc();
+    delegate.execute(new InstrumentedRunnable(task));
+  }
+
+  @Override
+  public Future<?> submit(Runnable task) {
+    submitted.inc();
+    return delegate.submit(new InstrumentedRunnable(task));
+  }
+
+  @Override
+  public <T> Future<T> submit(Runnable task, T result) {
+    submitted.inc();
+    return delegate.submit(new InstrumentedRunnable(task), result);
+  }
+
+  @Override
+  public <T> Future<T> submit(Callable<T> task) {
+    submitted.inc();
+    return delegate.submit(new InstrumentedCallable<>(task));
+  }
+
+  @Override
+  public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
+      throws InterruptedException {
+    submitted.add(Long.valueOf(tasks.size()));
+    return delegate.invokeAll(instrument(tasks));
+  }
+
+  @Override
+  public <T> List<Future<T>> invokeAll(
+      Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+      throws InterruptedException {
+    submitted.add(Long.valueOf(tasks.size()));
+    return delegate.invokeAll(instrument(tasks), timeout, unit);
+  }
+
+  @Override
+  public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
+      throws InterruptedException, ExecutionException {
+    submitted.add(Long.valueOf(tasks.size()));
+    return delegate.invokeAny(instrument(tasks));
+  }
+
+  @Override
+  public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+      throws InterruptedException, ExecutionException, TimeoutException {
+    submitted.add(Long.valueOf(tasks.size()));
+    return delegate.invokeAny(instrument(tasks), timeout, unit);
+  }
+
+  @Override
+  public void shutdown() {
+    delegate.shutdown();
+    observableMetrics.stream().forEach(IOUtils::closeQuietly);
+  }
+
+  @Override
+  public List<Runnable> shutdownNow() {
+    List<Runnable> tasks = delegate.shutdownNow();
+    observableMetrics.stream().forEach(IOUtils::closeQuietly);
+    return tasks;
+  }
+
+  @Override
+  public boolean isShutdown() {
+    return delegate.isShutdown();
+  }
+
+  @Override
+  public boolean isTerminated() {
+    return delegate.isTerminated();
+  }
+
+  @Override
+  public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+    return delegate.awaitTermination(timeout, unit);
+  }
+
+  private <T> List<InstrumentedCallable<T>> instrument(Collection<? extends Callable<T>> tasks) {
+    List<InstrumentedCallable<T>> instrumented = new ArrayList<>(tasks.size());
+    for (Callable<T> task : tasks) {
+      instrumented.add(new InstrumentedCallable<>(task));
+    }
+    return instrumented;
+  }
+
+  private class InstrumentedRunnable implements Runnable {
+    private final Runnable task;
+    private final MetricTimer idleTimer;
+
+    InstrumentedRunnable(Runnable task) {
+      this.task = task;
+      this.idleTimer = idle.start();
+    }
+
+    @Override
+    public void run() {
+      idleTimer.stop();
+      running.inc();
+
+      MetricTimer durationTimer = duration.start();
+      try {
+        task.run();
+      } finally {
+        durationTimer.stop();
+        running.dec();
+        completed.inc();
+      }
+    }
+  }
+
+  private class InstrumentedCallable<T> implements Callable<T> {
+    private final Callable<T> task;
+    private final MetricTimer idleTimer;
+
+    InstrumentedCallable(Callable<T> task) {
+      this.task = task;
+      this.idleTimer = idle.start();
+    }
+
+    @Override
+    public T call() throws Exception {
+      idleTimer.stop();
+      running.inc();
+
+      MetricTimer durationTimer = duration.start();
+      try {
+        return task.call();
+      } finally {
+        durationTimer.stop();
+        running.dec();
+        completed.inc();
+      }
+    }
+  }
+}
diff --git a/solr/core/src/test/org/apache/solr/util/stats/OtelInstrumentedExecutorServiceTest.java b/solr/core/src/test/org/apache/solr/util/stats/OtelInstrumentedExecutorServiceTest.java
new file mode 100644
index 00000000000..2d7ad162e4f
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/util/stats/OtelInstrumentedExecutorServiceTest.java
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.util.stats;
+
+import static com.carrotsearch.randomizedtesting.RandomizedTest.randomIntBetween;
+import static org.apache.solr.metrics.SolrMetricProducer.TYPE_ATTR;
+
+import io.prometheus.metrics.model.snapshots.CounterSnapshot;
+import io.prometheus.metrics.model.snapshots.CounterSnapshot.CounterDataPointSnapshot;
+import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
+import io.prometheus.metrics.model.snapshots.GaugeSnapshot.GaugeDataPointSnapshot;
+import io.prometheus.metrics.model.snapshots.HistogramSnapshot;
+import io.prometheus.metrics.model.snapshots.HistogramSnapshot.HistogramDataPointSnapshot;
+import io.prometheus.metrics.model.snapshots.MetricSnapshots;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import org.apache.solr.SolrTestCase;
+import org.apache.solr.core.SolrInfoBean;
+import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.util.SolrMetricTestUtils;
+import org.junit.Before;
+import org.junit.Test;
+
+public class OtelInstrumentedExecutorServiceTest extends SolrTestCase {
+  public static final int PARALLELISM = 10;
+  public static long EXEC_TIMEOUT = 1;
+  public static TimeUnit EXEC_TIMEOUT_UNITS = TimeUnit.SECONDS;
+  public static final String REGISTRY_NAME = "solr-test-otel-registry";
+  public static final String TAG_NAME = "solr-test-otel-tag";
+  public static final double DELTA = 1E-8;
+
+  public static SolrMetricsContext metricsContext;
+
+  @Before
+  public void setUpMetrics() {
+    metricsContext = new SolrMetricsContext(new SolrMetricManager(), REGISTRY_NAME, TAG_NAME);
+  }
+
+  @Test
+  public void taskCount() throws InterruptedException {
+    try (var exec = testExecutor("taskCount", Executors.newFixedThreadPool(PARALLELISM))) {
+      final int numTasks = 225;
+      for (int i = 0; i < numTasks; ++i) {
+        exec.submit(() -> {});
+      }
+      exec.awaitTermination(EXEC_TIMEOUT, EXEC_TIMEOUT_UNITS);
+
+      MetricSnapshots metrics =
+          metricsContext.getMetricManager().getPrometheusMetricReader(REGISTRY_NAME).collect();
+      GaugeSnapshot tasksRunning =
+          SolrMetricTestUtils.getMetricSnapshot(
+              GaugeSnapshot.class, metrics, "solr_executor_tasks_running");
+      CounterSnapshot taskCounters =
+          SolrMetricTestUtils.getMetricSnapshot(
+              CounterSnapshot.class, metrics, "solr_executor_tasks");
+
+      GaugeDataPointSnapshot runningTasks = tasksRunning.getDataPoints().getFirst();
+      CounterDataPointSnapshot submittedTasks = getCounterData(taskCounters, "submitted");
+      CounterDataPointSnapshot completedTasks = getCounterData(taskCounters, "completed");
+
+      assertEquals(0.0, runningTasks.getValue(), DELTA);
+      assertEquals(numTasks, submittedTasks.getValue(), DELTA);
+      assertEquals(numTasks, completedTasks.getValue(), DELTA);
+    }
+  }
+
+  @Test
+  public void taskRandomCount() throws InterruptedException {
+    try (var exec = testExecutor("taskRandomCount", Executors.newFixedThreadPool(PARALLELISM))) {
+      final int numTasks = randomIntBetween(1, 500);
+      for (int i = 0; i < numTasks; ++i) {
+        exec.submit(() -> {});
+      }
+      exec.awaitTermination(EXEC_TIMEOUT, EXEC_TIMEOUT_UNITS);
+
+      MetricSnapshots metrics =
+          metricsContext.getMetricManager().getPrometheusMetricReader(REGISTRY_NAME).collect();
+      GaugeSnapshot tasksRunning =
+          SolrMetricTestUtils.getMetricSnapshot(
+              GaugeSnapshot.class, metrics, "solr_executor_tasks_running");
+      CounterSnapshot taskCounters =
+          SolrMetricTestUtils.getMetricSnapshot(
+              CounterSnapshot.class, metrics, "solr_executor_tasks");
+
+      GaugeDataPointSnapshot runningTasks = tasksRunning.getDataPoints().getFirst();
+      CounterDataPointSnapshot submittedTasks = getCounterData(taskCounters, "submitted");
+      CounterDataPointSnapshot completedTasks = getCounterData(taskCounters, "completed");
+
+      assertEquals(0.0, runningTasks.getValue(), DELTA);
+      assertEquals(numTasks, submittedTasks.getValue(), DELTA);
+      assertEquals(numTasks, completedTasks.getValue(), DELTA);
+    }
+  }
+
+  @Test
+  public void taskTimers() throws InterruptedException {
+    try (var exec = testExecutor("taskTimers", Executors.newFixedThreadPool(PARALLELISM))) {
+      final long durationMs = 200;
+      final double durationDeltaMs = 10.0;
+      exec.submit(
+          () -> {
+            try {
+              Thread.sleep(durationMs);
+            } catch (InterruptedException e) {
+            }
+          });
+      exec.awaitTermination(EXEC_TIMEOUT, EXEC_TIMEOUT_UNITS);
+
+      MetricSnapshots metrics =
+          metricsContext.getMetricManager().getPrometheusMetricReader(REGISTRY_NAME).collect();
+      HistogramSnapshot taskTimers =
+          metrics.stream()
+              .filter(
+                  m -> m.getMetadata().getPrometheusName().startsWith("solr_executor_task_times"))
+              .findFirst()
+              .map(HistogramSnapshot.class::cast)
+              .get();
+
+      HistogramDataPointSnapshot idleTimer =
+          taskTimers.getDataPoints().stream()
+              .filter(data -> data.getLabels().get(TYPE_ATTR.toString()).equals("idle"))
+              .findFirst()
+              .get();
+      HistogramDataPointSnapshot durationTimer =
+          taskTimers.getDataPoints().stream()
+              .filter(data -> data.getLabels().get(TYPE_ATTR.toString()).equals("duration"))
+              .findFirst()
+              .get();
+
+      assertTrue(TimeUnit.SECONDS.toMillis(5) > idleTimer.getSum());
+      assertEquals(durationMs, durationTimer.getSum(), durationDeltaMs);
+    }
+  }
+
+  @Test
+  public void threadPoolTasks() throws InterruptedException {
+    try (var exec = testExecutor("threadPoolTasks", Executors.newFixedThreadPool(PARALLELISM))) {
+      final int numTasks = 225;
+      for (int i = 0; i < numTasks; ++i) {
+        exec.submit(() -> {});
+      }
+      exec.awaitTermination(EXEC_TIMEOUT, EXEC_TIMEOUT_UNITS);
+
+      MetricSnapshots metrics =
+          metricsContext.getMetricManager().getPrometheusMetricReader(REGISTRY_NAME).collect();
+      GaugeSnapshot sizeGauges =
+          SolrMetricTestUtils.getMetricSnapshot(
+              GaugeSnapshot.class, metrics, "solr_executor_thread_pool_size");
+      GaugeSnapshot taskGauges =
+          SolrMetricTestUtils.getMetricSnapshot(
+              GaugeSnapshot.class, metrics, "solr_executor_thread_pool_tasks");
+
+      GaugeDataPointSnapshot poolSize = getGaugeData(sizeGauges, "size");
+      GaugeDataPointSnapshot corePoolSize = getGaugeData(sizeGauges, "core");
+      GaugeDataPointSnapshot maxPoolSize = getGaugeData(sizeGauges, "max");
+
+      GaugeDataPointSnapshot activeTasks = getGaugeData(taskGauges, "active");
+      GaugeDataPointSnapshot completedTasks = getGaugeData(taskGauges, "completed");
+      GaugeDataPointSnapshot queuedTasks = getGaugeData(taskGauges, "queued");
+      GaugeDataPointSnapshot poolCapacity = getGaugeData(taskGauges, "capacity");
+
+      assertEquals(PARALLELISM, poolSize.getValue(), DELTA);
+      assertEquals(PARALLELISM, corePoolSize.getValue(), DELTA);
+      assertEquals(PARALLELISM, maxPoolSize.getValue(), DELTA);
+
+      assertEquals(0.0, activeTasks.getValue(), DELTA);
+      assertEquals(numTasks, completedTasks.getValue(), DELTA);
+      assertEquals(0.0, queuedTasks.getValue(), DELTA);
+      assertEquals(Integer.MAX_VALUE, poolCapacity.getValue(), DELTA);
+    }
+  }
+
+  @Test
+  public void forkJoinPoolTasks() throws InterruptedException {
+    try (var exec = testExecutor("forkJoinPoolTasks", Executors.newWorkStealingPool(PARALLELISM))) {
+      final int numTasks = 225;
+      for (int i = 0; i < numTasks; ++i) {
+        exec.submit(() -> {});
+      }
+      exec.awaitTermination(EXEC_TIMEOUT, EXEC_TIMEOUT_UNITS);
+
+      MetricSnapshots metrics =
+          metricsContext.getMetricManager().getPrometheusMetricReader(REGISTRY_NAME).collect();
+      GaugeSnapshot taskGauges =
+          SolrMetricTestUtils.getMetricSnapshot(
+              GaugeSnapshot.class, metrics, "solr_executor_fork_join_pool_tasks");
+      GaugeSnapshot threadGauges =
+          SolrMetricTestUtils.getMetricSnapshot(
+              GaugeSnapshot.class, metrics, "solr_executor_fork_join_pool_threads");
+      GaugeDataPointSnapshot stolenTasks = getGaugeData(taskGauges, "stolen");
+      GaugeDataPointSnapshot queuedTasks = getGaugeData(taskGauges, "queued");
+
+      GaugeDataPointSnapshot activeThreads = getGaugeData(threadGauges, "active");
+      GaugeDataPointSnapshot runningThreads = getGaugeData(threadGauges, "running");
+
+      assertNotNull(stolenTasks.getValue());
+      assertEquals(0.0, queuedTasks.getValue(), DELTA);
+
+      assertEquals(0.0, activeThreads.getValue(), DELTA);
+      assertEquals(0.0, runningThreads.getValue(), DELTA);
+    }
+  }
+
+  private static ExecutorService testExecutor(String name, ExecutorService exec) {
+    return MetricUtils.instrumentedExecutorService(
+        exec, metricsContext, SolrInfoBean.Category.ADMIN, name);
+  }
+
+  private static CounterDataPointSnapshot getCounterData(CounterSnapshot snapshot, String type) {
+    return snapshot.getDataPoints().stream()
+        .filter(data -> data.getLabels().get(TYPE_ATTR.toString()).equals(type))
+        .findFirst()
+        .get();
+  }
+
+  private static GaugeDataPointSnapshot getGaugeData(GaugeSnapshot snapshot, String type) {
+    return snapshot.getDataPoints().stream()
+        .filter(data -> data.getLabels().get(TYPE_ATTR.toString()).equals(type))
+        .findFirst()
+        .get();
+  }
+}
diff --git a/solr/test-framework/src/java/org/apache/solr/util/SolrMetricTestUtils.java b/solr/test-framework/src/java/org/apache/solr/util/SolrMetricTestUtils.java
index 8fcafc125c9..3531094cfc3 100644
--- a/solr/test-framework/src/java/org/apache/solr/util/SolrMetricTestUtils.java
+++ b/solr/test-framework/src/java/org/apache/solr/util/SolrMetricTestUtils.java
@@ -25,6 +25,7 @@ import io.prometheus.metrics.model.snapshots.DataPointSnapshot;
 import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
 import io.prometheus.metrics.model.snapshots.HistogramSnapshot;
 import io.prometheus.metrics.model.snapshots.Labels;
+import io.prometheus.metrics.model.snapshots.MetricSnapshot;
 import io.prometheus.metrics.model.snapshots.MetricSnapshots;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -210,6 +211,15 @@ public final class SolrMetricTestUtils {
     return container.getMetricManager().getPrometheusMetricReader(registryName);
   }
 
+  public static <S extends MetricSnapshot> S getMetricSnapshot(
+      Class<S> snapshotClass, MetricSnapshots metrics, String name) {
+    return metrics.stream()
+        .filter(m -> m.getMetadata().getPrometheusName().equals(name))
+        .map(snapshotClass::cast)
+        .findFirst()
+        .get();
+  }
+
   private static <T> T getDatapoint(
       SolrCore core, String metricName, Labels labels, Class<T> snapshotType) {
     var reader = getPrometheusMetricReader(core);


(solr) 10/33: SOLR-17818: Expose Open Metrics 1.0 format from /admin/metrics and exemplar support (#3427)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 8e02bfc4e87120e0a4a297b08a863245d803d151
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Wed Aug 6 10:35:29 2025 -0400

    SOLR-17818: Expose Open Metrics 1.0 format from /admin/metrics and exemplar support (#3427)
    
    * Add exemplar support
    
    * change open metrics to wt=prometheus
    
    * Open metrics format based on accept header
---
 .../src/java/org/apache/solr/core/SolrCore.java    |  2 +
 .../apache/solr/handler/admin/MetricsHandler.java  |  7 +-
 .../org/apache/solr/metrics/SolrMetricManager.java | 11 +--
 .../solr/response/PrometheusResponseWriter.java    | 34 +++++++-
 .../response/TestPrometheusResponseWriter.java     | 62 ++++++++++++++
 .../solr/opentelemetry/TestDistributedTracing.java |  2 +-
 .../solr/opentelemetry/TestMetricExemplars.java    | 95 ++++++++++++++++++++++
 7 files changed, 203 insertions(+), 10 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/core/SolrCore.java b/solr/core/src/java/org/apache/solr/core/SolrCore.java
index 825e8b11094..bfe73060d22 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrCore.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java
@@ -17,6 +17,7 @@
 package org.apache.solr.core;
 
 import static org.apache.solr.common.params.CommonParams.PATH;
+import static org.apache.solr.handler.admin.MetricsHandler.OPEN_METRICS_WT;
 import static org.apache.solr.handler.admin.MetricsHandler.PROMETHEUS_METRICS_WT;
 import static org.apache.solr.metrics.SolrCoreMetricManager.COLLECTION_ATTR;
 import static org.apache.solr.metrics.SolrCoreMetricManager.CORE_ATTR;
@@ -3102,6 +3103,7 @@ public class SolrCore implements SolrInfoBean, Closeable {
     m.put("schema.xml", new SchemaXmlResponseWriter());
     m.put("smile", new SmileResponseWriter());
     m.put(PROMETHEUS_METRICS_WT, new PrometheusResponseWriter());
+    m.put(OPEN_METRICS_WT, new PrometheusResponseWriter());
     m.put(ReplicationAPIBase.FILE_STREAM, getFileStreamWriter());
     DEFAULT_RESPONSE_WRITERS = Collections.unmodifiableMap(m);
     try {
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java
index ba7f6bba34b..f55c0c4b177 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java
@@ -69,7 +69,9 @@ public class MetricsHandler extends RequestHandlerBase implements PermissionName
   public static final String KEY_PARAM = "key";
   public static final String EXPR_PARAM = "expr";
   public static final String TYPE_PARAM = "type";
+  // NOCOMMIT: This wt=prometheus will be removed as it will become the default for /admin/metrics
   public static final String PROMETHEUS_METRICS_WT = "prometheus";
+  public static final String OPEN_METRICS_WT = "openmetrics";
 
   public static final String ALL = "all";
 
@@ -126,8 +128,9 @@ public class MetricsHandler extends RequestHandlerBase implements PermissionName
       return;
     }
 
-    // TODO SOLR-17458: Make this the default option after dropwizard removal
-    if (PROMETHEUS_METRICS_WT.equals(params.get(CommonParams.WT))) {
+    // NOCOMMIT SOLR-17458: Make this the default option after dropwizard removal
+    if (PROMETHEUS_METRICS_WT.equals(params.get(CommonParams.WT))
+        || OPEN_METRICS_WT.equals(params.get(CommonParams.WT))) {
       consumer.accept("metrics", metricManager.getPrometheusMetricReaders());
       return;
     }
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
index 7056a457861..0e6a1db637c 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
@@ -52,6 +52,8 @@ import io.opentelemetry.api.metrics.ObservableLongMeasurement;
 import io.opentelemetry.api.metrics.ObservableLongUpDownCounter;
 import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
 import io.opentelemetry.sdk.metrics.SdkMeterProvider;
+import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil;
+import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
@@ -725,9 +727,7 @@ public class SolrMetricManager {
   }
 
   /**
-   * Get (or create if not present) a named {@link SdkMeterProvider} under {@link
-   * MeterProviderAndReaders}. This also registers a corresponding {@link PrometheusMetricReader}
-   * Dropwizards's {@link SolrMetricManager#registry(String)} equivalent
+   * Get (or create if not present) a named {@link SdkMeterProvider}.
    *
    * @param providerName name of the meter provider and prometheus metric reader
    * @return existing or newly created meter provider
@@ -741,8 +741,9 @@ public class SolrMetricManager {
               var reader = new PrometheusMetricReader(true, null);
               // NOCOMMIT: We need to add a Periodic Metric Reader here if we want to push with OTLP
               // with an exporter
-              var provider = SdkMeterProvider.builder().registerMetricReader(reader).build();
-              return new MeterProviderAndReaders(provider, reader);
+              var provider = SdkMeterProvider.builder().registerMetricReader(reader);
+              SdkMeterProviderUtil.setExemplarFilter(provider, ExemplarFilter.traceBased());
+              return new MeterProviderAndReaders(provider.build(), reader);
             })
         .sdkMeterProvider();
   }
diff --git a/solr/core/src/java/org/apache/solr/response/PrometheusResponseWriter.java b/solr/core/src/java/org/apache/solr/response/PrometheusResponseWriter.java
index 77fd3642b2a..070ed662dc0 100644
--- a/solr/core/src/java/org/apache/solr/response/PrometheusResponseWriter.java
+++ b/solr/core/src/java/org/apache/solr/response/PrometheusResponseWriter.java
@@ -16,8 +16,11 @@
  */
 package org.apache.solr.response;
 
+import static org.apache.solr.handler.admin.MetricsHandler.OPEN_METRICS_WT;
+
 import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
 import io.opentelemetry.sdk.metrics.SdkMeterProvider;
+import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter;
 import io.prometheus.metrics.expositionformats.PrometheusTextFormatWriter;
 import io.prometheus.metrics.model.snapshots.CounterSnapshot;
 import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
@@ -31,6 +34,7 @@ import java.lang.invoke.MethodHandles;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.handler.admin.MetricsHandler;
 import org.apache.solr.request.SolrQueryRequest;
 import org.slf4j.Logger;
@@ -42,6 +46,9 @@ public class PrometheusResponseWriter implements QueryResponseWriter {
   // not TextQueryResponseWriter because Prometheus libs work with an OutputStream
 
   private static final String CONTENT_TYPE_PROMETHEUS = "text/plain; version=0.0.4";
+  private static final String CONTENT_TYPE_OPEN_METRICS =
+      "application/openmetrics-text; version=1.0.0; charset=utf-8";
+
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   @Override
@@ -55,12 +62,35 @@ public class PrometheusResponseWriter implements QueryResponseWriter {
     List<MetricSnapshot> snapshots =
         readers.values().stream().flatMap(r -> r.collect().stream()).toList();
 
-    new PrometheusTextFormatWriter(false).write(out, mergeSnapshots(snapshots));
+    if (writeOpenMetricsFormat(request)) {
+      new OpenMetricsTextFormatWriter(false, true).write(out, mergeSnapshots(snapshots));
+    } else {
+      new PrometheusTextFormatWriter(false).write(out, mergeSnapshots(snapshots));
+    }
   }
 
   @Override
   public String getContentType(SolrQueryRequest request, SolrQueryResponse response) {
-    return CONTENT_TYPE_PROMETHEUS;
+    return writeOpenMetricsFormat(request) ? CONTENT_TYPE_OPEN_METRICS : CONTENT_TYPE_PROMETHEUS;
+  }
+
+  private boolean writeOpenMetricsFormat(SolrQueryRequest request) {
+    String wt = request.getParams().get(CommonParams.WT);
+    if (OPEN_METRICS_WT.equals(wt)) {
+      return true;
+    }
+
+    String acceptHeader =
+        request.getHttpSolrCall() != null
+            ? request.getHttpSolrCall().getReq().getHeader("Accept")
+            : null;
+
+    if (acceptHeader == null) {
+      return false;
+    }
+
+    return acceptHeader.contains("application/openmetrics-text")
+        && (acceptHeader.contains("version=1.0.0"));
   }
 
   /**
diff --git a/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriter.java b/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriter.java
index 6d818157b6f..71545ea6628 100644
--- a/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriter.java
+++ b/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriter.java
@@ -17,7 +17,9 @@
 package org.apache.solr.response;
 
 import com.codahale.metrics.SharedMetricRegistries;
+import java.io.InputStream;
 import java.lang.invoke.MethodHandles;
+import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
@@ -28,6 +30,7 @@ import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrRequest.METHOD;
 import org.apache.solr.client.solrj.SolrRequest.SolrRequestType;
+import org.apache.solr.client.solrj.impl.InputStreamResponseParser;
 import org.apache.solr.client.solrj.impl.NoOpResponseParser;
 import org.apache.solr.client.solrj.request.GenericSolrRequest;
 import org.apache.solr.common.params.ModifiableSolrParams;
@@ -118,4 +121,63 @@ public class TestPrometheusResponseWriter extends SolrTestCaseJ4 {
           });
     }
   }
+
+  @Test
+  public void testAcceptHeaderOpenMetricsFormat() throws Exception {
+    ModifiableSolrParams params = new ModifiableSolrParams();
+    var req = new GenericSolrRequest(METHOD.GET, "/admin/metrics", SolrRequestType.ADMIN, params);
+
+    // NOCOMMIT: Remove this prometheus writer type after Dropwizard is removed
+    req.setResponseParser(new InputStreamResponseParser("prometheus"));
+
+    req.addHeader("Accept", "application/openmetrics-text;version=1.0.0");
+
+    try (SolrClient adminClient = getHttpSolrClient(solrClientTestRule.getBaseUrl())) {
+      NamedList<Object> res = adminClient.request(req);
+
+      try (InputStream in = (InputStream) res.get("stream")) {
+        String output = new String(in.readAllBytes(), StandardCharsets.UTF_8);
+        assertTrue(
+            "Should use OpenMetrics format when Accept header is set",
+            output.trim().endsWith("# EOF"));
+      }
+    }
+  }
+
+  @Test
+  public void testWtParameterOpenMetricsFormat() throws Exception {
+    ModifiableSolrParams params = new ModifiableSolrParams();
+    var req = new GenericSolrRequest(METHOD.GET, "/admin/metrics", SolrRequestType.ADMIN, params);
+    req.setResponseParser(new InputStreamResponseParser("openmetrics"));
+
+    try (SolrClient adminClient = getHttpSolrClient(solrClientTestRule.getBaseUrl())) {
+      NamedList<Object> res = adminClient.request(req);
+
+      try (InputStream in = (InputStream) res.get("stream")) {
+        String output = new String(in.readAllBytes(), StandardCharsets.UTF_8);
+        assertTrue(
+            "Should use OpenMetrics format when Accept header is set",
+            output.trim().endsWith("# EOF"));
+      }
+    }
+  }
+
+  @Test
+  public void testDefaultPrometheusFormat() throws Exception {
+    ModifiableSolrParams params = new ModifiableSolrParams();
+    var req = new GenericSolrRequest(METHOD.GET, "/admin/metrics", SolrRequestType.ADMIN, params);
+    // NOCOMMIT: Remove this prometheus writer type after Dropwizard is removed
+    req.setResponseParser(new InputStreamResponseParser("prometheus"));
+
+    try (SolrClient adminClient = getHttpSolrClient(solrClientTestRule.getBaseUrl())) {
+      NamedList<Object> res = adminClient.request(req);
+
+      try (InputStream in = (InputStream) res.get("stream")) {
+        String output = new String(in.readAllBytes(), StandardCharsets.UTF_8);
+        assertFalse(
+            "Should default to Prometheus format when no Accept header or wt=openmetrics is set",
+            output.trim().endsWith("# EOF"));
+      }
+    }
+  }
 }
diff --git a/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestDistributedTracing.java b/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestDistributedTracing.java
index 501893e99e8..4daba5c87a4 100644
--- a/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestDistributedTracing.java
+++ b/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestDistributedTracing.java
@@ -321,7 +321,7 @@ public class TestDistributedTracing extends SolrCloudTestCase {
     return result;
   }
 
-  private String getRootTraceId(List<SpanData> finishedSpans) {
+  static String getRootTraceId(List<SpanData> finishedSpans) {
     assertEquals(1, finishedSpans.stream().filter(TestDistributedTracing::isRootSpan).count());
     return finishedSpans.stream()
         .filter(TestDistributedTracing::isRootSpan)
diff --git a/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestMetricExemplars.java b/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestMetricExemplars.java
new file mode 100644
index 00000000000..687b86b20cb
--- /dev/null
+++ b/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestMetricExemplars.java
@@ -0,0 +1,95 @@
+package org.apache.solr.opentelemetry;
+
+import static org.apache.solr.opentelemetry.TestDistributedTracing.getAndClearSpans;
+import static org.apache.solr.opentelemetry.TestDistributedTracing.getRootTraceId;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.trace.TracerProvider;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.impl.CloudSolrClient;
+import org.apache.solr.client.solrj.impl.InputStreamResponseParser;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.request.GenericSolrRequest;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.util.NamedList;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestMetricExemplars extends SolrCloudTestCase {
+
+  @BeforeClass
+  public static void setupCluster() throws Exception {
+    System.setProperty("otel.traces.sampler", "always_on");
+    // force early init
+    CustomTestOtelTracerConfigurator.prepareForTest();
+
+    configureCluster(1)
+        .addConfig("config", TEST_PATH().resolve("collection1").resolve("conf"))
+        .withSolrXml(TEST_PATH().resolve("solr.xml"))
+        .withTraceIdGenerationDisabled()
+        .configure();
+
+    assertNotEquals(
+        "Expecting active otel, not noop impl",
+        TracerProvider.noop(),
+        GlobalOpenTelemetry.get().getTracerProvider());
+
+    CollectionAdminRequest.createCollection("collection1", "config", 1, 1)
+        .process(cluster.getSolrClient());
+    cluster.waitForActiveCollection("collection1", 1, 1);
+  }
+
+  @AfterClass
+  public static void afterClass() {
+    CustomTestOtelTracerConfigurator.resetForTest();
+  }
+
+  @Before
+  private void resetSpanData() {
+    getAndClearSpans();
+  }
+
+  @Test
+  public void testOpenMetricExemplars() throws Exception {
+    CloudSolrClient cloudClient = cluster.getSolrClient();
+
+    // Generate exemplars
+    cloudClient.add("collection1", sdoc("id", "1"));
+    var spans = getAndClearSpans();
+    var expectedTrace = getRootTraceId(spans);
+
+    var req =
+        new GenericSolrRequest(
+            SolrRequest.METHOD.GET,
+            "/admin/metrics",
+            SolrRequest.SolrRequestType.ADMIN,
+            new ModifiableSolrParams().set("wt", "openmetrics"));
+    req.setResponseParser(new InputStreamResponseParser("openmetrics"));
+    NamedList<Object> resp = cloudClient.request(req);
+
+    try (InputStream in = (InputStream) resp.get("stream")) {
+      String output = new String(in.readAllBytes(), StandardCharsets.UTF_8);
+      var line =
+          output
+              .lines()
+              .filter(
+                  l ->
+                      l.startsWith("solr_metrics_core_requests_total")
+                          && l.contains("handler=\"/update\"")
+                          && l.contains("collection=\"collection1\""))
+              .findFirst()
+              .orElseThrow(
+                  () ->
+                      new AssertionError(
+                          "Could not find /update solr_metrics_core_requests_total"));
+
+      String actualExemplarTrace = line.split("trace_id=\"")[1].split("\"")[0];
+      assertEquals(actualExemplarTrace, expectedTrace);
+    }
+  }
+}


(solr) 13/33: SOLR-17806: Use OtelUnit to create Otel instruments with units as metadata (#3466)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit bfa519d3689244cd436238001e4a5dca504edf46
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Wed Aug 13 11:31:56 2025 -0400

    SOLR-17806: Use OtelUnit to create Otel instruments with units as metadata (#3466)
    
    * Enum for OTEL Units
    
    * Cleanup
---
 .../src/java/org/apache/solr/core/SolrCore.java    |  8 ++--
 .../apache/solr/handler/RequestHandlerBase.java    |  5 ++-
 .../org/apache/solr/metrics/SolrMetricManager.java | 49 +++++++++++----------
 .../apache/solr/metrics/SolrMetricsContext.java    | 46 ++++++++++++++-----
 .../org/apache/solr/metrics/otel/OtelUnit.java     | 51 ++++++++++++++++++++++
 .../src/java/org/apache/solr/update/PeerSync.java  |  4 +-
 .../org/apache/solr/update/PeerSyncWithLeader.java |  3 +-
 .../src/java/org/apache/solr/update/UpdateLog.java |  3 +-
 8 files changed, 127 insertions(+), 42 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/core/SolrCore.java b/solr/core/src/java/org/apache/solr/core/SolrCore.java
index bfe73060d22..50c40530937 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrCore.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java
@@ -118,6 +118,7 @@ import org.apache.solr.logging.MDCLoggingContext;
 import org.apache.solr.metrics.SolrCoreMetricManager;
 import org.apache.solr.metrics.SolrMetricProducer;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.OtelUnit;
 import org.apache.solr.metrics.otel.instruments.AttributedLongCounter;
 import org.apache.solr.metrics.otel.instruments.AttributedLongTimer;
 import org.apache.solr.pkg.PackageListeners;
@@ -1337,7 +1338,8 @@ public class SolrCore implements SolrInfoBean, Closeable {
             .build();
 
     var baseSearcherTimerMetric =
-        parentContext.longHistogram("solr_searcher_timer", "Timer for opening new searchers", "ms");
+        parentContext.longHistogram(
+            "solr_searcher_timer", "Timer for opening new searchers", OtelUnit.MILLISECONDS);
 
     newSearcherCounter =
         new AttributedLongCounter(
@@ -1407,7 +1409,7 @@ public class SolrCore implements SolrInfoBean, Closeable {
             observableLongMeasurement.record(0L, usableSpaceAttributes);
           }
         }),
-        "By");
+        OtelUnit.BYTES);
 
     parentContext.observableLongGauge(
         "solr_core_index_size",
@@ -1416,7 +1418,7 @@ public class SolrCore implements SolrInfoBean, Closeable {
           if (!isClosed())
             observableLongMeasurement.record(getIndexSize(), baseGaugeCoreAttributes);
         }),
-        "By");
+        OtelUnit.BYTES);
 
     parentContext.observableLongGauge(
         "solr_core_segment_count",
diff --git a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
index 610bf338a6f..9f25b431c52 100644
--- a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
+++ b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
@@ -46,6 +46,7 @@ import org.apache.solr.metrics.SolrDelegateRegistryMetricsContext;
 import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.metrics.SolrMetricProducer;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.OtelUnit;
 import org.apache.solr.metrics.otel.instruments.AttributedLongCounter;
 import org.apache.solr.metrics.otel.instruments.AttributedLongTimer;
 import org.apache.solr.request.SolrQueryRequest;
@@ -223,7 +224,7 @@ public abstract class RequestHandlerBase
                 "solr_node_requests_timeout", "HTTP Solr node request timeouts");
         requestTimeMetric =
             solrMetricsContext.longHistogram(
-                "solr_node_requests_times", "HTTP Solr node request times", "ms");
+                "solr_node_requests_times", "HTTP Solr node request times", OtelUnit.MILLISECONDS);
       } else {
         requestMetric =
             solrMetricsContext.longCounter("solr_core_requests", "HTTP Solr core requests");
@@ -235,7 +236,7 @@ public abstract class RequestHandlerBase
                 "solr_core_requests_timeout", "HTTP Solr core request timeouts");
         requestTimeMetric =
             solrMetricsContext.longHistogram(
-                "solr_core_requests_times", "HTTP Solr core request times", "ms");
+                "solr_core_requests_times", "HTTP Solr core request times", OtelUnit.MILLISECONDS);
       }
 
       requests = new AttributedLongCounter(requestMetric, attributes);
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
index 0e6a1db637c..367bc9da886 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
@@ -84,6 +84,7 @@ import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.core.SolrResourceLoader;
 import org.apache.solr.logging.MDCLoggingContext;
+import org.apache.solr.metrics.otel.OtelUnit;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.MDC;
@@ -172,69 +173,69 @@ public class SolrMetricManager {
   }
 
   public LongCounter longCounter(
-      String registry, String counterName, String description, String unit) {
+      String registry, String counterName, String description, OtelUnit unit) {
     LongCounterBuilder builder =
         meterProvider(registry)
             .get(OTEL_SCOPE_NAME)
             .counterBuilder(counterName)
             .setDescription(description);
-    if (unit != null) builder.setUnit(unit);
+    if (unit != null) builder.setUnit(unit.getSymbol());
 
     return builder.build();
   }
 
   public LongUpDownCounter longUpDownCounter(
-      String registry, String counterName, String description, String unit) {
+      String registry, String counterName, String description, OtelUnit unit) {
     LongUpDownCounterBuilder builder =
         meterProvider(registry)
             .get(OTEL_SCOPE_NAME)
             .upDownCounterBuilder(counterName)
             .setDescription(description);
-    if (unit != null) builder.setUnit(unit);
+    if (unit != null) builder.setUnit(unit.getSymbol());
 
     return builder.build();
   }
 
   public DoubleUpDownCounter doubleUpDownCounter(
-      String registry, String counterName, String description, String unit) {
+      String registry, String counterName, String description, OtelUnit unit) {
     DoubleUpDownCounterBuilder builder =
         meterProvider(registry)
             .get(OTEL_SCOPE_NAME)
             .upDownCounterBuilder(counterName)
             .setDescription(description)
             .ofDoubles();
-    if (unit != null) builder.setUnit(unit);
+    if (unit != null) builder.setUnit(unit.getSymbol());
 
     return builder.build();
   }
 
   public DoubleCounter doubleCounter(
-      String registry, String counterName, String description, String unit) {
+      String registry, String counterName, String description, OtelUnit unit) {
     DoubleCounterBuilder builder =
         meterProvider(registry)
             .get(OTEL_SCOPE_NAME)
             .counterBuilder(counterName)
             .setDescription(description)
             .ofDoubles();
-    if (unit != null) builder.setUnit(unit);
+    if (unit != null) builder.setUnit(unit.getSymbol());
 
     return builder.build();
   }
 
   public DoubleHistogram doubleHistogram(
-      String registry, String histogramName, String description, String unit) {
+      String registry, String histogramName, String description, OtelUnit unit) {
     DoubleHistogramBuilder builder =
         meterProvider(registry)
             .get(OTEL_SCOPE_NAME)
             .histogramBuilder(histogramName)
             .setDescription(description);
-    if (unit != null) builder.setUnit(unit);
+    if (unit != null) builder.setUnit(unit.getSymbol());
 
     return builder.build();
   }
 
   public LongHistogram longHistogram(
-      String registry, String histogramName, String description, String unit) {
+      String registry, String histogramName, String description, OtelUnit unit) {
     LongHistogramBuilder builder =
         meterProvider(registry)
             .get(OTEL_SCOPE_NAME)
@@ -242,31 +243,31 @@ public class SolrMetricManager {
             .setDescription(description)
             .ofLongs();
 
-    if (unit != null) builder.setUnit(unit);
+    if (unit != null) builder.setUnit(unit.getSymbol());
 
     return builder.build();
   }
 
   public DoubleGauge doubleGauge(
-      String registry, String gaugeName, String description, String unit) {
+      String registry, String gaugeName, String description, OtelUnit unit) {
     DoubleGaugeBuilder builder =
         meterProvider(registry)
             .get(OTEL_SCOPE_NAME)
             .gaugeBuilder(gaugeName)
             .setDescription(description);
-    if (unit != null) builder.setUnit(unit);
+    if (unit != null) builder.setUnit(unit.getSymbol());
 
     return builder.build();
   }
 
-  public LongGauge longGauge(String registry, String gaugeName, String description, String unit) {
+  public LongGauge longGauge(String registry, String gaugeName, String description, OtelUnit unit) {
     LongGaugeBuilder builder =
         meterProvider(registry)
             .get(OTEL_SCOPE_NAME)
             .gaugeBuilder(gaugeName)
             .setDescription(description)
             .ofLongs();
-    if (unit != null) builder.setUnit(unit);
+    if (unit != null) builder.setUnit(unit.getSymbol());
 
     return builder.build();
   }
@@ -276,13 +277,13 @@ public class SolrMetricManager {
       String counterName,
       String description,
       Consumer<ObservableLongMeasurement> callback,
-      String unit) {
+      OtelUnit unit) {
     LongCounterBuilder builder =
         meterProvider(registry)
             .get(OTEL_SCOPE_NAME)
             .counterBuilder(counterName)
             .setDescription(description);
-    if (unit != null) builder.setUnit(unit);
+    if (unit != null) builder.setUnit(unit.getSymbol());
 
     return builder.buildWithCallback(callback);
   }
@@ -292,14 +293,14 @@ public class SolrMetricManager {
       String counterName,
       String description,
       Consumer<ObservableDoubleMeasurement> callback,
-      String unit) {
+      OtelUnit unit) {
     DoubleCounterBuilder builder =
         meterProvider(registry)
             .get(OTEL_SCOPE_NAME)
             .counterBuilder(counterName)
             .setDescription(description)
             .ofDoubles();
-    if (unit != null) builder.setUnit(unit);
+    if (unit != null) builder.setUnit(unit.getSymbol());
 
     return builder.buildWithCallback(callback);
   }
@@ -309,14 +310,14 @@ public class SolrMetricManager {
       String gaugeName,
       String description,
       Consumer<ObservableLongMeasurement> callback,
-      String unit) {
+      OtelUnit unit) {
     LongGaugeBuilder builder =
         meterProvider(registry)
             .get(OTEL_SCOPE_NAME)
             .gaugeBuilder(gaugeName)
             .setDescription(description)
             .ofLongs();
-    if (unit != null) builder.setUnit(unit);
+    if (unit != null) builder.setUnit(unit.getSymbol());
 
     return builder.buildWithCallback(callback);
   }
@@ -326,14 +327,14 @@ public class SolrMetricManager {
       String gaugeName,
       String description,
       Consumer<ObservableDoubleMeasurement> callback,
-      String unit) {
+      OtelUnit unit) {
     DoubleGaugeBuilder builder =
         meterProvider(registry)
             .get(OTEL_SCOPE_NAME)
             .gaugeBuilder(gaugeName)
             .setDescription(description);
 
-    if (unit != null) builder.setUnit(unit);
+    if (unit != null) builder.setUnit(unit.getSymbol());
 
     return builder.buildWithCallback(callback);
   }
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java
index 1601bae36a3..f7f87303936 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java
@@ -26,10 +26,12 @@ import com.codahale.metrics.Timer;
 import io.opentelemetry.api.metrics.DoubleCounter;
 import io.opentelemetry.api.metrics.DoubleGauge;
 import io.opentelemetry.api.metrics.DoubleHistogram;
+import io.opentelemetry.api.metrics.DoubleUpDownCounter;
 import io.opentelemetry.api.metrics.LongCounter;
 import io.opentelemetry.api.metrics.LongGauge;
 import io.opentelemetry.api.metrics.LongHistogram;
 import io.opentelemetry.api.metrics.LongUpDownCounter;
+import io.opentelemetry.api.metrics.ObservableDoubleCounter;
 import io.opentelemetry.api.metrics.ObservableDoubleGauge;
 import io.opentelemetry.api.metrics.ObservableDoubleMeasurement;
 import io.opentelemetry.api.metrics.ObservableLongCounter;
@@ -39,6 +41,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
+import org.apache.solr.metrics.otel.OtelUnit;
 import org.apache.solr.util.stats.MetricUtils;
 
 /**
@@ -155,7 +158,7 @@ public class SolrMetricsContext {
     return longCounter(metricName, description, null);
   }
 
-  public LongCounter longCounter(String metricName, String description, String unit) {
+  public LongCounter longCounter(String metricName, String description, OtelUnit unit) {
     return metricManager.longCounter(registryName, metricName, description, unit);
   }
 
@@ -163,7 +166,7 @@ public class SolrMetricsContext {
     return longUpDownCounter(metricName, description, null);
   }
 
-  public LongUpDownCounter longUpDownCounter(String metricName, String description, String unit) {
+  public LongUpDownCounter longUpDownCounter(String metricName, String description, OtelUnit unit) {
     return metricManager.longUpDownCounter(registryName, metricName, description, unit);
   }
 
@@ -171,15 +174,24 @@ public class SolrMetricsContext {
     return doubleCounter(metricName, description, null);
   }
 
-  public DoubleCounter doubleCounter(String metricName, String description, String unit) {
+  public DoubleCounter doubleCounter(String metricName, String description, OtelUnit unit) {
     return metricManager.doubleCounter(registryName, metricName, description, unit);
   }
 
+  public DoubleUpDownCounter doubleUpDownCounter(String metricName, String description) {
+    return doubleUpDownCounter(metricName, description, null);
+  }
+
+  public DoubleUpDownCounter doubleUpDownCounter(
+      String metricName, String description, OtelUnit unit) {
+    return metricManager.doubleUpDownCounter(registryName, metricName, description, unit);
+  }
+
   public DoubleHistogram doubleHistogram(String metricName, String description) {
     return metricManager.doubleHistogram(registryName, metricName, description, null);
   }
 
-  public DoubleHistogram doubleHistogram(String metricName, String description, String unit) {
+  public DoubleHistogram doubleHistogram(String metricName, String description, OtelUnit unit) {
     return metricManager.doubleHistogram(registryName, metricName, description, unit);
   }
 
@@ -187,7 +199,7 @@ public class SolrMetricsContext {
     return metricManager.longHistogram(registryName, metricName, description, null);
   }
 
-  public LongHistogram longHistogram(String metricName, String description, String unit) {
+  public LongHistogram longHistogram(String metricName, String description, OtelUnit unit) {
     return metricManager.longHistogram(registryName, metricName, description, unit);
   }
 
@@ -195,7 +207,7 @@ public class SolrMetricsContext {
     return metricManager.longGauge(registryName, metricName, description, null);
   }
 
-  public LongGauge longGauge(String metricName, String description, String unit) {
+  public LongGauge longGauge(String metricName, String description, OtelUnit unit) {
     return metricManager.longGauge(registryName, metricName, description, unit);
   }
 
@@ -203,7 +215,7 @@ public class SolrMetricsContext {
     return metricManager.doubleGauge(registryName, metricName, description, null);
   }
 
-  public DoubleGauge doubleGauge(String metricName, String description, String unit) {
+  public DoubleGauge doubleGauge(String metricName, String description, OtelUnit unit) {
     return metricManager.doubleGauge(registryName, metricName, description, unit);
   }
 
@@ -216,7 +228,7 @@ public class SolrMetricsContext {
       String metricName,
       String description,
       Consumer<ObservableLongMeasurement> callback,
-      String unit) {
+      OtelUnit unit) {
     return metricManager.observableLongGauge(registryName, metricName, description, callback, unit);
   }
 
@@ -229,7 +241,7 @@ public class SolrMetricsContext {
       String metricName,
       String description,
       Consumer<ObservableDoubleMeasurement> callback,
-      String unit) {
+      OtelUnit unit) {
     return metricManager.observableDoubleGauge(
         registryName, metricName, description, callback, unit);
   }
@@ -243,11 +255,25 @@ public class SolrMetricsContext {
       String metricName,
       String description,
       Consumer<ObservableLongMeasurement> callback,
-      String unit) {
+      OtelUnit unit) {
     return metricManager.observableLongCounter(
         registryName, metricName, description, callback, unit);
   }
 
+  public ObservableDoubleCounter observableDoubleCounter(
+      String metricName, String description, Consumer<ObservableDoubleMeasurement> callback) {
+    return observableDoubleCounter(metricName, description, callback, null);
+  }
+
+  public ObservableDoubleCounter observableDoubleCounter(
+      String metricName,
+      String description,
+      Consumer<ObservableDoubleMeasurement> callback,
+      OtelUnit unit) {
+    return metricManager.observableDoubleCounter(
+        registryName, metricName, description, callback, unit);
+  }
+
   /**
    * Convenience method for {@link SolrMetricManager#meter(SolrMetricsContext, String, String,
    * String...)}.
diff --git a/solr/core/src/java/org/apache/solr/metrics/otel/OtelUnit.java b/solr/core/src/java/org/apache/solr/metrics/otel/OtelUnit.java
new file mode 100644
index 00000000000..714c0a3d0ed
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/metrics/otel/OtelUnit.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.metrics.otel;
+
+/**
+ * Standard metric units for OpenTelemetry instruments based on UCUM (Unified Code for Units of
+ * Measure).
+ */
+public enum OtelUnit {
+  NANOSECONDS("ns"),
+  MICROSECONDS("us"),
+  MILLISECONDS("ms"),
+  SECONDS("s"),
+  MINUTES("min"),
+  HOURS("h"),
+  DAYS("d"),
+
+  BYTES("By"),
+  KILOBYTES("kBy"),
+  MEGABYTES("MBy"),
+  GIGABYTES("GBy");
+
+  private final String symbol;
+
+  OtelUnit(String symbol) {
+    this.symbol = symbol;
+  }
+
+  public String getSymbol() {
+    return symbol;
+  }
+
+  @Override
+  public String toString() {
+    return symbol;
+  }
+}
diff --git a/solr/core/src/java/org/apache/solr/update/PeerSync.java b/solr/core/src/java/org/apache/solr/update/PeerSync.java
index e4fa8e8bc39..a58d3b9c66c 100644
--- a/solr/core/src/java/org/apache/solr/update/PeerSync.java
+++ b/solr/core/src/java/org/apache/solr/update/PeerSync.java
@@ -49,6 +49,7 @@ import org.apache.solr.handler.component.ShardRequest;
 import org.apache.solr.handler.component.ShardResponse;
 import org.apache.solr.metrics.SolrMetricProducer;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.OtelUnit;
 import org.apache.solr.metrics.otel.instruments.AttributedLongCounter;
 import org.apache.solr.metrics.otel.instruments.AttributedLongTimer;
 import org.apache.solr.request.LocalSolrQueryRequest;
@@ -164,7 +165,8 @@ public class PeerSync implements SolrMetricProducer {
             baseAttributes);
     syncTime =
         new AttributedLongTimer(
-            solrMetricsContext.longHistogram("solr_core_peer_sync_time", "Peer sync times", "ms"),
+            solrMetricsContext.longHistogram(
+                "solr_core_peer_sync_time", "Peer sync times", OtelUnit.MILLISECONDS),
             baseAttributes);
   }
 
diff --git a/solr/core/src/java/org/apache/solr/update/PeerSyncWithLeader.java b/solr/core/src/java/org/apache/solr/update/PeerSyncWithLeader.java
index 94c6252dbe1..5a8e4186b29 100644
--- a/solr/core/src/java/org/apache/solr/update/PeerSyncWithLeader.java
+++ b/solr/core/src/java/org/apache/solr/update/PeerSyncWithLeader.java
@@ -41,6 +41,7 @@ import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.metrics.SolrMetricProducer;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.OtelUnit;
 import org.apache.solr.metrics.otel.instruments.AttributedLongCounter;
 import org.apache.solr.metrics.otel.instruments.AttributedLongTimer;
 import org.slf4j.Logger;
@@ -121,7 +122,7 @@ public class PeerSyncWithLeader implements SolrMetricProducer {
     syncTime =
         new AttributedLongTimer(
             solrMetricsContext.longHistogram(
-                "solr_core_sync_with_leader_time", "leader sync times", "ms"),
+                "solr_core_sync_with_leader_time", "leader sync times", OtelUnit.MILLISECONDS),
             baseAttributes);
   }
 
diff --git a/solr/core/src/java/org/apache/solr/update/UpdateLog.java b/solr/core/src/java/org/apache/solr/update/UpdateLog.java
index bcb4cde925f..661e5815346 100644
--- a/solr/core/src/java/org/apache/solr/update/UpdateLog.java
+++ b/solr/core/src/java/org/apache/solr/update/UpdateLog.java
@@ -79,6 +79,7 @@ import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.core.SolrPaths;
 import org.apache.solr.metrics.SolrMetricProducer;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.OtelUnit;
 import org.apache.solr.metrics.otel.instruments.AttributedLongCounter;
 import org.apache.solr.request.LocalSolrQueryRequest;
 import org.apache.solr.request.SolrQueryRequest;
@@ -658,7 +659,7 @@ public class UpdateLog implements PluginInfoInitialized, SolrMetricProducer {
         (observableLongMeasurement -> {
           observableLongMeasurement.record(getTotalLogsSize(), baseAttributes);
         }),
-        "By");
+        OtelUnit.BYTES);
 
     // NOCOMMIT: Do we want to keep this? Metric was just state with the numeric enum value.
     // Without context this doesn't mean anything and can be very confusing. Maybe keep the numeric


(solr) 21/33: SOLR-17806: Migrate SolrIndexWriter metrics to OTEL (#3568)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit e551fa15e48292927b2d06315ed4e959c6f16610
Author: cugarte <cu...@gmail.com>
AuthorDate: Mon Sep 8 09:40:07 2025 -0400

    SOLR-17806: Migrate SolrIndexWriter metrics to OTEL (#3568)
    
    * SOLR-17806: Migrate SolrIndexWriter metrics to OTEL
    
    * Adding unit test, merged gauges, closed observables.
    
    * Reusing metric name by switching merge type to an attribute.
    
    * gradlew tidy
---
 .../org/apache/solr/update/SolrIndexWriter.java    | 194 +++++++++++++-------
 .../collection1/conf/solrconfig-indexmetrics.xml   |   2 +
 .../apache/solr/update/SolrIndexMetricsTest.java   | 204 ++++++++++++++++-----
 3 files changed, 289 insertions(+), 111 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/update/SolrIndexWriter.java b/solr/core/src/java/org/apache/solr/update/SolrIndexWriter.java
index 7b215e74fab..7fd5e434d6f 100644
--- a/solr/core/src/java/org/apache/solr/update/SolrIndexWriter.java
+++ b/solr/core/src/java/org/apache/solr/update/SolrIndexWriter.java
@@ -16,9 +16,16 @@
  */
 package org.apache.solr.update;
 
-import com.codahale.metrics.Counter;
-import com.codahale.metrics.Meter;
-import com.codahale.metrics.Timer;
+import static org.apache.solr.metrics.SolrCoreMetricManager.COLLECTION_ATTR;
+import static org.apache.solr.metrics.SolrCoreMetricManager.CORE_ATTR;
+import static org.apache.solr.metrics.SolrCoreMetricManager.REPLICA_ATTR;
+import static org.apache.solr.metrics.SolrCoreMetricManager.SHARD_ATTR;
+import static org.apache.solr.metrics.SolrMetricProducer.CATEGORY_ATTR;
+import static org.apache.solr.metrics.SolrMetricProducer.TYPE_ATTR;
+
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.ObservableLongGauge;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.util.Collections;
@@ -37,11 +44,15 @@ import org.apache.lucene.store.Directory;
 import org.apache.lucene.util.InfoStream;
 import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.common.util.SuppressForbidden;
+import org.apache.solr.common.util.Utils;
 import org.apache.solr.core.DirectoryFactory;
 import org.apache.solr.core.DirectoryFactory.DirContext;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.OtelUnit;
+import org.apache.solr.metrics.otel.instruments.AttributedLongCounter;
+import org.apache.solr.metrics.otel.instruments.AttributedLongTimer;
 import org.apache.solr.schema.IndexSchema;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -64,6 +75,8 @@ public class SolrIndexWriter extends IndexWriter {
 
   public static final String COMMIT_COMMAND_VERSION = "commitCommandVer";
 
+  public static final AttributeKey<String> MERGE_TYPE_ATTR = AttributeKey.stringKey("merge_type");
+
   private final Object CLOSE_LOCK = new Object();
 
   String name;
@@ -73,12 +86,12 @@ public class SolrIndexWriter extends IndexWriter {
 
   // metrics
   private long majorMergeDocs = 512 * 1024;
-  private Timer majorMerge;
-  private Timer minorMerge;
-  private Meter majorMergedDocs;
-  private Meter majorDeletedDocs;
-  private Counter mergeErrors;
-  private Meter flushMeter; // original counter is package-private in IndexWriter
+  private AttributedLongTimer majorMerge;
+  private AttributedLongTimer minorMerge;
+  private AttributedLongCounter majorMergedDocs;
+  private AttributedLongCounter majorDeletedDocs;
+  private AttributedLongCounter mergeErrors;
+  private AttributedLongCounter flushes; // original counter is package-private in IndexWriter
   private boolean mergeTotals = false;
   private boolean mergeDetails = false;
   private final AtomicInteger runningMajorMerges = new AtomicInteger();
@@ -87,6 +100,7 @@ public class SolrIndexWriter extends IndexWriter {
   private final AtomicInteger runningMinorMergesSegments = new AtomicInteger();
   private final AtomicLong runningMajorMergesDocs = new AtomicLong();
   private final AtomicLong runningMinorMergesDocs = new AtomicLong();
+  private ObservableLongGauge mergeStats;
 
   private final SolrMetricsContext solrMetricsContext;
   // merge diagnostics.
@@ -177,66 +191,113 @@ public class SolrIndexWriter extends IndexWriter {
       } else {
         mergeTotals = false;
       }
+      String coreName = core.getCoreDescriptor().getName();
+      var baseAttributesBuilder =
+          Attributes.builder()
+              .put(CATEGORY_ATTR, SolrInfoBean.Category.INDEX.toString())
+              .put(CORE_ATTR, coreName);
+      if (core.getCoreContainer().isZooKeeperAware()) {
+        String collectionName = core.getCoreDescriptor().getCollectionName();
+        baseAttributesBuilder
+            .put(COLLECTION_ATTR, collectionName)
+            .put(SHARD_ATTR, core.getCoreDescriptor().getCloudDescriptor().getShardId())
+            .put(REPLICA_ATTR, Utils.parseMetricsReplicaName(collectionName, coreName));
+      }
+      var baseAttributes = baseAttributesBuilder.build();
       if (mergeDetails) {
         mergeTotals = true; // override
         majorMergedDocs =
-            solrMetricsContext.meter(
-                "docs", SolrInfoBean.Category.INDEX.toString(), "merge", "major");
+            new AttributedLongCounter(
+                solrMetricsContext.longCounter(
+                    "solr_indexwriter_major_merged_docs",
+                    "Number of documents merged while merging segments above the majorMergeDocs threshold ("
+                        + majorMergeDocs
+                        + ")"),
+                baseAttributes);
         majorDeletedDocs =
-            solrMetricsContext.meter(
-                "deletedDocs", SolrInfoBean.Category.INDEX.toString(), "merge", "major");
+            new AttributedLongCounter(
+                solrMetricsContext.longCounter(
+                    "solr_indexwriter_major_deleted_docs",
+                    "Number of deleted documents that were expunged while merging segments above the majorMergeDocs threshold ("
+                        + majorMergeDocs
+                        + ")"),
+                baseAttributes);
       }
       if (mergeTotals) {
         minorMerge =
-            solrMetricsContext.timer("minor", SolrInfoBean.Category.INDEX.toString(), "merge");
+            new AttributedLongTimer(
+                solrMetricsContext.longHistogram(
+                    "solr_indexwriter_merge",
+                    "Time spent merging segments below or equal to the majorMergeDocs threshold ("
+                        + majorMergeDocs
+                        + ")",
+                    OtelUnit.MILLISECONDS),
+                baseAttributes.toBuilder().put(MERGE_TYPE_ATTR, "minor").build());
         majorMerge =
-            solrMetricsContext.timer("major", SolrInfoBean.Category.INDEX.toString(), "merge");
+            new AttributedLongTimer(
+                solrMetricsContext.longHistogram(
+                    "solr_indexwriter_merge",
+                    "Time spent merging segments above the majorMergeDocs threshold ("
+                        + majorMergeDocs
+                        + ")",
+                    OtelUnit.MILLISECONDS),
+                baseAttributes.toBuilder().put(MERGE_TYPE_ATTR, "major").build());
         mergeErrors =
-            solrMetricsContext.counter("errors", SolrInfoBean.Category.INDEX.toString(), "merge");
+            new AttributedLongCounter(
+                solrMetricsContext.longCounter(
+                    "solr_indexwriter_merge_errors", "Number of merge errors"),
+                baseAttributes);
         String tag = core.getMetricTag();
-        solrMetricsContext.gauge(
-            () -> runningMajorMerges.get(),
-            true,
-            "running",
-            SolrInfoBean.Category.INDEX.toString(),
-            "merge",
-            "major");
-        solrMetricsContext.gauge(
-            () -> runningMinorMerges.get(),
-            true,
-            "running",
-            SolrInfoBean.Category.INDEX.toString(),
-            "merge",
-            "minor");
-        solrMetricsContext.gauge(
-            () -> runningMajorMergesDocs.get(),
-            true,
-            "running.docs",
-            SolrInfoBean.Category.INDEX.toString(),
-            "merge",
-            "major");
-        solrMetricsContext.gauge(
-            () -> runningMinorMergesDocs.get(),
-            true,
-            "running.docs",
-            SolrInfoBean.Category.INDEX.toString(),
-            "merge",
-            "minor");
-        solrMetricsContext.gauge(
-            () -> runningMajorMergesSegments.get(),
-            true,
-            "running.segments",
-            SolrInfoBean.Category.INDEX.toString(),
-            "merge",
-            "major");
-        solrMetricsContext.gauge(
-            () -> runningMinorMergesSegments.get(),
-            true,
-            "running.segments",
-            SolrInfoBean.Category.INDEX.toString(),
-            "merge",
-            "minor");
-        flushMeter = solrMetricsContext.meter("flush", SolrInfoBean.Category.INDEX.toString());
+        mergeStats =
+            solrMetricsContext.observableLongGauge(
+                "solr_indexwriter_merge_stats",
+                "Metrics around currently running segment merges; major := above the majorMergeDocs threshold ("
+                    + majorMergeDocs
+                    + "), minor := below or equal to the threshold",
+                (observableLongMeasurement -> {
+                  observableLongMeasurement.record(
+                      runningMajorMerges.get(),
+                      baseAttributes.toBuilder()
+                          .put(TYPE_ATTR, "running")
+                          .put(MERGE_TYPE_ATTR, "major")
+                          .build());
+                  observableLongMeasurement.record(
+                      runningMajorMergesDocs.get(),
+                      baseAttributes.toBuilder()
+                          .put(TYPE_ATTR, "running_docs")
+                          .put(MERGE_TYPE_ATTR, "major")
+                          .build());
+                  observableLongMeasurement.record(
+                      runningMajorMergesSegments.get(),
+                      baseAttributes.toBuilder()
+                          .put(TYPE_ATTR, "running_segments")
+                          .put(MERGE_TYPE_ATTR, "major")
+                          .build());
+                  observableLongMeasurement.record(
+                      runningMinorMerges.get(),
+                      baseAttributes.toBuilder()
+                          .put(TYPE_ATTR, "running")
+                          .put(MERGE_TYPE_ATTR, "minor")
+                          .build());
+                  observableLongMeasurement.record(
+                      runningMinorMergesDocs.get(),
+                      baseAttributes.toBuilder()
+                          .put(TYPE_ATTR, "running_docs")
+                          .put(MERGE_TYPE_ATTR, "minor")
+                          .build());
+                  observableLongMeasurement.record(
+                      runningMinorMergesSegments.get(),
+                      baseAttributes.toBuilder()
+                          .put(TYPE_ATTR, "running_segments")
+                          .put(MERGE_TYPE_ATTR, "minor")
+                          .build());
+                }));
+        flushes =
+            new AttributedLongCounter(
+                solrMetricsContext.longCounter(
+                    "solr_indexwriter_flush",
+                    "Number of times added/deleted documents have been flushed to the Directory"),
+                baseAttributes);
       }
     }
   }
@@ -284,21 +345,21 @@ public class SolrIndexWriter extends IndexWriter {
     }
     boolean major = totalNumDocs > majorMergeDocs;
     int segmentsCount = merge.segments.size();
-    Timer.Context context;
+    AttributedLongTimer.MetricTimer context;
     if (major) {
       runningMajorMerges.incrementAndGet();
       runningMajorMergesDocs.addAndGet(totalNumDocs);
       runningMajorMergesSegments.addAndGet(segmentsCount);
       if (mergeDetails) {
-        majorMergedDocs.mark(totalNumDocs);
-        majorDeletedDocs.mark(deletedDocs);
+        majorMergedDocs.add(totalNumDocs);
+        majorDeletedDocs.add(deletedDocs);
       }
-      context = majorMerge.time();
+      context = majorMerge.start();
     } else {
       runningMinorMerges.incrementAndGet();
       runningMinorMergesDocs.addAndGet(totalNumDocs);
       runningMinorMergesSegments.addAndGet(segmentsCount);
-      context = minorMerge.time();
+      context = minorMerge.start();
     }
     try {
       super.merge(merge);
@@ -326,8 +387,8 @@ public class SolrIndexWriter extends IndexWriter {
 
   @Override
   protected void doAfterFlush() throws IOException {
-    if (flushMeter != null) { // this is null when writer is used only for snapshot cleanup
-      flushMeter.mark(); // or if mergeTotals == false
+    if (flushes != null) { // this is null when writer is used only for snapshot cleanup
+      flushes.inc(); // or if mergeTotals == false
     }
     super.doAfterFlush();
   }
@@ -416,6 +477,7 @@ public class SolrIndexWriter extends IndexWriter {
       if (directoryFactory != null) {
         directoryFactory.release(directory);
       }
+      IOUtils.closeQuietly(mergeStats);
       if (solrMetricsContext != null) {
         solrMetricsContext.unregister();
       }
diff --git a/solr/core/src/test-files/solr/collection1/conf/solrconfig-indexmetrics.xml b/solr/core/src/test-files/solr/collection1/conf/solrconfig-indexmetrics.xml
index 6238e7db021..ec5c9406fee 100644
--- a/solr/core/src/test-files/solr/collection1/conf/solrconfig-indexmetrics.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/solrconfig-indexmetrics.xml
@@ -31,6 +31,8 @@
     <metrics>
       <bool name="merge">${solr.tests.metrics.merge:false}</bool>
       <bool name="mergeDetails">${solr.tests.metrics.mergeDetails:false}</bool>
+      <!-- majorMergeDocs default comes from the default of SolrIndexWriter.majorMergeDocs -->
+      <long name="majorMergeDocs">${solr.tests.metrics.majorMergeDocs:524288}</long>
     </metrics>
     <!-- intentionally set very low values here to trigger multiple flushes and merges.
          DO NOT USE THESE ABSURD VALUES IN PRODUCTION. -->
diff --git a/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java b/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java
index ecfd83c02d4..6b0e96e77af 100644
--- a/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java
+++ b/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java
@@ -16,14 +16,14 @@
  */
 package org.apache.solr.update;
 
-import com.codahale.metrics.Meter;
-import com.codahale.metrics.Metric;
-import com.codahale.metrics.MetricRegistry;
-import com.codahale.metrics.Timer;
-import java.util.Map;
+import static org.apache.solr.metrics.SolrMetricProducer.CATEGORY_ATTR;
+import static org.apache.solr.update.SolrIndexWriter.MERGE_TYPE_ATTR;
+
+import io.prometheus.metrics.model.snapshots.MetricSnapshots;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.core.SolrCore;
+import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.metrics.SolrMetricTestUtils;
 import org.apache.solr.request.SolrQueryRequest;
 import org.junit.After;
@@ -61,28 +61,49 @@ public class SolrIndexMetricsTest extends SolrTestCaseJ4 {
 
     addDocs();
 
-    MetricRegistry registry =
-        h.getCoreContainer()
-            .getMetricManager()
-            .registry(h.getCore().getCoreMetricManager().getRegistryName());
-    assertNotNull(registry);
-
-    Map<String, Metric> metrics = registry.getMetrics();
-
-    // NOCOMMIT: As we migrate more metrics to OTEL, this will need to migrate to check prometheus
-    // reader instead
-    assertEquals(
-        10, metrics.entrySet().stream().filter(e -> e.getKey().startsWith("INDEX")).count());
-
-    // check basic index meters
-    Timer timer = (Timer) metrics.get("INDEX.merge.minor");
-    assertTrue("minorMerge: " + timer.getCount(), timer.getCount() >= 3);
-    timer = (Timer) metrics.get("INDEX.merge.major");
-    assertEquals("majorMerge: " + timer.getCount(), 0, timer.getCount());
-    // check detailed meters
-    assertNull((Meter) metrics.get("INDEX.merge.major.docs"));
-    Meter meter = (Meter) metrics.get("INDEX.flush");
-    assertTrue("flush: " + meter.getCount(), meter.getCount() > 10);
+    try (SolrCore core = h.getCoreContainer().getCore("collection1")) {
+      // check basic index meters
+      var minorMergeTimer =
+          SolrMetricTestUtils.getHistogramDatapoint(
+              core,
+              "solr_indexwriter_merge_milliseconds",
+              SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+                  .label(CATEGORY_ATTR.toString(), SolrInfoBean.Category.INDEX.toString())
+                  .label(MERGE_TYPE_ATTR.toString(), "minor")
+                  .build());
+      assertTrue("minorMerge: " + minorMergeTimer.getCount(), minorMergeTimer.getCount() >= 3);
+      var majorMergeTimer =
+          SolrMetricTestUtils.getHistogramDatapoint(
+              core,
+              "solr_indexwriter_merge_milliseconds",
+              SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+                  .label(CATEGORY_ATTR.toString(), SolrInfoBean.Category.INDEX.toString())
+                  .label(MERGE_TYPE_ATTR.toString(), "major")
+                  .build());
+      // major merge timer should have a value of 0, and because 0 values are not reported, no
+      // datapoint is available
+      assertNull("majorMergeTimer", majorMergeTimer);
+
+      // check detailed meters
+      var majorMergeDocs =
+          SolrMetricTestUtils.getCounterDatapoint(
+              core,
+              "solr_indexwriter_major_merged_docs",
+              SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+                  .label(CATEGORY_ATTR.toString(), SolrInfoBean.Category.INDEX.toString())
+                  .build());
+      // major merge docs should be null because mergeDetails is false
+      assertNull("majorMergeDocs", majorMergeDocs);
+
+      var flushCounter =
+          SolrMetricTestUtils.getCounterDatapoint(
+              core,
+              "solr_indexwriter_flush",
+              SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+                  .label(CATEGORY_ATTR.toString(), SolrInfoBean.Category.INDEX.toString())
+                  .build());
+      assertTrue("flush: " + flushCounter.getValue(), flushCounter.getValue() > 10);
+    }
   }
 
   @Test
@@ -119,27 +140,120 @@ public class SolrIndexMetricsTest extends SolrTestCaseJ4 {
 
     addDocs();
 
-    MetricRegistry registry =
-        h.getCoreContainer()
-            .getMetricManager()
-            .registry(h.getCore().getCoreMetricManager().getRegistryName());
-    assertNotNull(registry);
+    try (SolrCore core = h.getCoreContainer().getCore("collection1")) {
+      var prometheusMetricReader = SolrMetricTestUtils.getPrometheusMetricReader(core);
+      assertNotNull(prometheusMetricReader);
+      MetricSnapshots otelMetrics = prometheusMetricReader.collect();
+      assertTrue("Metrics count: " + otelMetrics.size(), otelMetrics.size() >= 19);
+
+      // check basic index meters
+      var minorMergeTimer =
+          SolrMetricTestUtils.getHistogramDatapoint(
+              core,
+              "solr_indexwriter_merge_milliseconds",
+              SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+                  .label(CATEGORY_ATTR.toString(), SolrInfoBean.Category.INDEX.toString())
+                  .label(MERGE_TYPE_ATTR.toString(), "minor")
+                  .build());
+      assertTrue("minorMergeTimer: " + minorMergeTimer.getCount(), minorMergeTimer.getCount() >= 3);
+      var majorMergeTimer =
+          SolrMetricTestUtils.getHistogramDatapoint(
+              core,
+              "solr_indexwriter_merge_milliseconds",
+              SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+                  .label(CATEGORY_ATTR.toString(), SolrInfoBean.Category.INDEX.toString())
+                  .label(MERGE_TYPE_ATTR.toString(), "major")
+                  .build());
+      // major merge timer should have a value of 0, and because 0 values are not reported, no
+      // datapoint is available
+      assertNull("majorMergeTimer", majorMergeTimer);
+
+      // check detailed meters
+      var majorMergeDocs =
+          SolrMetricTestUtils.getCounterDatapoint(
+              core,
+              "solr_indexwriter_major_merged_docs",
+              SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+                  .label(CATEGORY_ATTR.toString(), SolrInfoBean.Category.INDEX.toString())
+                  .build());
+      // major merge docs should have a value of 0, and because 0 values are not reported, no
+      // datapoint is available
+      assertNull("majorMergeDocs", majorMergeDocs);
+
+      var flushCounter =
+          SolrMetricTestUtils.getCounterDatapoint(
+              core,
+              "solr_indexwriter_flush",
+              SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+                  .label(CATEGORY_ATTR.toString(), SolrInfoBean.Category.INDEX.toString())
+                  .build());
+      assertTrue("flush: " + flushCounter.getValue(), flushCounter.getValue() > 10);
+    }
+  }
+
+  public void testIndexMetricsMajorAndMinorMergesWithDetails() throws Exception {
+    System.setProperty("solr.tests.metrics.merge", "false"); // test mergeDetails override too
+    System.setProperty("solr.tests.metrics.mergeDetails", "true");
+    System.setProperty("solr.tests.metrics.majorMergeDocs", "450");
+    initCore("solrconfig-indexmetrics.xml", "schema.xml");
+
+    addDocs();
+
+    try (SolrCore core = h.getCoreContainer().getCore("collection1")) {
+      var prometheusMetricReader = SolrMetricTestUtils.getPrometheusMetricReader(core);
+      assertNotNull(prometheusMetricReader);
+      MetricSnapshots otelMetrics = prometheusMetricReader.collect();
+      assertTrue("Metrics count: " + otelMetrics.size(), otelMetrics.size() >= 18);
 
-    Map<String, Metric> metrics = registry.getMetrics();
+      // addDocs() adds 1000 documents and then sends a commit.  maxBufferedDocs==100,
+      // segmentsPerTier==3,
+      //     maxMergeAtOnce==3 and majorMergeDocs==450.  Thus, new documents form segments with 100
+      // docs, merges are
+      //     called for when there are 3 segments at the lowest tier, and the merges are as follows:
+      //     1. 100 + 100 + 100 ==> new 300 doc segment, below the 450 threshold ==> minor merge
+      //     2. 100 + 100 + 100 ==> new 300 doc segment, below the 450 threshold ==> minor merge
+      //     3. 300 + 100 + 100 ==> new 500 doc segment, above the 450 threshold ==> major merge
+      //     4. 300 + 100 + 100 ==> new 500 doc segment, above the 450 threshold ==> major merge
 
-    assertTrue(
-        metrics.entrySet().stream().filter(e -> e.getKey().startsWith("INDEX")).count() >= 12);
+      // check basic index meters
+      var minorMergeTimer =
+          SolrMetricTestUtils.getHistogramDatapoint(
+              core,
+              "solr_indexwriter_merge_milliseconds",
+              SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+                  .label(CATEGORY_ATTR.toString(), SolrInfoBean.Category.INDEX.toString())
+                  .label(MERGE_TYPE_ATTR.toString(), "minor")
+                  .build());
+      assertTrue("minorMergeTimer: " + minorMergeTimer.getCount(), minorMergeTimer.getCount() == 2);
+      var majorMergeTimer =
+          SolrMetricTestUtils.getHistogramDatapoint(
+              core,
+              "solr_indexwriter_merge_milliseconds",
+              SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+                  .label(CATEGORY_ATTR.toString(), SolrInfoBean.Category.INDEX.toString())
+                  .label(MERGE_TYPE_ATTR.toString(), "major")
+                  .build());
+      assertTrue("majorMergeTimer: " + majorMergeTimer.getCount(), majorMergeTimer.getCount() == 2);
 
-    // check basic index meters
-    Timer timer = (Timer) metrics.get("INDEX.merge.minor");
-    assertTrue("minorMerge: " + timer.getCount(), timer.getCount() >= 3);
-    timer = (Timer) metrics.get("INDEX.merge.major");
-    assertEquals("majorMerge: " + timer.getCount(), 0, timer.getCount());
-    // check detailed meters
-    Meter meter = (Meter) metrics.get("INDEX.merge.major.docs");
-    assertEquals("majorMergeDocs: " + meter.getCount(), 0, meter.getCount());
+      // check detailed meters
+      var majorMergeDocs =
+          SolrMetricTestUtils.getCounterDatapoint(
+              core,
+              "solr_indexwriter_major_merged_docs",
+              SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+                  .label(CATEGORY_ATTR.toString(), SolrInfoBean.Category.INDEX.toString())
+                  .build());
+      // majorMergeDocs is the total number of docs merged during major merge operations
+      assertTrue("majorMergeDocs: " + majorMergeDocs.getValue(), majorMergeDocs.getValue() == 1000);
 
-    meter = (Meter) metrics.get("INDEX.flush");
-    assertTrue("flush: " + meter.getCount(), meter.getCount() > 10);
+      var flushCounter =
+          SolrMetricTestUtils.getCounterDatapoint(
+              core,
+              "solr_indexwriter_flush",
+              SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+                  .label(CATEGORY_ATTR.toString(), SolrInfoBean.Category.INDEX.toString())
+                  .build());
+      assertTrue("flush: " + flushCounter.getValue(), flushCounter.getValue() >= 10);
+    }
   }
 }


(solr) 04/33: Change up requestHandlerBase (#3415)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit fcfb995949ee365cfc3ac138223883c3deab0ab7
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Tue Jul 15 08:45:43 2025 -0400

    Change up requestHandlerBase (#3415)
---
 .../apache/solr/handler/RequestHandlerBase.java    | 20 ++++++++-----
 .../solr/handler/RequestHandlerBaseTest.java       | 35 ++++------------------
 2 files changed, 18 insertions(+), 37 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
index 6038db647a3..8d51929f7cd 100644
--- a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
+++ b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
@@ -226,39 +226,45 @@ public abstract class RequestHandlerBase
       var baseRequestMetric =
           solrMetricsContext.longCounter("solr_metrics_core_requests", "HTTP Solr request counts");
 
+      var baseErrorRequestMetric =
+          solrMetricsContext.longCounter(
+              "solr_metrics_core_requests_errors", "HTTP Solr request error counts");
+
       var baseRequestTimeMetric =
           solrMetricsContext.longHistogram(
               "solr_metrics_core_requests_times", "HTTP Solr request times", "ms");
 
       otelRequests =
           new AttributedLongCounter(
-              baseRequestMetric,
-              Attributes.builder().putAll(attributes).put(TYPE_ATTR, "requests").build());
+              baseRequestMetric, Attributes.builder().putAll(attributes).build());
 
       otelNumServerErrors =
           new AttributedLongCounter(
-              baseRequestMetric,
+              baseErrorRequestMetric,
               Attributes.builder()
                   .putAll(attributes)
                   .put(AttributeKey.stringKey("source"), "server")
-                  .put(TYPE_ATTR, "errors")
                   .build());
 
       otelNumClientErrors =
           new AttributedLongCounter(
-              baseRequestMetric,
+              baseErrorRequestMetric,
               Attributes.builder()
                   .putAll(attributes)
                   .put(AttributeKey.stringKey("source"), "client")
-                  .put(TYPE_ATTR, "errors")
                   .build());
 
       otelNumTimeouts =
           new AttributedLongCounter(
-              baseRequestMetric,
+              baseErrorRequestMetric,
               Attributes.builder().putAll(attributes).put(TYPE_ATTR, "timeouts").build());
 
       otelRequestTimes = new AttributedLongTimer(baseRequestTimeMetric, attributes);
+      // NOCOMMIT: Temporary to see metrics
+      otelRequests.add(0L);
+      otelNumTimeouts.add(0L);
+      otelNumClientErrors.add(0L);
+      otelNumServerErrors.add(0L);
     }
   }
 
diff --git a/solr/core/src/test/org/apache/solr/handler/RequestHandlerBaseTest.java b/solr/core/src/test/org/apache/solr/handler/RequestHandlerBaseTest.java
index 6b23c268556..1ea000903cc 100644
--- a/solr/core/src/test/org/apache/solr/handler/RequestHandlerBaseTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/RequestHandlerBaseTest.java
@@ -17,7 +17,6 @@
 
 package org.apache.solr.handler;
 
-import static org.apache.solr.metrics.SolrMetricProducer.TYPE_ATTR;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
@@ -80,11 +79,7 @@ public class RequestHandlerBaseTest extends SolrTestCaseJ4 {
 
     verify(mockLongCounter, never())
         .add(
-            eq(1L),
-            argThat(
-                attrs ->
-                    "errors".equals(attrs.get(TYPE_ATTR))
-                        && "source".equals(attrs.get(AttributeKey.stringKey("client")))));
+            eq(1L), argThat(attrs -> "source".equals(attrs.get(AttributeKey.stringKey("client")))));
   }
 
   @Test
@@ -95,19 +90,9 @@ public class RequestHandlerBaseTest extends SolrTestCaseJ4 {
     RequestHandlerBase.processErrorMetricsOnException(e, metrics);
 
     verify(mockLongCounter, never())
-        .add(
-            eq(1L),
-            argThat(
-                attrs ->
-                    "errors".equals(attrs.get(TYPE_ATTR))
-                        && "client".equals(attrs.get(SOURCE_ATTR))));
+        .add(eq(1L), argThat(attrs -> "client".equals(attrs.get(SOURCE_ATTR))));
     verify(mockLongCounter, never())
-        .add(
-            eq(1L),
-            argThat(
-                attrs ->
-                    "errors".equals(attrs.get(TYPE_ATTR))
-                        && "server".equals(attrs.get(SOURCE_ATTR))));
+        .add(eq(1L), argThat(attrs -> "server".equals(attrs.get(SOURCE_ATTR))));
   }
 
   @Test
@@ -118,20 +103,10 @@ public class RequestHandlerBaseTest extends SolrTestCaseJ4 {
     RequestHandlerBase.processErrorMetricsOnException(e, metrics);
 
     verify(mockLongCounter, times(1))
-        .add(
-            eq(1L),
-            argThat(
-                attrs ->
-                    "errors".equals(attrs.get(TYPE_ATTR))
-                        && "client".equals(attrs.get(SOURCE_ATTR))));
+        .add(eq(1L), argThat(attrs -> "client".equals(attrs.get(SOURCE_ATTR))));
 
     verify(mockLongCounter, never())
-        .add(
-            eq(1L),
-            argThat(
-                attrs ->
-                    "errors".equals(attrs.get(TYPE_ATTR))
-                        && "server".equals(attrs.get(SOURCE_ATTR))));
+        .add(eq(1L), argThat(attrs -> "server".equals(attrs.get(SOURCE_ATTR))));
   }
 
   @Test


(solr) 18/33: SOLR-17806: OTEL Java 17 Runtime telemetry for JVM metrics (#3494)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit a6b687c8b044350e81ca0324a6f840c1495b70ac
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Fri Aug 29 17:08:20 2025 -0400

    SOLR-17806: OTEL Java 17 Runtime telemetry for JVM metrics (#3494)
    
    * JVM Runtime metrics
    
    * Cleanup
    
    * Log to error
    
    * Remove registry name
    
    ---------
    
    Co-authored-by: Matthew Biscocho <mb...@bloomberg.net>
---
 gradle/libs.versions.toml                          |   2 +
 solr/api/gradle.lockfile                           |   6 ++
 solr/benchmark/gradle.lockfile                     |   6 ++
 solr/core/build.gradle                             |   3 +
 solr/core/gradle.lockfile                          |   6 ++
 .../solr/metrics/AltBufferPoolMetricSet.java       |  46 --------
 .../solr/metrics/OperatingSystemMetricSet.java     |  49 ---------
 .../apache/solr/metrics/OtelRuntimeJvmMetrics.java |  71 +++++++++++++
 .../org/apache/solr/metrics/SolrMetricManager.java |   1 +
 .../apache/solr/servlet/CoreContainerProvider.java |  99 +++---------------
 .../org/apache/solr/metrics/JvmMetricsTest.java    | 116 ++++++---------------
 solr/cross-dc-manager/gradle.lockfile              |   6 ++
 solr/modules/analysis-extras/gradle.lockfile       |   6 ++
 solr/modules/clustering/gradle.lockfile            |   6 ++
 solr/modules/cross-dc/gradle.lockfile              |   6 ++
 solr/modules/extraction/gradle.lockfile            |   6 ++
 solr/modules/gcs-repository/gradle.lockfile        |   6 ++
 solr/modules/jwt-auth/gradle.lockfile              |   6 ++
 solr/modules/langid/gradle.lockfile                |   6 ++
 solr/modules/llm/gradle.lockfile                   |   6 ++
 solr/modules/ltr/gradle.lockfile                   |   6 ++
 solr/modules/opentelemetry/gradle.lockfile         |   6 ++
 solr/modules/s3-repository/gradle.lockfile         |   6 ++
 solr/modules/scripting/gradle.lockfile             |   6 ++
 solr/modules/sql/gradle.lockfile                   |   6 ++
 solr/prometheus-exporter/gradle.lockfile           |   6 ++
 .../exporter/SolrExporterIntegrationTest.java      |  10 --
 .../prometheus/scraper/SolrCloudScraperTest.java   |  19 ----
 solr/server/gradle.lockfile                        |   6 ++
 solr/solr-ref-guide/gradle.lockfile                |   6 ++
 solr/solrj-streaming/gradle.lockfile               |   6 ++
 solr/solrj-zookeeper/gradle.lockfile               |   6 ++
 solr/solrj/gradle.lockfile                         |   6 ++
 .../solr/common/cloud/TestNodesSysPropsCacher.java |   3 +
 solr/test-framework/gradle.lockfile                |   6 ++
 .../org/apache/solr/SolrIgnoredThreadsFilter.java  |   5 +
 .../src/java/org/apache/solr/SolrTestCaseJ4.java   |   1 +
 solr/webapp/gradle.lockfile                        |   6 ++
 38 files changed, 280 insertions(+), 295 deletions(-)

diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 7010e88aefb..9f7d5561da4 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -176,6 +176,7 @@ openapi = "7.14.0"
 openjdk-jmh = "1.37"
 opentelemetry = "1.53.0"
 opentelemetry-prometheus = "1.50.0-alpha"
+opentelemetry-runtime-telemetry = "2.11.0-alpha"
 osgi-annotation = "8.1.0"
 oshai-logging = "7.0.13"
 # @keep for version alignment
@@ -484,6 +485,7 @@ opentelemetry-bom = { module = "io.opentelemetry:opentelemetry-bom", version.ref
 opentelemetry-context = { module = "io.opentelemetry:opentelemetry-context", version.ref = "opentelemetry" }
 opentelemetry-exporter-otlp = { module = "io.opentelemetry:opentelemetry-exporter-otlp", version.ref = "opentelemetry" }
 opentelemetry-exporter-prometheus = { module = "io.opentelemetry:opentelemetry-exporter-prometheus", version.ref = "opentelemetry-prometheus" }
+opentelemetry-runtime-telemetry = { module = "io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17", version.ref = "opentelemetry-runtime-telemetry" }
 opentelemetry-sdk = { module = "io.opentelemetry:opentelemetry-sdk", version.ref = "opentelemetry" }
 opentelemetry-sdkextension-autoconfigure = { module = "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure", version.ref = "opentelemetry" }
 opentelemetry-sdktesting = { module = "io.opentelemetry:opentelemetry-sdk-testing", version.ref = "opentelemetry" }
diff --git a/solr/api/gradle.lockfile b/solr/api/gradle.lockfile
index 69b2fdd9e91..a106d8086a8 100644
--- a/solr/api/gradle.lockfile
+++ b/solr/api/gradle.lockfile
@@ -58,6 +58,12 @@ io.netty:netty-transport-classes-epoll:4.2.6.Final=jarValidation,testCompileClas
 io.netty:netty-transport-native-epoll:4.2.6.Final=jarValidation,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport-native-unix-common:4.2.6.Final=jarValidation,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport:4.2.6.Final=jarValidation,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=jarValidation,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=jarValidation,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=jarValidation,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=jarValidation,testRuntimeClasspath
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=jarValidation,testRuntimeClasspath
 io.opentelemetry:opentelemetry-api:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/benchmark/gradle.lockfile b/solr/benchmark/gradle.lockfile
index 1e2a048bb5b..9f5ff8f297e 100644
--- a/solr/benchmark/gradle.lockfile
+++ b/solr/benchmark/gradle.lockfile
@@ -53,6 +53,12 @@ io.netty:netty-transport-classes-epoll:4.2.6.Final=compileClasspath,jarValidatio
 io.netty:netty-transport-native-epoll:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport-native-unix-common:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=jarValidation,runtimeClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-api:1.53.0=compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/core/build.gradle b/solr/core/build.gradle
index 75fe8b62f2e..12502f94c16 100644
--- a/solr/core/build.gradle
+++ b/solr/core/build.gradle
@@ -166,6 +166,9 @@ dependencies {
     exclude group: "io.opentelemetry", module: "opentelemetry-sdk-logs"
   })
 
+  // JVM Runtime Telemetry metrics
+  implementation libs.opentelemetry.runtime.telemetry
+
   implementation libs.apache.commons.exec
 
   implementation libs.apache.log4j.api
diff --git a/solr/core/gradle.lockfile b/solr/core/gradle.lockfile
index 5cb899ad1b5..199ca89b88f 100644
--- a/solr/core/gradle.lockfile
+++ b/solr/core/gradle.lockfile
@@ -58,6 +58,12 @@ io.netty:netty-transport-classes-epoll:4.2.6.Final=apiHelper,compileClasspath,ja
 io.netty:netty-transport-native-epoll:4.2.6.Final=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport-native-unix-common:4.2.6.Final=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport:4.2.6.Final=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-api:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/core/src/java/org/apache/solr/metrics/AltBufferPoolMetricSet.java b/solr/core/src/java/org/apache/solr/metrics/AltBufferPoolMetricSet.java
deleted file mode 100644
index 50ed0646c52..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/AltBufferPoolMetricSet.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics;
-
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Metric;
-import com.codahale.metrics.MetricSet;
-import java.lang.management.BufferPoolMXBean;
-import java.lang.management.ManagementFactory;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * This is an alternative implementation of {@link com.codahale.metrics.jvm.BufferPoolMetricSet}
- * that doesn't need an MBean server.
- */
-public class AltBufferPoolMetricSet implements MetricSet {
-
-  @Override
-  public Map<String, Metric> getMetrics() {
-    final Map<String, Metric> metrics = new HashMap<>();
-    List<BufferPoolMXBean> pools = ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class);
-    for (final BufferPoolMXBean pool : pools) {
-      String name = pool.getName();
-      metrics.put(name + ".Count", (Gauge<Long>) () -> pool.getCount());
-      metrics.put(name + ".MemoryUsed", (Gauge<Long>) () -> pool.getMemoryUsed());
-      metrics.put(name + ".TotalCapacity", (Gauge<Long>) () -> pool.getTotalCapacity());
-    }
-    return metrics;
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/OperatingSystemMetricSet.java b/solr/core/src/java/org/apache/solr/metrics/OperatingSystemMetricSet.java
deleted file mode 100644
index 5024843fe4e..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/OperatingSystemMetricSet.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics;
-
-import com.codahale.metrics.Metric;
-import com.codahale.metrics.MetricSet;
-import java.lang.management.ManagementFactory;
-import java.lang.management.OperatingSystemMXBean;
-import java.util.HashMap;
-import java.util.Map;
-import org.apache.solr.util.stats.MetricUtils;
-
-/**
- * This is an extended replacement for {@link com.codahale.metrics.jvm.FileDescriptorRatioGauge} -
- * that class uses reflection and doesn't work under Java 9. This implementation tries to retrieve
- * bean properties from known implementations of {@link java.lang.management.OperatingSystemMXBean}.
- */
-public class OperatingSystemMetricSet implements MetricSet {
-
-  @Override
-  public Map<String, Metric> getMetrics() {
-    final Map<String, Metric> metrics = new HashMap<>();
-    OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean();
-    MetricUtils.addMXBeanMetrics(
-        os,
-        MetricUtils.OS_MXBEAN_CLASSES,
-        null,
-        (k, v) -> {
-          if (!metrics.containsKey(k)) {
-            metrics.put(k, v);
-          }
-        });
-    return metrics;
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/OtelRuntimeJvmMetrics.java b/solr/core/src/java/org/apache/solr/metrics/OtelRuntimeJvmMetrics.java
new file mode 100644
index 00000000000..1711142a447
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/metrics/OtelRuntimeJvmMetrics.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.metrics;
+
+import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetrics;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import java.lang.invoke.MethodHandles;
+import org.apache.solr.common.util.EnvUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Manages JVM metrics collection using OpenTelemetry Runtime Metrics with JFR features */
+public class OtelRuntimeJvmMetrics {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  // Main feature flag to enable/disable all JVM metrics
+  private static final String JVM_METRICS_ENABLED = "solr.metrics.jvm.enabled";
+
+  private RuntimeMetrics runtimeMetrics;
+  private boolean isInitialized = false;
+
+  public OtelRuntimeJvmMetrics initialize(
+      SolrMetricManager solrMetricManager, String registryName) {
+    if (!EnvUtils.getPropertyAsBool(JVM_METRICS_ENABLED, true)) {
+      log.info("JVM metrics collection is disabled");
+      return this;
+    }
+    this.runtimeMetrics =
+        RuntimeMetrics.builder(
+                OpenTelemetrySdk.builder()
+                    .setMeterProvider(solrMetricManager.meterProvider(registryName))
+                    .build())
+            // TODO: We should have this configurable to enable/disable specific JVM metrics
+            .enableAllFeatures()
+            .build();
+    isInitialized = true;
+    log.info("JVM metrics collection successfully initialized");
+    return this;
+  }
+
+  public void close() {
+    if (runtimeMetrics != null && isInitialized) {
+      try {
+        runtimeMetrics.close();
+      } catch (Exception e) {
+        log.error("Failed to close JVM metrics collection", e);
+      } finally {
+        runtimeMetrics = null;
+        isInitialized = false;
+      }
+    }
+  }
+
+  public boolean isInitialized() {
+    return isInitialized && runtimeMetrics != null;
+  }
+}
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
index a950e1520fb..e2dd4f75158 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
@@ -1080,6 +1080,7 @@ public class SolrMetricManager {
         context, registry, new GaugeWrapper<>(gauge, tag), strategy, metricName, metricPath);
   }
 
+  // NOCOMMIT: No longer need
   public int unregisterGauges(String registryName, String tagSegment) {
     if (tagSegment == null) {
       return 0;
diff --git a/solr/core/src/java/org/apache/solr/servlet/CoreContainerProvider.java b/solr/core/src/java/org/apache/solr/servlet/CoreContainerProvider.java
index 6624363bf40..3097f2fcb35 100644
--- a/solr/core/src/java/org/apache/solr/servlet/CoreContainerProvider.java
+++ b/solr/core/src/java/org/apache/solr/servlet/CoreContainerProvider.java
@@ -23,11 +23,6 @@ import static org.apache.solr.servlet.SolrDispatchFilter.SOLR_INSTALL_DIR_ATTRIB
 import static org.apache.solr.servlet.SolrDispatchFilter.SOLR_LOG_LEVEL;
 import static org.apache.solr.servlet.SolrDispatchFilter.SOLR_LOG_MUTECONSOLE;
 
-import com.codahale.metrics.jvm.CachedThreadStatesGaugeSet;
-import com.codahale.metrics.jvm.ClassLoadingGaugeSet;
-import com.codahale.metrics.jvm.GarbageCollectorMetricSet;
-import com.codahale.metrics.jvm.MemoryUsageGaugeSet;
-import com.codahale.metrics.jvm.ThreadStatesGaugeSet;
 import com.google.common.annotations.VisibleForTesting;
 import jakarta.servlet.ServletContext;
 import jakarta.servlet.ServletContextEvent;
@@ -42,7 +37,6 @@ import java.util.Arrays;
 import java.util.Locale;
 import java.util.Optional;
 import java.util.Properties;
-import java.util.concurrent.TimeUnit;
 import java.util.stream.Stream;
 import javax.naming.Context;
 import javax.naming.InitialContext;
@@ -57,16 +51,12 @@ import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.cloud.SolrZkClient;
 import org.apache.solr.common.util.EnvUtils;
 import org.apache.solr.core.CoreContainer;
-import org.apache.solr.core.MetricsConfig;
 import org.apache.solr.core.NodeConfig;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrInfoBean.Group;
 import org.apache.solr.core.SolrXmlConfig;
-import org.apache.solr.metrics.AltBufferPoolMetricSet;
-import org.apache.solr.metrics.MetricsMap;
-import org.apache.solr.metrics.OperatingSystemMetricSet;
+import org.apache.solr.metrics.OtelRuntimeJvmMetrics;
 import org.apache.solr.metrics.SolrMetricManager;
-import org.apache.solr.metrics.SolrMetricManager.ResolutionStrategy;
 import org.apache.solr.metrics.SolrMetricProducer;
 import org.apache.solr.servlet.RateLimitManager.Builder;
 import org.apache.solr.util.StartupLoggingUtils;
@@ -85,7 +75,7 @@ public class CoreContainerProvider implements ServletContextListener {
   private Properties extraProperties;
   private SolrMetricManager metricManager;
   private RateLimitManager rateLimitManager;
-  private String registryName;
+  private OtelRuntimeJvmMetrics otelRuntimeJvmMetrics;
 
   /**
    * Acquires an instance from the context. Never null.
@@ -152,23 +142,11 @@ public class CoreContainerProvider implements ServletContextListener {
     //    }
 
     cores = null;
-    try {
-      if (metricManager != null) {
-        try {
-          metricManager.unregisterGauges(registryName, metricTag);
-        } catch (NullPointerException e) {
-          // okay
-        } catch (Exception e) {
-          log.warn("Exception closing FileCleaningTracker", e);
-        } finally {
-          metricManager = null;
-        }
-      }
-    } finally {
-      if (cc != null) {
-        cc.shutdown();
-      }
+    if (otelRuntimeJvmMetrics != null) otelRuntimeJvmMetrics.close();
+    if (cc != null) {
+      cc.shutdown();
     }
+    metricManager = null;
   }
 
   private void init(ServletContext servletContext) {
@@ -217,7 +195,7 @@ public class CoreContainerProvider implements ServletContextListener {
               });
 
       coresInit = createCoreContainer(computeSolrHome(servletContext), extraProperties);
-      setupJvmMetrics(coresInit, coresInit.getNodeConfig().getMetricsConfig());
+      setupJvmMetrics(coresInit);
 
       SolrZkClient zkClient = null;
       ZkController zkController = coresInit.getZkController();
@@ -397,64 +375,11 @@ public class CoreContainerProvider implements ServletContextListener {
     return coreContainer;
   }
 
-  private void setupJvmMetrics(CoreContainer coresInit, MetricsConfig config) {
-    metricManager = coresInit.getMetricManager();
-    registryName = SolrMetricManager.getRegistryName(Group.jvm);
-    final NodeConfig nodeConfig = coresInit.getConfig();
-    try {
-      metricManager.registerAll(
-          registryName, new AltBufferPoolMetricSet(), ResolutionStrategy.IGNORE, "buffers");
-      metricManager.registerAll(
-          registryName, new ClassLoadingGaugeSet(), ResolutionStrategy.IGNORE, "classes");
-      metricManager.registerAll(
-          registryName, new OperatingSystemMetricSet(), ResolutionStrategy.IGNORE, "os");
-      metricManager.registerAll(
-          registryName, new GarbageCollectorMetricSet(), ResolutionStrategy.IGNORE, "gc");
-      metricManager.registerAll(
-          registryName, new MemoryUsageGaugeSet(), ResolutionStrategy.IGNORE, "memory");
-
-      if (config.getCacheConfig() != null
-          && config.getCacheConfig().threadsIntervalSeconds != null) {
-        if (log.isInfoEnabled()) {
-          log.info(
-              "Threads metrics will be cached for {} seconds",
-              config.getCacheConfig().threadsIntervalSeconds);
-        }
-        metricManager.registerAll(
-            registryName,
-            new CachedThreadStatesGaugeSet(
-                config.getCacheConfig().threadsIntervalSeconds, TimeUnit.SECONDS),
-            SolrMetricManager.ResolutionStrategy.IGNORE,
-            "threads");
-      } else {
-        metricManager.registerAll(
-            registryName,
-            new ThreadStatesGaugeSet(),
-            SolrMetricManager.ResolutionStrategy.IGNORE,
-            "threads");
-      }
-
-      MetricsMap sysprops =
-          new MetricsMap(
-              map ->
-                  System.getProperties()
-                      .forEach(
-                          (k, v) -> {
-                            if (!nodeConfig.isSysPropHidden(String.valueOf(k))) {
-                              map.putNoEx(String.valueOf(k), v);
-                            }
-                          }));
-      metricManager.registerGauge(
-          null,
-          registryName,
-          sysprops,
-          metricTag,
-          ResolutionStrategy.IGNORE,
-          "properties",
-          "system");
-    } catch (Exception e) {
-      log.warn("Error registering JVM metrics", e);
-    }
+  private void setupJvmMetrics(CoreContainer coresInit) {
+    this.metricManager = coresInit.getMetricManager();
+    this.otelRuntimeJvmMetrics =
+        new OtelRuntimeJvmMetrics()
+            .initialize(metricManager, SolrMetricManager.getRegistryName(Group.jvm));
   }
 
   public RateLimitManager getRateLimitManager() {
diff --git a/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java b/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java
index 014ea19e6f5..5bc20b0d09e 100644
--- a/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java
@@ -16,20 +16,20 @@
  */
 package org.apache.solr.metrics;
 
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Metric;
+import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
+import io.prometheus.metrics.model.snapshots.MetricSnapshots;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Arrays;
-import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
 import org.apache.solr.SolrJettyTestBase;
 import org.apache.solr.core.NodeConfig;
 import org.apache.solr.core.SolrXmlConfig;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-/** Test {@link OperatingSystemMetricSet} and proper JVM metrics registration. */
 public class JvmMetricsTest extends SolrJettyTestBase {
   static final String[] STRING_OS_METRICS = {"arch", "name", "version"};
   static final String[] NUMERIC_OS_METRICS = {"availableProcessors", "systemLoadAverage"};
@@ -45,71 +45,10 @@ public class JvmMetricsTest extends SolrJettyTestBase {
 
   @BeforeClass
   public static void beforeTest() throws Exception {
+    System.setProperty("solr.metrics.jvm.enabled", "true");
     createAndStartJetty(legacyExampleCollection1SolrHome());
   }
 
-  @Test
-  public void testOperatingSystemMetricSet() {
-    OperatingSystemMetricSet set = new OperatingSystemMetricSet();
-    Map<String, Metric> metrics = set.getMetrics();
-    assertTrue(metrics.size() > 0);
-    for (String metric : NUMERIC_OS_METRICS) {
-      Gauge<?> gauge = (Gauge<?>) metrics.get(metric);
-      assertNotNull(metric, gauge);
-      double value = ((Number) gauge.getValue()).doubleValue();
-      // SystemLoadAverage on Windows may be -1.0
-      assertTrue("unexpected value of " + metric + ": " + value, value >= 0 || value == -1.0);
-    }
-    for (String metric : STRING_OS_METRICS) {
-      Gauge<?> gauge = (Gauge<?>) metrics.get(metric);
-      assertNotNull(metric, gauge);
-      String value = (String) gauge.getValue();
-      assertNotNull(value);
-      assertFalse(value.isEmpty());
-    }
-  }
-
-  @Test
-  public void testAltBufferPoolMetricSet() {
-    AltBufferPoolMetricSet set = new AltBufferPoolMetricSet();
-    Map<String, Metric> metrics = set.getMetrics();
-    assertTrue(metrics.size() > 0);
-    for (String name : BUFFER_METRICS) {
-      assertNotNull(name, metrics.get(name));
-      Object g = metrics.get(name);
-      assertTrue(g instanceof Gauge);
-      Object v = ((Gauge<?>) g).getValue();
-      assertTrue(v instanceof Long);
-    }
-  }
-
-  @Test
-  public void testSystemProperties() {
-    if (System.getProperty("solr.security.auth.basicauth.credentials") == null) {
-      // make sure it's set
-      System.setProperty("solr.security.auth.basicauth.credentials", "foo:bar");
-    }
-    SolrMetricManager metricManager = getJetty().getCoreContainer().getMetricManager();
-    Map<String, Metric> metrics = metricManager.registry("solr.jvm").getMetrics();
-    Metric metric = metrics.get("system.properties");
-    assertNotNull(metrics.toString(), metric);
-    MetricsMap map = (MetricsMap) ((SolrMetricManager.GaugeWrapper<?>) metric).getGauge();
-    assertNotNull(metrics.toString(), map);
-    Map<String, Object> values = map.getValue();
-    System.getProperties()
-        .forEach(
-            (k, v) -> {
-              if (NodeConfig.NodeConfigBuilder.DEFAULT_HIDDEN_SYS_PROPS.contains(k)) {
-                assertNull("hidden property " + k + " present!", values.get(k));
-              } else if (k == "solr.security.auth.basicauth.credentials") {
-                // DEFAULT_HIDDEN_SYS_PROPS.contains doesn't match a partial pattern.
-                assertNull("hidden property " + k + " present!", values.get(k));
-              } else {
-                assertEquals(v, values.get(String.valueOf(k)));
-              }
-            });
-  }
-
   @Test
   public void testHiddenSysProps() throws Exception {
     Path home = TEST_PATH();
@@ -128,29 +67,38 @@ public class JvmMetricsTest extends SolrJettyTestBase {
 
   @Test
   public void testSetupJvmMetrics() {
-    SolrMetricManager metricManager = getJetty().getCoreContainer().getMetricManager();
-    Map<String, Metric> metrics = metricManager.registry("solr.jvm").getMetrics();
-    assertTrue(metrics.size() > 0);
-    assertTrue(
-        metrics.toString(),
-        metrics.entrySet().stream().anyMatch(e -> e.getKey().startsWith("buffers.")));
+    PrometheusMetricReader reader =
+        getJetty().getCoreContainer().getMetricManager().getPrometheusMetricReader("solr.jvm");
+    MetricSnapshots snapshots = reader.collect();
+    assertTrue("Should have metric snapshots", snapshots.size() > 0);
+
+    Set<String> metricNames =
+        snapshots.stream()
+            .map(metric -> metric.getMetadata().getPrometheusName())
+            .collect(Collectors.toSet());
+
     assertTrue(
-        metrics.toString(),
-        metrics.entrySet().stream().anyMatch(e -> e.getKey().startsWith("classes.")));
+        "Should have JVM memory metrics",
+        metricNames.stream().anyMatch(name -> name.startsWith("jvm_memory")));
+
     assertTrue(
-        metrics.toString(),
-        metrics.entrySet().stream().anyMatch(e -> e.getKey().startsWith("os.")));
+        "Should have JVM thread metrics",
+        metricNames.stream().anyMatch(name -> name.startsWith("jvm_thread")));
+
     assertTrue(
-        metrics.toString(),
-        metrics.entrySet().stream().anyMatch(e -> e.getKey().startsWith("gc.")));
+        "Should have JVM class metrics",
+        metricNames.stream().anyMatch(name -> name.startsWith("jvm_class")));
+
     assertTrue(
-        metrics.toString(),
-        metrics.entrySet().stream().anyMatch(e -> e.getKey().startsWith("memory.")));
+        "Should have JVM CPU metrics",
+        metricNames.stream().anyMatch(name -> name.startsWith("jvm_cpu")));
+
     assertTrue(
-        metrics.toString(),
-        metrics.entrySet().stream().anyMatch(e -> e.getKey().startsWith("threads.")));
+        "Should have JVM GC metrics",
+        metricNames.stream().anyMatch(name -> name.startsWith("jvm_gc")));
+
     assertTrue(
-        metrics.toString(),
-        metrics.entrySet().stream().anyMatch(e -> e.getKey().startsWith("system.")));
+        "Should have JVM buffer metrics",
+        metricNames.stream().anyMatch(name -> name.startsWith("jvm_buffer")));
   }
 }
diff --git a/solr/cross-dc-manager/gradle.lockfile b/solr/cross-dc-manager/gradle.lockfile
index ad7df219e1d..9f2a5c5cb62 100644
--- a/solr/cross-dc-manager/gradle.lockfile
+++ b/solr/cross-dc-manager/gradle.lockfile
@@ -70,6 +70,12 @@ io.netty:netty-transport-classes-epoll:4.2.6.Final=compileClasspath,jarValidatio
 io.netty:netty-transport-native-epoll:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport-native-unix-common:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-api:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/modules/analysis-extras/gradle.lockfile b/solr/modules/analysis-extras/gradle.lockfile
index 0e367394f21..ddf54fa8334 100644
--- a/solr/modules/analysis-extras/gradle.lockfile
+++ b/solr/modules/analysis-extras/gradle.lockfile
@@ -55,6 +55,12 @@ io.netty:netty-transport-classes-epoll:4.2.6.Final=apiHelper,compileClasspath,ja
 io.netty:netty-transport-native-epoll:4.2.6.Final=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport-native-unix-common:4.2.6.Final=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport:4.2.6.Final=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-api:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/modules/clustering/gradle.lockfile b/solr/modules/clustering/gradle.lockfile
index 373d5ebb9fa..1cdd529ed37 100644
--- a/solr/modules/clustering/gradle.lockfile
+++ b/solr/modules/clustering/gradle.lockfile
@@ -53,6 +53,12 @@ io.netty:netty-transport-classes-epoll:4.2.6.Final=compileClasspath,jarValidatio
 io.netty:netty-transport-native-epoll:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport-native-unix-common:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-api:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/modules/cross-dc/gradle.lockfile b/solr/modules/cross-dc/gradle.lockfile
index 631a766e08e..5388ff79f4f 100644
--- a/solr/modules/cross-dc/gradle.lockfile
+++ b/solr/modules/cross-dc/gradle.lockfile
@@ -54,6 +54,12 @@ io.netty:netty-transport-classes-epoll:4.2.6.Final=apiHelper,compileClasspath,ja
 io.netty:netty-transport-native-epoll:4.2.6.Final=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport-native-unix-common:4.2.6.Final=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport:4.2.6.Final=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-api:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/modules/extraction/gradle.lockfile b/solr/modules/extraction/gradle.lockfile
index 27722aff9a7..b8f5d733a27 100644
--- a/solr/modules/extraction/gradle.lockfile
+++ b/solr/modules/extraction/gradle.lockfile
@@ -81,6 +81,12 @@ io.netty:netty-transport-classes-epoll:4.2.6.Final=compileClasspath,jarValidatio
 io.netty:netty-transport-native-epoll:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport-native-unix-common:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-api:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/modules/gcs-repository/gradle.lockfile b/solr/modules/gcs-repository/gradle.lockfile
index 1ab934dabc1..e22fe9840ac 100644
--- a/solr/modules/gcs-repository/gradle.lockfile
+++ b/solr/modules/gcs-repository/gradle.lockfile
@@ -103,6 +103,12 @@ io.netty:netty-transport:4.2.6.Final=apiHelper,compileClasspath,jarValidation,ru
 io.opencensus:opencensus-api:0.31.1=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 io.opencensus:opencensus-contrib-http-util:0.31.1=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 io.opencensus:opencensus-proto:0.2.0=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-api:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/modules/jwt-auth/gradle.lockfile b/solr/modules/jwt-auth/gradle.lockfile
index dd6f9dc774e..e44398aeeba 100644
--- a/solr/modules/jwt-auth/gradle.lockfile
+++ b/solr/modules/jwt-auth/gradle.lockfile
@@ -67,6 +67,12 @@ io.netty:netty-transport-classes-epoll:4.2.6.Final=compileClasspath,jarValidatio
 io.netty:netty-transport-native-epoll:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport-native-unix-common:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-api:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/modules/langid/gradle.lockfile b/solr/modules/langid/gradle.lockfile
index 8144d8517fb..ec261f84302 100644
--- a/solr/modules/langid/gradle.lockfile
+++ b/solr/modules/langid/gradle.lockfile
@@ -54,6 +54,12 @@ io.netty:netty-transport-classes-epoll:4.2.6.Final=compileClasspath,jarValidatio
 io.netty:netty-transport-native-epoll:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport-native-unix-common:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-api:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/modules/llm/gradle.lockfile b/solr/modules/llm/gradle.lockfile
index 74861b226f9..5f301fcb1d5 100644
--- a/solr/modules/llm/gradle.lockfile
+++ b/solr/modules/llm/gradle.lockfile
@@ -68,6 +68,12 @@ io.netty:netty-transport-classes-epoll:4.2.6.Final=compileClasspath,jarValidatio
 io.netty:netty-transport-native-epoll:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport-native-unix-common:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-api:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/modules/ltr/gradle.lockfile b/solr/modules/ltr/gradle.lockfile
index 6fa8ed49485..1c05c0fdc00 100644
--- a/solr/modules/ltr/gradle.lockfile
+++ b/solr/modules/ltr/gradle.lockfile
@@ -53,6 +53,12 @@ io.netty:netty-transport-classes-epoll:4.2.6.Final=compileClasspath,jarValidatio
 io.netty:netty-transport-native-epoll:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport-native-unix-common:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-api:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/modules/opentelemetry/gradle.lockfile b/solr/modules/opentelemetry/gradle.lockfile
index ca7e5eb6f9f..182700eea53 100644
--- a/solr/modules/opentelemetry/gradle.lockfile
+++ b/solr/modules/opentelemetry/gradle.lockfile
@@ -75,6 +75,12 @@ io.netty:netty-transport-classes-epoll:4.2.6.Final=compileClasspath,jarValidatio
 io.netty:netty-transport-native-epoll:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport-native-unix-common:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-api:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-bom:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/modules/s3-repository/gradle.lockfile b/solr/modules/s3-repository/gradle.lockfile
index 714f2e35b96..2f0dbdb4b09 100644
--- a/solr/modules/s3-repository/gradle.lockfile
+++ b/solr/modules/s3-repository/gradle.lockfile
@@ -70,6 +70,12 @@ io.netty:netty-transport-classes-epoll:4.2.6.Final=apiHelper,compileClasspath,ja
 io.netty:netty-transport-native-epoll:4.2.6.Final=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport-native-unix-common:4.2.6.Final=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport:4.2.6.Final=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-api:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/modules/scripting/gradle.lockfile b/solr/modules/scripting/gradle.lockfile
index 55897192931..5ad8e1ed9af 100644
--- a/solr/modules/scripting/gradle.lockfile
+++ b/solr/modules/scripting/gradle.lockfile
@@ -53,6 +53,12 @@ io.netty:netty-transport-classes-epoll:4.2.6.Final=compileClasspath,jarValidatio
 io.netty:netty-transport-native-epoll:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport-native-unix-common:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-api:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/modules/sql/gradle.lockfile b/solr/modules/sql/gradle.lockfile
index baf17e02dd0..f7d2ba572f6 100644
--- a/solr/modules/sql/gradle.lockfile
+++ b/solr/modules/sql/gradle.lockfile
@@ -56,6 +56,12 @@ io.netty:netty-transport-classes-epoll:4.2.6.Final=compileClasspath,jarValidatio
 io.netty:netty-transport-native-epoll:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport-native-unix-common:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-api:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/prometheus-exporter/gradle.lockfile b/solr/prometheus-exporter/gradle.lockfile
index 0fd347ff573..1549a84a17a 100644
--- a/solr/prometheus-exporter/gradle.lockfile
+++ b/solr/prometheus-exporter/gradle.lockfile
@@ -53,6 +53,12 @@ io.netty:netty-transport-classes-epoll:4.2.6.Final=compileClasspath,jarValidatio
 io.netty:netty-transport-native-epoll:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport-native-unix-common:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=jarValidation,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=jarValidation,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=jarValidation,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=jarValidation,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=jarValidation,solrPlatformLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=jarValidation,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-api:1.53.0=jarValidation,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=jarValidation,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=jarValidation,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/prometheus-exporter/src/test/org/apache/solr/prometheus/exporter/SolrExporterIntegrationTest.java b/solr/prometheus-exporter/src/test/org/apache/solr/prometheus/exporter/SolrExporterIntegrationTest.java
index 4620a08eae1..08b9e62d144 100644
--- a/solr/prometheus-exporter/src/test/org/apache/solr/prometheus/exporter/SolrExporterIntegrationTest.java
+++ b/solr/prometheus-exporter/src/test/org/apache/solr/prometheus/exporter/SolrExporterIntegrationTest.java
@@ -61,16 +61,6 @@ public class SolrExporterIntegrationTest extends SolrExporterTestBase {
     assertEquals(18, durationHistogram.size());
   }
 
-  @Test
-  public void jvmMetrics() throws Exception {
-    Map<String, Double> jvmMetrics = metricsWithName(getAllMetrics(), "solr_metrics_jvm_threads");
-
-    // exact set of metrics can vary based on JVM impl (ie: windows)
-    // but there should always be at least one per known thread state per node...
-    assertTrue(
-        jvmMetrics.toString(), (NUM_NODES * Thread.State.values().length) < jvmMetrics.size());
-  }
-
   @Test
   public void jsonFacetMetrics() throws Exception {
     Map<String, Double> facetMetrics = metricsWithName(getAllMetrics(), "solr_facets_category");
diff --git a/solr/prometheus-exporter/src/test/org/apache/solr/prometheus/scraper/SolrCloudScraperTest.java b/solr/prometheus-exporter/src/test/org/apache/solr/prometheus/scraper/SolrCloudScraperTest.java
index 386b1252e04..1265314fb9e 100644
--- a/solr/prometheus-exporter/src/test/org/apache/solr/prometheus/scraper/SolrCloudScraperTest.java
+++ b/solr/prometheus-exporter/src/test/org/apache/solr/prometheus/scraper/SolrCloudScraperTest.java
@@ -172,25 +172,6 @@ public class SolrCloudScraperTest extends PrometheusExporterTestBase {
     }
   }
 
-  @Test
-  public void metricsForEachHost() throws Exception {
-    Map<String, MetricSamples> metricsByHost =
-        solrCloudScraper.metricsForAllHosts(configuration.getMetricsConfiguration().get(0));
-
-    List<Replica> replicas = getCollectionState().getReplicas();
-    assertEquals(replicas.size(), metricsByHost.size());
-
-    for (Replica replica : replicas) {
-      List<Collector.MetricFamilySamples> replicaSamples =
-          metricsByHost.get(replica.getBaseUrl()).asList();
-      assertEquals(1, replicaSamples.size());
-      assertEquals("solr_metrics_jvm_buffers", replicaSamples.get(0).name);
-
-      assertEquals("cluster_id", replicaSamples.get(0).samples.get(0).labelNames.get(2));
-      assertEquals("test", replicaSamples.get(0).samples.get(0).labelValues.get(2));
-    }
-  }
-
   @Test
   public void search() throws Exception {
     List<Collector.MetricFamilySamples> samples =
diff --git a/solr/server/gradle.lockfile b/solr/server/gradle.lockfile
index 64ba9349292..98f5c97cb24 100644
--- a/solr/server/gradle.lockfile
+++ b/solr/server/gradle.lockfile
@@ -52,6 +52,12 @@ io.netty:netty-transport-classes-epoll:4.2.6.Final=jarValidation,runtimeClasspat
 io.netty:netty-transport-native-epoll:4.2.6.Final=jarValidation,runtimeClasspath,solrCore
 io.netty:netty-transport-native-unix-common:4.2.6.Final=jarValidation,runtimeClasspath,solrCore
 io.netty:netty-transport:4.2.6.Final=jarValidation,runtimeClasspath,solrCore
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=jarValidation,runtimeClasspath,solrCore
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=jarValidation,runtimeClasspath,solrCore
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=jarValidation,runtimeClasspath,solrCore
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=jarValidation,runtimeClasspath,solrCore
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=jarValidation,runtimeClasspath,solrCore
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=jarValidation,runtimeClasspath,solrCore
 io.opentelemetry:opentelemetry-api:1.53.0=jarValidation,runtimeClasspath,solrCore
 io.opentelemetry:opentelemetry-common:1.53.0=jarValidation,runtimeClasspath,solrCore
 io.opentelemetry:opentelemetry-context:1.53.0=jarValidation,runtimeClasspath,solrCore
diff --git a/solr/solr-ref-guide/gradle.lockfile b/solr/solr-ref-guide/gradle.lockfile
index 633346d95ec..ea526d855d7 100644
--- a/solr/solr-ref-guide/gradle.lockfile
+++ b/solr/solr-ref-guide/gradle.lockfile
@@ -52,6 +52,12 @@ io.netty:netty-transport-classes-epoll:4.2.6.Final=testCompileClasspath,testRunt
 io.netty:netty-transport-native-epoll:4.2.6.Final=testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport-native-unix-common:4.2.6.Final=testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport:4.2.6.Final=testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=testRuntimeClasspath
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=testRuntimeClasspath
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=testRuntimeClasspath
 io.opentelemetry:opentelemetry-api:1.53.0=testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=testCompileClasspath,testRuntimeClasspath
diff --git a/solr/solrj-streaming/gradle.lockfile b/solr/solrj-streaming/gradle.lockfile
index 4e558d1d9b8..51cd7f859cf 100644
--- a/solr/solrj-streaming/gradle.lockfile
+++ b/solr/solrj-streaming/gradle.lockfile
@@ -62,6 +62,12 @@ io.netty:netty-transport-native-unix-common:4.1.119.Final=permitTestUnusedDeclar
 io.netty:netty-transport-native-unix-common:4.2.6.Final=jarValidation,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport:4.1.119.Final=permitTestUnusedDeclared
 io.netty:netty-transport:4.2.6.Final=jarValidation,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=jarValidation,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=jarValidation,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=jarValidation,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=jarValidation,testRuntimeClasspath
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=jarValidation,testRuntimeClasspath
 io.opentelemetry:opentelemetry-api:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/solrj-zookeeper/gradle.lockfile b/solr/solrj-zookeeper/gradle.lockfile
index 1d8f75cde5a..e9d944d9340 100644
--- a/solr/solrj-zookeeper/gradle.lockfile
+++ b/solr/solrj-zookeeper/gradle.lockfile
@@ -67,6 +67,12 @@ io.netty:netty-transport-native-unix-common:4.1.119.Final=apiHelper
 io.netty:netty-transport-native-unix-common:4.2.6.Final=compileClasspath,jarValidation,permitTestUsedUndeclared,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport:4.1.119.Final=apiHelper
 io.netty:netty-transport:4.2.6.Final=compileClasspath,jarValidation,permitTestUsedUndeclared,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=jarValidation,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=jarValidation,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=jarValidation,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=jarValidation,testRuntimeClasspath
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=jarValidation,testRuntimeClasspath
 io.opentelemetry:opentelemetry-api:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/solrj/gradle.lockfile b/solr/solrj/gradle.lockfile
index 904c15c7034..56efd7d9a9c 100644
--- a/solr/solrj/gradle.lockfile
+++ b/solr/solrj/gradle.lockfile
@@ -62,6 +62,12 @@ io.netty:netty-transport-native-unix-common:4.1.119.Final=permitTestUnusedDeclar
 io.netty:netty-transport-native-unix-common:4.2.6.Final=jarValidation,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport:4.1.119.Final=permitTestUnusedDeclared
 io.netty:netty-transport:4.2.6.Final=jarValidation,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=jarValidation,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=jarValidation,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=jarValidation,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=jarValidation,testRuntimeClasspath
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=jarValidation,testRuntimeClasspath
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=jarValidation,testRuntimeClasspath
 io.opentelemetry:opentelemetry-api:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=jarValidation,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/solrj/src/test/org/apache/solr/common/cloud/TestNodesSysPropsCacher.java b/solr/solrj/src/test/org/apache/solr/common/cloud/TestNodesSysPropsCacher.java
index 489de336a89..fbdfe7c002b 100644
--- a/solr/solrj/src/test/org/apache/solr/common/cloud/TestNodesSysPropsCacher.java
+++ b/solr/solrj/src/test/org/apache/solr/common/cloud/TestNodesSysPropsCacher.java
@@ -28,6 +28,9 @@ import org.junit.Test;
 public class TestNodesSysPropsCacher extends SolrCloudTestCase {
 
   @Test
+  // NOCOMMIT: These metrics were system properties that must be retrieved somewhere else that isn't
+  // the /admin/metrics endpoint
+  @AwaitsFix(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17458")
   public void testSysProps() throws Exception {
     System.setProperty("metricsEnabled", "true");
     MiniSolrCloudCluster cluster =
diff --git a/solr/test-framework/gradle.lockfile b/solr/test-framework/gradle.lockfile
index 7c581367523..1c87cc8e6db 100644
--- a/solr/test-framework/gradle.lockfile
+++ b/solr/test-framework/gradle.lockfile
@@ -54,6 +54,12 @@ io.netty:netty-transport-classes-epoll:4.2.6.Final=apiHelper,compileClasspath,ja
 io.netty:netty-transport-native-epoll:4.2.6.Final=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport-native-unix-common:4.2.6.Final=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 io.netty:netty-transport:4.2.6.Final=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-api:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-common:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/test-framework/src/java/org/apache/solr/SolrIgnoredThreadsFilter.java b/solr/test-framework/src/java/org/apache/solr/SolrIgnoredThreadsFilter.java
index b9d31b8b604..153bacba9ac 100644
--- a/solr/test-framework/src/java/org/apache/solr/SolrIgnoredThreadsFilter.java
+++ b/solr/test-framework/src/java/org/apache/solr/SolrIgnoredThreadsFilter.java
@@ -83,6 +83,11 @@ public class SolrIgnoredThreadsFilter implements ThreadFilter {
       return true;
     }
 
+    // JVM JFR (Java Flight Recorder) periodic tasks thread
+    if (threadName.equals("JFR Periodic Tasks")) {
+      return true;
+    }
+
     return threadName.startsWith("closeThreadPool");
   }
 }
diff --git a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
index 53e1915f7c2..769c7d7bf13 100644
--- a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
+++ b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
@@ -282,6 +282,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
     System.setProperty("solr.cloud.wait-for-updates-with-stale-state-pause", "500");
     System.setProperty("solr.filterCache.async", String.valueOf(random().nextBoolean()));
     System.setProperty("solr.http.disableCookies", Boolean.toString(rarely()));
+    System.setProperty("solr.metrics.jvm.enabled", "false");
 
     startTrackingSearchers();
     ignoreException("ignore_exception");
diff --git a/solr/webapp/gradle.lockfile b/solr/webapp/gradle.lockfile
index 5c45b55f9c7..2358a1e548d 100644
--- a/solr/webapp/gradle.lockfile
+++ b/solr/webapp/gradle.lockfile
@@ -52,6 +52,12 @@ io.netty:netty-transport-classes-epoll:4.2.6.Final=permitUnusedDeclared,solrCore
 io.netty:netty-transport-native-epoll:4.2.6.Final=permitUnusedDeclared,solrCore
 io.netty:netty-transport-native-unix-common:4.2.6.Final=permitUnusedDeclared,solrCore
 io.netty:netty-transport:4.2.6.Final=permitUnusedDeclared,solrCore
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.11.0-alpha=permitUnusedDeclared,solrCore
+io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.11.0=permitUnusedDeclared,solrCore
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17:2.11.0-alpha=permitUnusedDeclared,solrCore
+io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:2.11.0-alpha=permitUnusedDeclared,solrCore
+io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=permitUnusedDeclared,solrCore
+io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=permitUnusedDeclared,solrCore
 io.opentelemetry:opentelemetry-api:1.53.0=permitUnusedDeclared,solrCore
 io.opentelemetry:opentelemetry-common:1.53.0=permitUnusedDeclared,solrCore
 io.opentelemetry:opentelemetry-context:1.53.0=permitUnusedDeclared,solrCore


(solr) 09/33: SOLR-17806: Migrate UpdateLog metrics to OTEL (#3438)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 2c2183d3239e805c93afe315e5cd131c7b2a1f7b
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Wed Aug 6 10:33:08 2025 -0400

    SOLR-17806: Migrate UpdateLog metrics to OTEL (#3438)
    
    * Init migration
    
    * Better test cleanup
    
    * Change to OPERATION_ATTR
    
    * Clean up switch
---
 .../src/java/org/apache/solr/update/UpdateLog.java | 118 ++++++++++++++-------
 .../org/apache/solr/cloud/TestTlogReplica.java     |  13 ++-
 .../test/org/apache/solr/search/TestRecovery.java  |  82 +++++++++-----
 3 files changed, 140 insertions(+), 73 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/update/UpdateLog.java b/solr/core/src/java/org/apache/solr/update/UpdateLog.java
index 3d379c5d808..bcb4cde925f 100644
--- a/solr/core/src/java/org/apache/solr/update/UpdateLog.java
+++ b/solr/core/src/java/org/apache/solr/update/UpdateLog.java
@@ -21,8 +21,7 @@ import static org.apache.solr.update.processor.DistributingUpdateProcessorFactor
 
 import com.carrotsearch.hppc.LongHashSet;
 import com.carrotsearch.hppc.LongSet;
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Meter;
+import io.opentelemetry.api.common.AttributeKey;
 import io.opentelemetry.api.common.Attributes;
 import java.io.Closeable;
 import java.io.IOException;
@@ -80,6 +79,7 @@ import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.core.SolrPaths;
 import org.apache.solr.metrics.SolrMetricProducer;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.instruments.AttributedLongCounter;
 import org.apache.solr.request.LocalSolrQueryRequest;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestInfo;
@@ -264,10 +264,9 @@ public class UpdateLog implements PluginInfoInitialized, SolrMetricProducer {
   protected List<Long> startingVersions;
 
   // metrics
-  protected Gauge<Integer> bufferedOpsGauge;
-  protected Meter applyingBufferedOpsMeter;
-  protected Meter replayOpsMeter;
-  protected Meter copyOverOldUpdatesMeter;
+  protected AttributedLongCounter applyingBufferedOpsCounter;
+  protected AttributedLongCounter replayOpsCounter;
+  protected AttributedLongCounter copyOverOldUpdatesCounter;
   protected SolrMetricsContext solrMetricsContext;
 
   public static class LogPtr {
@@ -627,40 +626,65 @@ public class UpdateLog implements PluginInfoInitialized, SolrMetricProducer {
     }
   }
 
-  // TODO SOLR-17458: Migrate to Otel
   @Override
   public void initializeMetrics(
       SolrMetricsContext parentContext, Attributes attributes, String scope) {
     solrMetricsContext = parentContext.getChildContext(this);
-    bufferedOpsGauge =
-        () -> {
-          if (state == State.BUFFERING) {
-            if (bufferTlog == null) return 0;
-            // numRecords counts header as a record
-            return bufferTlog.numRecords() - 1;
-          }
-          if (tlog == null) {
-            return 0;
-          } else if (state == State.APPLYING_BUFFERED) {
-            // numRecords counts header as a record
-            return tlog.numRecords()
-                - 1
-                - recoveryInfo.adds
-                - recoveryInfo.deleteByQuery
-                - recoveryInfo.deletes
-                - recoveryInfo.errors.get();
-          } else {
-            return 0;
-          }
-        };
 
-    solrMetricsContext.gauge(bufferedOpsGauge, true, "ops", scope, "buffered");
-    solrMetricsContext.gauge(() -> logs.size(), true, "logs", scope, "replay", "remaining");
-    solrMetricsContext.gauge(() -> getTotalLogsSize(), true, "bytes", scope, "replay", "remaining");
-    applyingBufferedOpsMeter = solrMetricsContext.meter("ops", scope, "applyingBuffered");
-    replayOpsMeter = solrMetricsContext.meter("ops", scope, "replay");
-    copyOverOldUpdatesMeter = solrMetricsContext.meter("ops", scope, "copyOverOldUpdates");
-    solrMetricsContext.gauge(() -> state.getValue(), true, "state", scope);
+    // NOCOMMIT: We do not need a scope attribute
+    // Will need to fix up SolrCoreMetricManager instead of directly removing it from builder.
+    var baseAttributes =
+        attributes.toBuilder()
+            .remove(AttributeKey.stringKey("scope"))
+            .put(CATEGORY_ATTR, SolrInfoBean.Category.TLOG.toString())
+            .build();
+
+    solrMetricsContext.observableLongGauge(
+        "solr_core_update_log_buffered_ops",
+        "The current number of buffered operations",
+        (observableLongMeasurement ->
+            observableLongMeasurement.record(computeBufferedOps(), baseAttributes)));
+
+    solrMetricsContext.observableLongGauge(
+        "solr_core_update_log_replay_logs_remaining",
+        "The current number of tlogs remaining to be replayed",
+        (observableLongMeasurement -> {
+          observableLongMeasurement.record(logs.size(), baseAttributes);
+        }));
+
+    solrMetricsContext.observableLongGauge(
+        "solr_core_update_log_size_remaining",
+        "The total size in bytes of all tlogs remaining to be replayed",
+        (observableLongMeasurement -> {
+          observableLongMeasurement.record(getTotalLogsSize(), baseAttributes);
+        }),
+        "By");
+
+    // NOCOMMIT: Do we want to keep this? Metric was just state with the numeric enum value.
+    // Without context this doesn't mean anything and can be very confusing. Maybe keep the numeric
+    // value and put the value meaning in the
+    // description?
+    //    solrMetricsContext.gauge(() -> state.getValue(), true, "state", scope);
+
+    applyingBufferedOpsCounter =
+        new AttributedLongCounter(
+            solrMetricsContext.longCounter(
+                "solr_core_update_log_applied_buffered_ops",
+                "Total number of buffered operations applied"),
+            baseAttributes);
+
+    replayOpsCounter =
+        new AttributedLongCounter(
+            solrMetricsContext.longCounter(
+                "solr_core_update_log_replay_ops", "Total number of log replay operations"),
+            baseAttributes);
+
+    copyOverOldUpdatesCounter =
+        new AttributedLongCounter(
+            solrMetricsContext.longCounter(
+                "solr_core_update_log_old_updates_copied",
+                "Total number of updates copied from previous tlog or last tlog to a new tlog"),
+            baseAttributes);
   }
 
   @Override
@@ -668,6 +692,24 @@ public class UpdateLog implements PluginInfoInitialized, SolrMetricProducer {
     return solrMetricsContext;
   }
 
+  private long computeBufferedOps() {
+    return switch (state) {
+        // numRecords counts header as a record
+      case BUFFERING -> (bufferTlog == null ? 0 : bufferTlog.numRecords() - 1);
+      case APPLYING_BUFFERED -> {
+        if (tlog == null) yield 0;
+        // numRecords counts header as a record
+        yield tlog.numRecords()
+            - 1
+            - recoveryInfo.adds
+            - recoveryInfo.deleteByQuery
+            - recoveryInfo.deletes
+            - recoveryInfo.errors.get();
+      }
+      default -> 0;
+    };
+  }
+
   /**
    * Returns a new {@link org.apache.solr.update.TransactionLog}. Sub-classes can override this
    * method to change the implementation of the transaction log.
@@ -1509,7 +1551,7 @@ public class UpdateLog implements PluginInfoInitialized, SolrMetricProducer {
    *     over
    */
   public void copyOverOldUpdates(long commitVersion, TransactionLog oldTlog) {
-    copyOverOldUpdatesMeter.mark();
+    copyOverOldUpdatesCounter.inc();
 
     SolrQueryRequest req = new LocalSolrQueryRequest(uhandler.core, new ModifiableSolrParams());
     TransactionLog.LogReader logReader = null;
@@ -2290,9 +2332,9 @@ public class UpdateLog implements PluginInfoInitialized, SolrMetricProducer {
               throw rsp.getException();
             }
             if (state == State.REPLAYING) {
-              replayOpsMeter.mark();
+              replayOpsCounter.inc();
             } else if (state == State.APPLYING_BUFFERED) {
-              applyingBufferedOpsMeter.mark();
+              applyingBufferedOpsCounter.inc();
             } else {
               // XXX should not happen?
             }
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestTlogReplica.java b/solr/core/src/test/org/apache/solr/cloud/TestTlogReplica.java
index 7c375f9e0ac..11308e89b5d 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestTlogReplica.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestTlogReplica.java
@@ -19,7 +19,6 @@ package org.apache.solr.cloud;
 import static org.apache.solr.cloud.TestPullReplica.getHypotheticalTlogDir;
 
 import com.carrotsearch.randomizedtesting.annotations.Repeat;
-import com.codahale.metrics.Meter;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.nio.file.Files;
@@ -1239,11 +1238,11 @@ public class TestTlogReplica extends SolrCloudTestCase {
   }
 
   private long getTimesCopyOverOldUpdates(SolrCore core) {
-    return ((Meter)
-            core.getSolrMetricsContext()
-                .getMetricRegistry()
-                .getMetrics()
-                .get("TLOG.copyOverOldUpdates.ops"))
-        .getCount();
+    var metric =
+        SolrMetricTestUtils.getCounterDatapoint(
+            core,
+            "solr_core_update_log_old_updates_copied",
+            SolrMetricTestUtils.newCloudLabelsBuilder(core).label("category", "TLOG").build());
+    return (metric != null) ? (long) metric.getValue() : 0L;
   }
 }
diff --git a/solr/core/src/test/org/apache/solr/search/TestRecovery.java b/solr/core/src/test/org/apache/solr/search/TestRecovery.java
index ec2dead4da4..b4ad7161251 100644
--- a/solr/core/src/test/org/apache/solr/search/TestRecovery.java
+++ b/solr/core/src/test/org/apache/solr/search/TestRecovery.java
@@ -20,8 +20,6 @@ import static org.apache.solr.search.TestRecovery.VersionProvider.getNextVersion
 import static org.apache.solr.update.processor.DistributingUpdateProcessorFactory.DISTRIB_UPDATE_PARAM;
 import static org.apache.solr.util.SolrMatchers.subListMatches;
 
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Meter;
 import com.codahale.metrics.Metric;
 import com.codahale.metrics.MetricRegistry;
 import java.io.RandomAccessFile;
@@ -46,6 +44,7 @@ import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.util.TimeSource;
 import org.apache.solr.common.util.Utils;
 import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.metrics.SolrMetricTestUtils;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.update.UpdateHandler;
@@ -230,8 +229,6 @@ public class TestRecovery extends SolrTestCaseJ4 {
       h.close();
       createCore();
 
-      Map<String, Metric> metrics = getMetrics(); // live map view
-
       // Solr should kick this off now
       // h.getCore().getUpdateHandler().getUpdateLog().recoverFromLog();
 
@@ -244,18 +241,27 @@ public class TestRecovery extends SolrTestCaseJ4 {
 
       assertEquals(
           UpdateLog.State.REPLAYING, h.getCore().getUpdateHandler().getUpdateLog().getState());
+
+      var attributes =
+          SolrMetricTestUtils.newStandaloneLabelsBuilder(h.getCore())
+              .label("category", "TLOG")
+              .build();
+
       // check metrics
-      @SuppressWarnings({"unchecked"})
-      Gauge<Integer> state = (Gauge<Integer>) metrics.get("TLOG.state");
-      assertEquals(UpdateLog.State.REPLAYING.ordinal(), state.getValue().intValue());
-      @SuppressWarnings({"unchecked"})
-      Gauge<Integer> replayingLogs = (Gauge<Integer>) metrics.get("TLOG.replay.remaining.logs");
-      assertTrue(replayingLogs.getValue() > 0);
-      @SuppressWarnings({"unchecked"})
-      Gauge<Long> replayingDocs = (Gauge<Long>) metrics.get("TLOG.replay.remaining.bytes");
-      assertTrue(replayingDocs.getValue() > 0);
-      Meter replayDocs = (Meter) metrics.get("TLOG.replay.ops");
-      long initialOps = replayDocs.getCount();
+      // NOCOMMIT: Need to decide if we are keeping this state metric or not
+      //      @SuppressWarnings({"unchecked"})
+      //      Gauge<Integer> state = (Gauge<Integer>) metrics.get("TLOG.state");
+      //      assertEquals(UpdateLog.State.REPLAYING.ordinal(), state.getValue().intValue());
+
+      var actualReplayingLogs =
+          SolrMetricTestUtils.getGaugeDatapoint(
+              h.getCore(), "solr_core_update_log_replay_logs_remaining", attributes);
+      assertTrue(actualReplayingLogs.getValue() > 0);
+
+      var actualReplayingDocs =
+          SolrMetricTestUtils.getGaugeDatapoint(
+              h.getCore(), "solr_core_update_log_size_remaining_bytes", attributes);
+      assertTrue(actualReplayingDocs.getValue() > 0);
 
       // unblock recovery
       logReplay.release(1000);
@@ -271,8 +277,14 @@ public class TestRecovery extends SolrTestCaseJ4 {
 
       assertJQ(req("q", "*:*"), "/response/numFound==3");
 
-      assertEquals(7L, replayDocs.getCount() - initialOps);
-      assertEquals(UpdateLog.State.ACTIVE.ordinal(), state.getValue().intValue());
+      var actualReplayOps =
+          SolrMetricTestUtils.getCounterDatapoint(
+                  h.getCore(), "solr_core_update_log_replay_ops", attributes)
+              .getValue();
+      assertEquals(7.0, actualReplayOps, 0.0);
+
+      // NOCOMMIT: Need to decide if we are keeping this state metric or not
+      //      assertEquals(UpdateLog.State.ACTIVE.ordinal(), state.getValue().intValue());
 
       // make sure we can still access versions after recovery
       assertJQ(req("qt", "/get", "getVersions", "" + versions.size()), "/versions==" + versions);
@@ -662,14 +674,16 @@ public class TestRecovery extends SolrTestCaseJ4 {
 
       ulog.bufferUpdates();
       assertEquals(UpdateLog.State.BUFFERING, ulog.getState());
-      @SuppressWarnings({"unchecked"})
-      Gauge<Integer> state = (Gauge<Integer>) metrics.get("TLOG.state");
-      assertEquals(UpdateLog.State.BUFFERING.ordinal(), state.getValue().intValue());
-      @SuppressWarnings({"unchecked"})
-      Gauge<Integer> bufferedOps = (Gauge<Integer>) metrics.get("TLOG.buffered.ops");
-      int initialOps = bufferedOps.getValue();
-      Meter applyingBuffered = (Meter) metrics.get("TLOG.applyingBuffered.ops");
-      long initialApplyingOps = applyingBuffered.getCount();
+
+      var attributes =
+          SolrMetricTestUtils.newStandaloneLabelsBuilder(h.getCore())
+              .label("category", "TLOG")
+              .build();
+
+      // NOCOMMIT: Need to decide if we are keeping this state metric or not
+      //      @SuppressWarnings({"unchecked"})
+      //      Gauge<Integer> state = (Gauge<Integer>) metrics.get("TLOG.state");
+      //      assertEquals(UpdateLog.State.BUFFERING.ordinal(), state.getValue().intValue());
 
       String v3 = getNextVersion();
       String v940_del = "-" + getNextVersion();
@@ -722,7 +736,11 @@ public class TestRecovery extends SolrTestCaseJ4 {
       // be bad for updates to be visible if we're just buffering.
       assertJQ(req("qt", "/get", "id", "B3"), "=={'doc':null}");
 
-      assertEquals(6, bufferedOps.getValue() - initialOps);
+      var actualBufferedOpsValue =
+          SolrMetricTestUtils.getGaugeDatapoint(
+                  h.getCore(), "solr_core_update_log_buffered_ops", attributes)
+              .getValue();
+      assertEquals(6, actualBufferedOpsValue, 0.0);
 
       rinfoFuture = ulog.applyBufferedUpdates();
       assertNotNull(rinfoFuture);
@@ -734,7 +752,11 @@ public class TestRecovery extends SolrTestCaseJ4 {
       UpdateLog.RecoveryInfo rinfo = rinfoFuture.get();
       assertEquals(UpdateLog.State.ACTIVE, ulog.getState());
 
-      assertEquals(6L, applyingBuffered.getCount() - initialApplyingOps);
+      var actualAppliedBufferedOpsValue =
+          SolrMetricTestUtils.getCounterDatapoint(
+                  h.getCore(), "solr_core_update_log_applied_buffered_ops", attributes)
+              .getValue();
+      assertEquals(6, actualAppliedBufferedOpsValue, 0.0);
 
       assertThatJQ(
           req("qt", "/get", "getVersions", "6"),
@@ -868,7 +890,11 @@ public class TestRecovery extends SolrTestCaseJ4 {
       assertEquals(
           UpdateLog.State.ACTIVE, ulog.getState()); // leave each test method in a good state
 
-      assertEquals(0, bufferedOps.getValue().intValue());
+      actualBufferedOpsValue =
+          SolrMetricTestUtils.getGaugeDatapoint(
+                  h.getCore(), "solr_core_update_log_buffered_ops", attributes)
+              .getValue();
+      assertEquals(0, actualBufferedOpsValue, 0.0);
     } finally {
       UpdateLog.testing_logReplayHook = null;
       UpdateLog.testing_logReplayFinishHook = null;


(solr) 12/33: SOLR-17806: Migrate ZkContainer metrics to OTEL (#3453)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 42c9b415738ea8092d9e4f3690d875055d0e940c
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Mon Aug 11 14:36:33 2025 -0400

    SOLR-17806: Migrate ZkContainer metrics to OTEL (#3453)
    
    * Migrate ZkContainer metrics
    
    * Fix naming
---
 .../java/org/apache/solr/core/CoreContainer.java   |  7 +-
 .../src/java/org/apache/solr/core/ZkContainer.java | 73 +++++++++++++++--
 .../apache/solr/handler/RequestHandlerBase.java    |  5 --
 .../java/org/apache/solr/metrics/MetricsMap.java   |  3 +
 .../apache/solr/cloud/PeerSyncReplicationTest.java |  4 +-
 .../solr/cloud/TestRandomRequestDistribution.java  |  2 +-
 .../solr/opentelemetry/TestMetricExemplars.java    |  6 +-
 .../solr/common/cloud/SolrZKMetricsListener.java   | 93 +++++++++++++---------
 .../org/apache/solr/common/cloud/SolrZkClient.java |  3 +-
 9 files changed, 136 insertions(+), 60 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index 59e5fe8c561..cd6bf740b36 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -26,6 +26,7 @@ import static org.apache.solr.common.params.CommonParams.INFO_HANDLER_PATH;
 import static org.apache.solr.common.params.CommonParams.METRICS_PATH;
 import static org.apache.solr.common.params.CommonParams.ZK_PATH;
 import static org.apache.solr.common.params.CommonParams.ZK_STATUS_PATH;
+import static org.apache.solr.metrics.SolrMetricProducer.CATEGORY_ATTR;
 import static org.apache.solr.metrics.SolrMetricProducer.HANDLER_ATTR;
 import static org.apache.solr.search.SolrIndexSearcher.EXECUTOR_MAX_CPU_THREADS;
 import static org.apache.solr.security.AuthenticationPlugin.AUTHENTICATION_PLUGIN_PROP;
@@ -817,10 +818,12 @@ public class CoreContainer {
     if (isZooKeeperAware()) {
       solrClientCache.setDefaultZKHost(getZkController().getZkServerAddress());
       // initialize ZkClient metrics
-      // NOCOMMIT SOLR-17458: Add Otel
       zkSys
           .getZkMetricsProducer()
-          .initializeMetrics(solrMetricsContext, Attributes.empty(), "zkClient");
+          .initializeMetrics(
+              solrMetricsContext,
+              Attributes.of(CATEGORY_ATTR, SolrInfoBean.Category.CONTAINER.toString()),
+              "zkClient");
       pkiAuthenticationSecurityBuilder =
           new PKIAuthenticationPlugin(
               this,
diff --git a/solr/core/src/java/org/apache/solr/core/ZkContainer.java b/solr/core/src/java/org/apache/solr/core/ZkContainer.java
index c6a1b79d283..bd675378a1a 100644
--- a/solr/core/src/java/org/apache/solr/core/ZkContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/ZkContainer.java
@@ -39,7 +39,6 @@ import org.apache.solr.common.util.ExecutorUtil;
 import org.apache.solr.common.util.SolrNamedThreadFactory;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.logging.MDCLoggingContext;
-import org.apache.solr.metrics.MetricsMap;
 import org.apache.solr.metrics.SolrMetricProducer;
 import org.apache.solr.metrics.SolrMetricsContext;
 import org.apache.zookeeper.KeeperException;
@@ -141,18 +140,82 @@ public class ZkContainer {
         }
 
         this.zkController = zkController;
-        MetricsMap metricsMap = new MetricsMap(zkController.getZkClient().getMetrics());
+
         metricProducer =
             new SolrMetricProducer() {
               SolrMetricsContext ctx;
 
-              // TODO SOLR-17458: Migrate to Otel
               @Override
               public void initializeMetrics(
                   SolrMetricsContext parentContext, Attributes attributes, String scope) {
                 ctx = parentContext.getChildContext(this);
-                ctx.gauge(
-                    metricsMap, true, scope, null, SolrInfoBean.Category.CONTAINER.toString());
+
+                var metricsListener = zkController.getZkClient().getMetrics();
+
+                ctx.observableLongCounter(
+                    "solr_zk_ops",
+                    "Total number of ZooKeeper operations",
+                    measurement -> {
+                      measurement.record(
+                          metricsListener.getReads(),
+                          attributes.toBuilder().put(OPERATION_ATTR, "read").build());
+                      measurement.record(
+                          metricsListener.getDeletes(),
+                          attributes.toBuilder().put(OPERATION_ATTR, "delete").build());
+                      measurement.record(
+                          metricsListener.getWrites(),
+                          attributes.toBuilder().put(OPERATION_ATTR, "write").build());
+                      measurement.record(
+                          metricsListener.getMultiOps(),
+                          attributes.toBuilder().put(OPERATION_ATTR, "multi").build());
+                      measurement.record(
+                          metricsListener.getExistsChecks(),
+                          attributes.toBuilder().put(OPERATION_ATTR, "exists").build());
+                    });
+
+                ctx.observableLongCounter(
+                    "solr_zk_read",
+                    "Total bytes read from ZooKeeper",
+                    measurement -> {
+                      measurement.record(metricsListener.getBytesRead(), attributes);
+                    },
+                    "By");
+
+                ctx.observableLongCounter(
+                    "solr_zk_watches_fired",
+                    "Total number of ZooKeeper watches fired",
+                    measurement -> {
+                      measurement.record(metricsListener.getWatchesFired(), attributes);
+                    });
+
+                ctx.observableLongCounter(
+                    "solr_zk_written",
+                    "Total bytes written to ZooKeeper",
+                    measurement -> {
+                      measurement.record(metricsListener.getBytesWritten());
+                    },
+                    "By");
+
+                ctx.observableLongCounter(
+                    "solr_zk_cumulative_multi_ops_total",
+                    "Total cumulative multi-operations count",
+                    measurement -> {
+                      measurement.record(metricsListener.getCumulativeMultiOps());
+                    });
+
+                ctx.observableLongCounter(
+                    "solr_zk_child_fetches",
+                    "Total number of ZooKeeper child node fetches",
+                    measurement -> {
+                      measurement.record(metricsListener.getChildFetches());
+                    });
+
+                ctx.observableLongCounter(
+                    "solr_zk_cumulative_children_fetched",
+                    "Total cumulative children fetched count",
+                    measurement -> {
+                      measurement.record(metricsListener.getCumulativeChildrenFetched());
+                    });
               }
 
               @Override
diff --git a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
index 6a70abe9441..610bf338a6f 100644
--- a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
+++ b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
@@ -253,11 +253,6 @@ public abstract class RequestHandlerBase
       numTimeouts = new AttributedLongCounter(timeoutRequestMetric, attributes);
 
       requestTimes = new AttributedLongTimer(requestTimeMetric, attributes);
-      // NOCOMMIT: Temporary to see metrics
-      requests.add(0L);
-      numTimeouts.add(0L);
-      numClientErrors.add(0L);
-      numServerErrors.add(0L);
     }
   }
 
diff --git a/solr/core/src/java/org/apache/solr/metrics/MetricsMap.java b/solr/core/src/java/org/apache/solr/metrics/MetricsMap.java
index aec6f3e352e..1d667cf556b 100644
--- a/solr/core/src/java/org/apache/solr/metrics/MetricsMap.java
+++ b/solr/core/src/java/org/apache/solr/metrics/MetricsMap.java
@@ -55,6 +55,9 @@ import org.slf4j.LoggerFactory;
  * {@link javax.management.openmbean.OpenType#ALLOWED_CLASSNAMES_LIST}, otherwise only their
  * toString() representation will be shown in JConsole.
  */
+// NOCOMMIT: This MetricMap appears to be a shim for MapWriter for responses and a way to shim
+// gauges into Dropwizard and JXM which is being removed. This should be removed completely as it is
+// no longer needed with OTEL
 public class MetricsMap implements Gauge<Map<String, Object>>, MapWriter, DynamicMBean {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
diff --git a/solr/core/src/test/org/apache/solr/cloud/PeerSyncReplicationTest.java b/solr/core/src/test/org/apache/solr/cloud/PeerSyncReplicationTest.java
index 469f3f2cd31..931551b8757 100644
--- a/solr/core/src/test/org/apache/solr/cloud/PeerSyncReplicationTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/PeerSyncReplicationTest.java
@@ -198,7 +198,7 @@ public class PeerSyncReplicationTest extends AbstractFullDistribZkTestBase {
         assertTrue(
             SolrMetricTestUtils.getHistogramDatapoint(
                     core,
-                    "solr_core_leader_sync_time_milliseconds",
+                    "solr_core_sync_with_leader_time_milliseconds",
                     SolrMetricTestUtils.newCloudLabelsBuilder(core)
                         .label("category", "REPLICATION")
                         .build())
@@ -206,7 +206,7 @@ public class PeerSyncReplicationTest extends AbstractFullDistribZkTestBase {
         assertNull(
             SolrMetricTestUtils.getCounterDatapoint(
                 core,
-                "solr_core_leader_sync_errors",
+                "solr_core_sync_with_leader_sync_errors",
                 SolrMetricTestUtils.newCloudLabelsBuilder(core)
                     .label("category", "REPLICATION")
                     .build()));
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestRandomRequestDistribution.java b/solr/core/src/test/org/apache/solr/cloud/TestRandomRequestDistribution.java
index a83da0d2e7b..cf4b41eb150 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestRandomRequestDistribution.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestRandomRequestDistribution.java
@@ -279,7 +279,7 @@ public class TestRandomRequestDistribution extends AbstractFullDistribZkTestBase
 
   private Double getSelectRequestCount(SolrCore core) {
     var labels =
-        SolrMetricTestUtils.getCloudLabelsBase(core)
+        SolrMetricTestUtils.newCloudLabelsBuilder(core)
             .label("category", "QUERY")
             .label("handler", "/select")
             .label("internal", "false")
diff --git a/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestMetricExemplars.java b/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestMetricExemplars.java
index 687b86b20cb..e0660f8559a 100644
--- a/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestMetricExemplars.java
+++ b/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestMetricExemplars.java
@@ -79,14 +79,12 @@ public class TestMetricExemplars extends SolrCloudTestCase {
               .lines()
               .filter(
                   l ->
-                      l.startsWith("solr_metrics_core_requests_total")
+                      l.startsWith("solr_core_requests_total")
                           && l.contains("handler=\"/update\"")
                           && l.contains("collection=\"collection1\""))
               .findFirst()
               .orElseThrow(
-                  () ->
-                      new AssertionError(
-                          "Could not find /update solr_metrics_core_requests_total"));
+                  () -> new AssertionError("Could not find /update solr_core_requests_total"));
 
       String actualExemplarTrace = line.split("trace_id=\"")[1].split("\"")[0];
       assertEquals(actualExemplarTrace, expectedTrace);
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/SolrZKMetricsListener.java b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/SolrZKMetricsListener.java
index e6375b9348a..d8cc8f350b7 100644
--- a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/SolrZKMetricsListener.java
+++ b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/SolrZKMetricsListener.java
@@ -16,7 +16,6 @@
  */
 package org.apache.solr.common.cloud;
 
-import java.io.IOException;
 import java.util.concurrent.atomic.LongAdder;
 import org.apache.curator.drivers.AdvancedTracerDriver;
 import org.apache.curator.drivers.EventTrace;
@@ -24,32 +23,20 @@ import org.apache.curator.drivers.OperationTrace;
 import org.apache.curator.framework.CuratorFramework;
 import org.apache.curator.framework.api.CuratorEvent;
 import org.apache.curator.framework.api.CuratorListener;
-import org.apache.solr.common.MapWriter;
-import org.apache.solr.common.annotation.JsonProperty;
-import org.apache.solr.common.util.ReflectMapWriter;
 
-public class SolrZKMetricsListener extends AdvancedTracerDriver
-    implements ReflectMapWriter, CuratorListener {
-  // all fields of this class are public because ReflectMapWriter requires them to be.
-  // however the object itself is private and only this class can modify it
+public class SolrZKMetricsListener extends AdvancedTracerDriver implements CuratorListener {
 
-  @JsonProperty public final LongAdder watchesFired = new LongAdder();
-  @JsonProperty public final LongAdder reads = new LongAdder();
-  @JsonProperty public final LongAdder writes = new LongAdder();
-  @JsonProperty public final LongAdder bytesRead = new LongAdder();
-  @JsonProperty public final LongAdder bytesWritten = new LongAdder();
-
-  @JsonProperty public final LongAdder multiOps = new LongAdder();
-
-  @JsonProperty public final LongAdder cumulativeMultiOps = new LongAdder();
-
-  @JsonProperty public final LongAdder childFetches = new LongAdder();
-
-  @JsonProperty public final LongAdder cumulativeChildrenFetched = new LongAdder();
-
-  @JsonProperty public final LongAdder existsChecks = new LongAdder();
-
-  @JsonProperty public final LongAdder deletes = new LongAdder();
+  final LongAdder watchesFired = new LongAdder();
+  final LongAdder reads = new LongAdder();
+  final LongAdder writes = new LongAdder();
+  final LongAdder bytesRead = new LongAdder();
+  final LongAdder bytesWritten = new LongAdder();
+  final LongAdder multiOps = new LongAdder();
+  final LongAdder cumulativeMultiOps = new LongAdder();
+  final LongAdder childFetches = new LongAdder();
+  final LongAdder cumulativeChildrenFetched = new LongAdder();
+  final LongAdder existsChecks = new LongAdder();
+  final LongAdder deletes = new LongAdder();
 
   /*
   This is used by curator for all operations, but we will only use it for Foreground operations.
@@ -128,19 +115,47 @@ public class SolrZKMetricsListener extends AdvancedTracerDriver
     }
   }
 
-  @Override
-  public void writeMap(MapWriter.EntryWriter ew) throws IOException {
-    ReflectMapWriter.super.writeMap(
-        new MapWriter.EntryWriter() {
-          @Override
-          public MapWriter.EntryWriter put(CharSequence k, Object v) throws IOException {
-            if (v instanceof LongAdder) {
-              ew.put(k, ((LongAdder) v).longValue());
-            } else {
-              ew.put(k, v);
-            }
-            return this;
-          }
-        });
+  public long getWatchesFired() {
+    return watchesFired.longValue();
+  }
+
+  public long getReads() {
+    return reads.longValue();
+  }
+
+  public long getWrites() {
+    return writes.longValue();
+  }
+
+  public long getBytesRead() {
+    return bytesRead.longValue();
+  }
+
+  public long getBytesWritten() {
+    return bytesWritten.longValue();
+  }
+
+  public long getMultiOps() {
+    return multiOps.longValue();
+  }
+
+  public long getCumulativeMultiOps() {
+    return cumulativeMultiOps.longValue();
+  }
+
+  public long getChildFetches() {
+    return childFetches.longValue();
+  }
+
+  public long getCumulativeChildrenFetched() {
+    return cumulativeChildrenFetched.longValue();
+  }
+
+  public long getExistsChecks() {
+    return existsChecks.longValue();
+  }
+
+  public long getDeletes() {
+    return deletes.longValue();
   }
 }
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/SolrZkClient.java b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/SolrZkClient.java
index 14ec233981d..eaefc7589fc 100644
--- a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/SolrZkClient.java
+++ b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/SolrZkClient.java
@@ -47,7 +47,6 @@ import org.apache.curator.retry.ExponentialBackoffRetry;
 import org.apache.curator.utils.ZKPaths;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.impl.SolrZkClientTimeout;
-import org.apache.solr.common.MapWriter;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.util.Compressor;
 import org.apache.solr.common.util.ExecutorUtil;
@@ -86,7 +85,7 @@ public class SolrZkClient implements Closeable {
   private int minStateByteLenForCompression;
 
   // The metrics collector is shared across all SolrZkClient objects
-  public MapWriter getMetrics() {
+  public SolrZKMetricsListener getMetrics() {
     return metricsListener;
   }
 


(solr) 32/33: SOLR-17798: Integrate SDK OTLP metric exporter (#3413)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit fc3545764a0a8abe8062158f295ad49d7bd8e9c6
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Fri Sep 26 12:10:44 2025 -0400

    SOLR-17798: Integrate SDK OTLP metric exporter (#3413)
    
    * Create dynamic SDK Meter Providers and tests
    
    * Bad merge from feature branch
    
    * Wrap meter providers and metric readers
    
    * Test cleanup
    
    * Cleanup
    
    * Initialize a OTLP exporter
    
    * Add Noop metric expoter
    
    * Add test for exporter
    
    * Create exporter factory
    
    * Drop revert opentelemetry version upgrade
    
    * Add some java docs
    
    * Wrong merged conflicts
    
    * Tidy lines
    
    * Add trace based exemplars
    
    * Move to OpenMetricsTextFormatWriter
    
    * Change context type to open metrics
    
    * Revert "Change context type to open metrics"
    
    This reverts commit b594327aae48c813cf81e36c5e2dfbe3bcb76bc8.
    
    * Revert "Move to OpenMetricsTextFormatWriter"
    
    This reverts commit 96435ebe2ac15b7cc3781c031d1229a1504e1fdc.
    
    * Revert "Add trace based exemplars"
    
    This reverts commit d33a85df3ac5ddb4401f36d9ed3c34f7a02c43fc.
    
    * Move Otlp exporter into Open Telemetry module
    
    * Close meterProviders and readers
    
    * Retry instead of sleep
    
    ---------
    
    Co-authored-by: Matthew Biscocho <mb...@bloomberg.net>
---
 gradle/libs.versions.toml                          |  1 +
 .../java/org/apache/solr/core/CoreContainer.java   |  3 ++
 .../apache/solr/handler/RequestHandlerBase.java    |  8 +--
 .../apache/solr/metrics/SolrCoreMetricManager.java |  1 +
 .../org/apache/solr/metrics/SolrMetricManager.java | 62 +++++++++++++++++++---
 .../solr/metrics/otel/MetricExporterFactory.java   | 34 ++++++++++++
 .../solr/metrics/otel/NoopMetricExporter.java      | 46 ++++++++++++++++
 .../apache/solr/blockcache/BufferStoreTest.java    |  2 +-
 .../apache/solr/metrics/SolrMetricManagerTest.java | 46 +++++++++++++---
 .../solr/metrics/SolrMetricReporterTest.java       |  2 +-
 .../org/apache/solr/search/TestCaffeineCache.java  |  2 +-
 .../org/apache/solr/search/TestSolrCachePerf.java  |  4 +-
 .../test/org/apache/solr/search/TestThinCache.java |  2 +-
 .../stats/OtelInstrumentedExecutorServiceTest.java |  2 +-
 .../handler/MirroringCollectionsHandlerTest.java   |  3 --
 solr/modules/opentelemetry/build.gradle            |  4 +-
 solr/modules/opentelemetry/gradle.lockfile         |  2 +-
 .../solr/opentelemetry/OtlpExporterFactory.java    | 57 ++++++++++++++++++++
 .../src/java/org/apache/solr/SolrTestCaseJ4.java   |  3 +-
 19 files changed, 249 insertions(+), 35 deletions(-)

diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 9f7d5561da4..f07b3cb1f61 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -485,6 +485,7 @@ opentelemetry-bom = { module = "io.opentelemetry:opentelemetry-bom", version.ref
 opentelemetry-context = { module = "io.opentelemetry:opentelemetry-context", version.ref = "opentelemetry" }
 opentelemetry-exporter-otlp = { module = "io.opentelemetry:opentelemetry-exporter-otlp", version.ref = "opentelemetry" }
 opentelemetry-exporter-prometheus = { module = "io.opentelemetry:opentelemetry-exporter-prometheus", version.ref = "opentelemetry-prometheus" }
+opentelemetry-exporter-sender-okhttp = { module = "io.opentelemetry:opentelemetry-exporter-sender-okhttp", version.ref = "opentelemetry" }
 opentelemetry-runtime-telemetry = { module = "io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17", version.ref = "opentelemetry-runtime-telemetry" }
 opentelemetry-sdk = { module = "io.opentelemetry:opentelemetry-sdk", version.ref = "opentelemetry" }
 opentelemetry-sdkextension-autoconfigure = { module = "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure", version.ref = "opentelemetry" }
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index 6590349ad87..064e5ed2aee 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -1362,6 +1362,9 @@ public class CoreContainer {
             SolrMetricManager.getRegistryName(SolrInfoBean.Group.jvm), metricTag);
         metricManager.unregisterGauges(
             SolrMetricManager.getRegistryName(SolrInfoBean.Group.jetty), metricTag);
+
+        // Close all OTEL meter providers and metrics
+        metricManager.closeAllRegistries();
       }
 
       if (isZooKeeperAware()) {
diff --git a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
index d34b482402a..4ac39cda833 100644
--- a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
+++ b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
@@ -37,7 +37,6 @@ import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SuppressForbidden;
 import org.apache.solr.core.CoreContainer;
-import org.apache.solr.core.MetricsConfig;
 import org.apache.solr.core.PluginBag;
 import org.apache.solr.core.PluginInfo;
 import org.apache.solr.core.SolrCore;
@@ -193,11 +192,7 @@ public abstract class RequestHandlerBase
   public static class HandlerMetrics {
     public static final HandlerMetrics NO_OP =
         new HandlerMetrics(
-            new SolrMetricsContext(
-                new SolrMetricManager(
-                    null, new MetricsConfig.MetricsConfigBuilder().setEnabled(false).build()),
-                "NO_OP",
-                "NO_OP"),
+            new SolrMetricsContext(new SolrMetricManager(null), "NO_OP", "NO_OP"),
             Attributes.empty());
 
     public AttributedLongCounter requests;
@@ -207,7 +202,6 @@ public abstract class RequestHandlerBase
     public AttributedLongTimer requestTimes;
 
     public HandlerMetrics(SolrMetricsContext solrMetricsContext, Attributes attributes) {
-
       LongCounter requestMetric;
       LongCounter errorRequestMetric;
       LongCounter timeoutRequestMetric;
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
index 86c81c83bd8..aa64dcbacb1 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
@@ -190,6 +190,7 @@ public class SolrCoreMetricManager implements Closeable {
     metricManager.unregisterGauges(
         solrMetricsContext.getRegistryName(), solrMetricsContext.getTag());
 
+    metricManager.meterProvider(solrMetricsContext.getRegistryName()).close();
     registeredProducers.clear();
   }
 
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
index b4e1e708d86..43da56a7f75 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
@@ -16,6 +16,9 @@
  */
 package org.apache.solr.metrics;
 
+import static org.apache.solr.metrics.otel.MetricExporterFactory.OTLP_EXPORTER_ENABLED;
+import static org.apache.solr.metrics.otel.MetricExporterFactory.OTLP_EXPORTER_INTERVAL;
+
 import com.codahale.metrics.Counter;
 import com.codahale.metrics.Gauge;
 import com.codahale.metrics.Histogram;
@@ -57,6 +60,8 @@ import io.opentelemetry.sdk.metrics.InstrumentSelector;
 import io.opentelemetry.sdk.metrics.InstrumentType;
 import io.opentelemetry.sdk.metrics.SdkMeterProvider;
 import io.opentelemetry.sdk.metrics.View;
+import io.opentelemetry.sdk.metrics.export.MetricExporter;
+import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
 import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil;
 import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter;
 import java.io.IOException;
@@ -81,6 +86,8 @@ import java.util.function.Consumer;
 import java.util.regex.Pattern;
 import java.util.regex.PatternSyntaxException;
 import java.util.stream.Collectors;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.MetricsConfig;
@@ -90,6 +97,8 @@ import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.core.SolrResourceLoader;
 import org.apache.solr.logging.MDCLoggingContext;
 import org.apache.solr.metrics.otel.FilterablePrometheusMetricReader;
+import org.apache.solr.metrics.otel.MetricExporterFactory;
+import org.apache.solr.metrics.otel.NoopMetricExporter;
 import org.apache.solr.metrics.otel.OtelUnit;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -161,6 +170,8 @@ public class SolrMetricManager {
   private final ConcurrentMap<String, MeterProviderAndReaders> meterProviderAndReaders =
       new ConcurrentHashMap<>();
 
+  private final MetricExporter metricExporter;
+
   private static final List<Double> SOLR_NANOSECOND_HISTOGRAM_BOUNDARIES =
       List.of(
           0.0,
@@ -178,8 +189,9 @@ public class SolrMetricManager {
           100_000_000.0,
           1_000_000_000.0);
 
-  public SolrMetricManager() {
+  public SolrMetricManager(MetricExporter exporter) {
     metricsConfig = new MetricsConfig.MetricsConfigBuilder().build();
+    metricExporter = exporter;
     counterSupplier = MetricSuppliers.counterSupplier(null, null);
     meterSupplier = MetricSuppliers.meterSupplier(null, null);
     timerSupplier = MetricSuppliers.timerSupplier(null, null);
@@ -188,6 +200,7 @@ public class SolrMetricManager {
 
   public SolrMetricManager(SolrResourceLoader loader, MetricsConfig metricsConfig) {
     this.metricsConfig = metricsConfig;
+    this.metricExporter = loadMetricExporter(loader);
     counterSupplier = MetricSuppliers.counterSupplier(loader, metricsConfig.getCounterSupplier());
     meterSupplier = MetricSuppliers.meterSupplier(loader, metricsConfig.getMeterSupplier());
     timerSupplier = MetricSuppliers.timerSupplier(loader, metricsConfig.getTimerSupplier());
@@ -827,9 +840,7 @@ public class SolrMetricManager {
             providerName,
             key -> {
               var reader = new FilterablePrometheusMetricReader(true, null);
-              // NOCOMMIT: We need to add a Periodic Metric Reader here if we want to push with OTLP
-              // with an exporter
-              var provider =
+              var builder =
                   SdkMeterProvider.builder()
                       .registerMetricReader(reader)
                       .registerView(
@@ -841,9 +852,14 @@ public class SolrMetricManager {
                               .setAggregation(
                                   Aggregation.explicitBucketHistogram(
                                       SOLR_NANOSECOND_HISTOGRAM_BOUNDARIES))
-                              .build()); // TODO: Make histogram bucket boundaries configurable
-              SdkMeterProviderUtil.setExemplarFilter(provider, ExemplarFilter.traceBased());
-              return new MeterProviderAndReaders(provider.build(), reader);
+                              .build()); // TODO: Make histogram bucket boundaries configurable;
+              if (metricExporter != null)
+                builder.registerMetricReader(
+                    PeriodicMetricReader.builder(metricExporter)
+                        .setInterval(OTLP_EXPORTER_INTERVAL, TimeUnit.MILLISECONDS)
+                        .build());
+              SdkMeterProviderUtil.setExemplarFilter(builder, ExemplarFilter.traceBased());
+              return new MeterProviderAndReaders(builder.build(), reader);
             })
         .sdkMeterProvider();
   }
@@ -871,15 +887,27 @@ public class SolrMetricManager {
    *
    * @param registry name of the registry to remove
    */
+  // NOCOMMIT: Remove this
   public void removeRegistry(String registry) {
     meterProviderAndReaders.computeIfPresent(
         enforcePrefix(registry),
         (key, meterAndReader) -> {
-          meterAndReader.sdkMeterProvider().close();
+          IOUtils.closeQuietly(meterAndReader.sdkMeterProvider());
           return null;
         });
   }
 
+  /** Close all meter providers and their associated metric readers. */
+  public void closeAllRegistries() {
+    meterProviderAndReaders
+        .values()
+        .forEach(
+            meterAndReader -> {
+              IOUtils.closeQuietly(meterAndReader.sdkMeterProvider);
+            });
+    meterProviderAndReaders.clear();
+  }
+
   /**
    * Potential conflict resolution strategies when attempting to register a new metric that already
    * exists
@@ -1736,6 +1764,24 @@ public class SolrMetricManager {
     return (mpr != null) ? mpr.prometheusMetricReader() : null;
   }
 
+  public MetricExporter getMetricExporter() {
+    return metricExporter;
+  }
+
+  private MetricExporter loadMetricExporter(SolrResourceLoader loader) {
+    if (!OTLP_EXPORTER_ENABLED) return new NoopMetricExporter();
+    try {
+      MetricExporterFactory exporterFactory =
+          loader.newInstance(
+              "org.apache.solr.opentelemetry.OtlpExporterFactory", MetricExporterFactory.class);
+      return exporterFactory.getExporter();
+    } catch (SolrException e) {
+      log.error(
+          "Could not load OTLP exporter. Check that the Open Telemetry module is enabled.", e);
+      return new NoopMetricExporter();
+    }
+  }
+
   private record MeterProviderAndReaders(
       SdkMeterProvider sdkMeterProvider, FilterablePrometheusMetricReader prometheusMetricReader) {}
 }
diff --git a/solr/core/src/java/org/apache/solr/metrics/otel/MetricExporterFactory.java b/solr/core/src/java/org/apache/solr/metrics/otel/MetricExporterFactory.java
new file mode 100644
index 00000000000..5d0ecc67427
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/metrics/otel/MetricExporterFactory.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.metrics.otel;
+
+import io.opentelemetry.sdk.metrics.export.MetricExporter;
+import org.apache.solr.common.util.EnvUtils;
+
+public interface MetricExporterFactory {
+
+  public static final Boolean OTLP_EXPORTER_ENABLED =
+      Boolean.parseBoolean(EnvUtils.getProperty("solr.metrics.otlpExporterEnabled", "false"));
+
+  public static final String OTLP_EXPORTER_PROTOCOL =
+      EnvUtils.getProperty("solr.metrics.otlpExporterProtocol", "grpc");
+
+  public static final int OTLP_EXPORTER_INTERVAL =
+      Integer.parseInt(EnvUtils.getProperty("solr.metrics.otlpExporterInterval", "60000"));
+
+  MetricExporter getExporter();
+}
diff --git a/solr/core/src/java/org/apache/solr/metrics/otel/NoopMetricExporter.java b/solr/core/src/java/org/apache/solr/metrics/otel/NoopMetricExporter.java
new file mode 100644
index 00000000000..578a8f5a01c
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/metrics/otel/NoopMetricExporter.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.metrics.otel;
+
+import io.opentelemetry.sdk.common.CompletableResultCode;
+import io.opentelemetry.sdk.metrics.InstrumentType;
+import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
+import io.opentelemetry.sdk.metrics.data.MetricData;
+import io.opentelemetry.sdk.metrics.export.MetricExporter;
+import java.util.Collection;
+
+public class NoopMetricExporter implements MetricExporter {
+  @Override
+  public CompletableResultCode export(Collection<MetricData> metrics) {
+    return CompletableResultCode.ofSuccess();
+  }
+
+  @Override
+  public CompletableResultCode flush() {
+    return CompletableResultCode.ofSuccess();
+  }
+
+  @Override
+  public CompletableResultCode shutdown() {
+    return CompletableResultCode.ofSuccess();
+  }
+
+  @Override
+  public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) {
+    return AggregationTemporality.CUMULATIVE;
+  }
+}
diff --git a/solr/core/src/test/org/apache/solr/blockcache/BufferStoreTest.java b/solr/core/src/test/org/apache/solr/blockcache/BufferStoreTest.java
index a628b9263a7..f42067715a0 100644
--- a/solr/core/src/test/org/apache/solr/blockcache/BufferStoreTest.java
+++ b/solr/core/src/test/org/apache/solr/blockcache/BufferStoreTest.java
@@ -39,7 +39,7 @@ public class BufferStoreTest extends SolrTestCase {
   @Before
   public void setup() {
     metrics = new Metrics();
-    metricManager = new SolrMetricManager();
+    metricManager = new SolrMetricManager(null);
     registry = TestUtil.randomSimpleString(random(), 2, 10);
     String scope = TestUtil.randomSimpleString(random(), 2, 10);
     SolrMetricsContext solrMetricsContext = new SolrMetricsContext(metricManager, registry, "foo");
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java b/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
index 683ed940733..4af0a5f3809 100644
--- a/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
@@ -30,24 +30,30 @@ import io.opentelemetry.api.metrics.LongGauge;
 import io.opentelemetry.api.metrics.LongHistogram;
 import io.opentelemetry.api.metrics.LongUpDownCounter;
 import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
+import io.opentelemetry.sdk.metrics.data.MetricData;
+import io.opentelemetry.sdk.testing.exporter.InMemoryMetricExporter;
 import io.prometheus.metrics.model.snapshots.CounterSnapshot;
 import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
 import io.prometheus.metrics.model.snapshots.HistogramSnapshot;
 import io.prometheus.metrics.model.snapshots.MetricSnapshot;
 import io.prometheus.metrics.model.snapshots.MetricSnapshots;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Random;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.DoubleAdder;
 import java.util.concurrent.atomic.LongAdder;
 import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.RetryUtil;
 import org.apache.solr.core.PluginInfo;
 import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.util.SolrMetricTestUtils;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -59,20 +65,24 @@ public class SolrMetricManagerTest extends SolrTestCaseJ4 {
   @Before
   public void setUp() throws Exception {
     super.setUp();
-    this.metricManager = new SolrMetricManager();
+    this.metricManager = new SolrMetricManager(InMemoryMetricExporter.create());
     // Initialize a metric reader for tests
     metricManager.meterProvider(METER_PROVIDER_NAME);
     this.reader = metricManager.getPrometheusMetricReader(METER_PROVIDER_NAME);
   }
 
+  @After
+  public void tearDown() throws Exception {
+    metricManager.closeAllRegistries();
+    super.tearDown();
+  }
+
   // NOCOMMIT: Migration of this to OTEL isn't possible. You can't register instruments to a
   // meterprovider that the provider itself didn't create
   @Test
   public void testRegisterAll() throws Exception {
     Random r = random();
 
-    SolrMetricManager metricManager = new SolrMetricManager();
-
     Map<String, Counter> metrics = SolrMetricTestUtils.getRandomMetrics(r, true);
     MetricRegistry mr = new MetricRegistry();
     for (Map.Entry<String, Counter> entry : metrics.entrySet()) {
@@ -101,8 +111,6 @@ public class SolrMetricManagerTest extends SolrTestCaseJ4 {
   public void testClearMetrics() {
     Random r = random();
 
-    SolrMetricManager metricManager = new SolrMetricManager();
-
     Map<String, Counter> metrics = SolrMetricTestUtils.getRandomMetrics(r, true);
     String registryName = TestUtil.randomSimpleString(r, 1, 10);
 
@@ -144,8 +152,6 @@ public class SolrMetricManagerTest extends SolrTestCaseJ4 {
   public void testSimpleMetrics() {
     Random r = random();
 
-    SolrMetricManager metricManager = new SolrMetricManager();
-
     String registryName = TestUtil.randomSimpleString(r, 1, 10);
 
     metricManager.counter(null, registryName, "simple_counter", "foo", "bar");
@@ -390,6 +396,32 @@ public class SolrMetricManagerTest extends SolrTestCaseJ4 {
     assertEquals(-10.1, actual.getDataPoints().getFirst().getValue(), 1e-6);
   }
 
+  @Test
+  public void testMetricExporter() throws Exception {
+    LongCounter counter =
+        metricManager.longCounter(METER_PROVIDER_NAME, "my_counter", "desc", null);
+    counter.add(5);
+    counter.add(3);
+    InMemoryMetricExporter exporter = (InMemoryMetricExporter) metricManager.getMetricExporter();
+
+    RetryUtil.retryUntil(
+        "my_counter metric not found from exporter within timeout",
+        50,
+        100L,
+        TimeUnit.MILLISECONDS,
+        () -> {
+          Collection<MetricData> metrics = exporter.getFinishedMetricItems();
+          return metrics.stream()
+              .filter(m -> "my_counter".equals(m.getName()))
+              .findFirst()
+              .map(
+                  actual ->
+                      actual.getLongSumData().getPoints().stream()
+                          .anyMatch(p -> p.getValue() == 8L))
+              .orElse(false);
+        });
+  }
+
   @Test
   public void testCloseMeterProviders() {
     LongCounter counter =
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricReporterTest.java b/solr/core/src/test/org/apache/solr/metrics/SolrMetricReporterTest.java
index c0ff0827d57..b9dd45e467c 100644
--- a/solr/core/src/test/org/apache/solr/metrics/SolrMetricReporterTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrMetricReporterTest.java
@@ -33,7 +33,7 @@ public class SolrMetricReporterTest extends SolrTestCaseJ4 {
   public void testInit() throws Exception {
     Random random = random();
 
-    SolrMetricManager metricManager = new SolrMetricManager();
+    SolrMetricManager metricManager = new SolrMetricManager(null);
 
     final String registryName = TestUtil.randomSimpleString(random);
     final MockMetricReporter reporter = new MockMetricReporter(metricManager, registryName);
diff --git a/solr/core/src/test/org/apache/solr/search/TestCaffeineCache.java b/solr/core/src/test/org/apache/solr/search/TestCaffeineCache.java
index 12d7813ec2d..d045a1d46de 100644
--- a/solr/core/src/test/org/apache/solr/search/TestCaffeineCache.java
+++ b/solr/core/src/test/org/apache/solr/search/TestCaffeineCache.java
@@ -45,7 +45,7 @@ import org.junit.Test;
 /** Test for {@link CaffeineCache}. */
 public class TestCaffeineCache extends SolrTestCase {
 
-  SolrMetricManager metricManager = new SolrMetricManager();
+  SolrMetricManager metricManager = new SolrMetricManager(null);
   String registry = TestUtil.randomSimpleString(random(), 2, 10);
   String scope = TestUtil.randomSimpleString(random(), 2, 10);
 
diff --git a/solr/core/src/test/org/apache/solr/search/TestSolrCachePerf.java b/solr/core/src/test/org/apache/solr/search/TestSolrCachePerf.java
index 8e9e4717676..0675e43e752 100644
--- a/solr/core/src/test/org/apache/solr/search/TestSolrCachePerf.java
+++ b/solr/core/src/test/org/apache/solr/search/TestSolrCachePerf.java
@@ -33,6 +33,7 @@ import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.NoopMetricExporter;
 import org.apache.solr.util.SolrMetricTestUtils;
 import org.junit.Before;
 import org.junit.Test;
@@ -102,7 +103,7 @@ public class TestSolrCachePerf extends SolrTestCaseJ4 {
       boolean useCompute)
       throws Exception {
     for (Class<? extends SolrCache> clazz : IMPLS) {
-      SolrMetricManager metricManager = new SolrMetricManager();
+      SolrMetricManager metricManager = new SolrMetricManager(new NoopMetricExporter());
       @SuppressWarnings({"unchecked"})
       SolrCache<String, String> cache = clazz.getDeclaredConstructor().newInstance();
       Map<String, String> params = new HashMap<>();
@@ -182,6 +183,7 @@ public class TestSolrCachePerf extends SolrTestCaseJ4 {
       perImplRatio.addValue(hitRatio);
       perImplTime.addValue((double) (stopTime - startTime));
       cache.close();
+      metricManager.closeAllRegistries();
     }
   }
 }
diff --git a/solr/core/src/test/org/apache/solr/search/TestThinCache.java b/solr/core/src/test/org/apache/solr/search/TestThinCache.java
index 00c7d7a02a1..ff174290d65 100644
--- a/solr/core/src/test/org/apache/solr/search/TestThinCache.java
+++ b/solr/core/src/test/org/apache/solr/search/TestThinCache.java
@@ -84,7 +84,7 @@ public class TestThinCache extends SolrTestCaseJ4 {
     lrf = h.getRequestFactory("/select", 0, 20);
   }
 
-  SolrMetricManager metricManager = new SolrMetricManager();
+  SolrMetricManager metricManager = new SolrMetricManager(null);
   String registry = TestUtil.randomSimpleString(random(), 2, 10);
   String scope = TestUtil.randomSimpleString(random(), 2, 10);
 
diff --git a/solr/core/src/test/org/apache/solr/util/stats/OtelInstrumentedExecutorServiceTest.java b/solr/core/src/test/org/apache/solr/util/stats/OtelInstrumentedExecutorServiceTest.java
index 2d7ad162e4f..550477620e0 100644
--- a/solr/core/src/test/org/apache/solr/util/stats/OtelInstrumentedExecutorServiceTest.java
+++ b/solr/core/src/test/org/apache/solr/util/stats/OtelInstrumentedExecutorServiceTest.java
@@ -49,7 +49,7 @@ public class OtelInstrumentedExecutorServiceTest extends SolrTestCase {
 
   @Before
   public void setUpMetrics() {
-    metricsContext = new SolrMetricsContext(new SolrMetricManager(), REGISTRY_NAME, TAG_NAME);
+    metricsContext = new SolrMetricsContext(new SolrMetricManager(null), REGISTRY_NAME, TAG_NAME);
   }
 
   @Test
diff --git a/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/handler/MirroringCollectionsHandlerTest.java b/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/handler/MirroringCollectionsHandlerTest.java
index ed914dfaeb1..a4cb9a97cc3 100644
--- a/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/handler/MirroringCollectionsHandlerTest.java
+++ b/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/handler/MirroringCollectionsHandlerTest.java
@@ -31,7 +31,6 @@ import org.apache.solr.common.cloud.Replica;
 import org.apache.solr.common.cloud.SolrZkClient;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.core.CoreContainer;
-import org.apache.solr.core.OpenTelemetryConfigurator;
 import org.apache.solr.core.SolrXmlConfig;
 import org.apache.solr.crossdc.common.KafkaCrossDcConf;
 import org.apache.solr.crossdc.common.KafkaMirroringSink;
@@ -161,8 +160,6 @@ public class MirroringCollectionsHandlerTest extends SolrTestCaseJ4 {
 
   @Test
   public void testCoreContainerInit() throws Exception {
-    OpenTelemetryConfigurator.resetForTest();
-
     Path home = createTempDir();
     String solrXml = IOUtils.resourceToString("/mirroring-solr.xml", StandardCharsets.UTF_8);
     CoreContainer cores = new CoreContainer(SolrXmlConfig.fromString(home, solrXml));
diff --git a/solr/modules/opentelemetry/build.gradle b/solr/modules/opentelemetry/build.gradle
index 7ab50a32421..00a693d3fd6 100644
--- a/solr/modules/opentelemetry/build.gradle
+++ b/solr/modules/opentelemetry/build.gradle
@@ -17,7 +17,7 @@
 
 apply plugin: 'java-library'
 
-description = 'Open Telemetry (OTEL) tracer'
+description = 'Open Telemetry (OTEL) tracer and OTLP exporter'
 
 dependencies {
   implementation platform(project(':platform'))
@@ -31,7 +31,7 @@ dependencies {
 
   implementation libs.opentelemetry.api
   implementation libs.opentelemetry.sdkextension.autoconfigure
-  runtimeOnly libs.opentelemetry.exporter.otlp
+  implementation libs.opentelemetry.exporter.otlp
   // End users must recompile with jaeger exporter and/or zipkin exporter if they need these
 
   // NOTE: sdk-autoconfigure needs both opentelemetry-sdk-metrics and opentelemetry-sdk-logs even if we don't use them
diff --git a/solr/modules/opentelemetry/gradle.lockfile b/solr/modules/opentelemetry/gradle.lockfile
index 9e62cfe1990..934ef9815c7 100644
--- a/solr/modules/opentelemetry/gradle.lockfile
+++ b/solr/modules/opentelemetry/gradle.lockfile
@@ -88,7 +88,7 @@ io.opentelemetry:opentelemetry-context:1.53.0=compileClasspath,jarValidation,run
 io.opentelemetry:opentelemetry-exporter-common:1.50.0=solrPlatformLibs
 io.opentelemetry:opentelemetry-exporter-common:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-exporter-otlp-common:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
-io.opentelemetry:opentelemetry-exporter-otlp:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-otlp:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-exporter-sender-okhttp:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/modules/opentelemetry/src/java/org/apache/solr/opentelemetry/OtlpExporterFactory.java b/solr/modules/opentelemetry/src/java/org/apache/solr/opentelemetry/OtlpExporterFactory.java
new file mode 100644
index 00000000000..879aecd99b8
--- /dev/null
+++ b/solr/modules/opentelemetry/src/java/org/apache/solr/opentelemetry/OtlpExporterFactory.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.opentelemetry;
+
+import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter;
+import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter;
+import io.opentelemetry.sdk.metrics.export.MetricExporter;
+import java.lang.invoke.MethodHandles;
+import org.apache.solr.metrics.otel.MetricExporterFactory;
+import org.apache.solr.metrics.otel.NoopMetricExporter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Factory class for creating OpenTelemetry OTLP metric exporters and its configuration properties.
+ *
+ * @see io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter
+ * @see io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter
+ * @see NoopMetricExporter
+ */
+public class OtlpExporterFactory implements MetricExporterFactory {
+
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  public MetricExporter getExporter() {
+    if (!OTLP_EXPORTER_ENABLED) {
+      log.info("OTLP metric exporter is disabled.");
+      return new NoopMetricExporter();
+    }
+
+    return switch (OTLP_EXPORTER_PROTOCOL) {
+      case "grpc" -> OtlpGrpcMetricExporter.getDefault();
+      case "http" -> OtlpHttpMetricExporter.getDefault();
+      case "none" -> new NoopMetricExporter();
+      default -> {
+        log.warn(
+            "Unknown OTLP exporter type: {}. Defaulting to NO-OP exporter.",
+            OTLP_EXPORTER_PROTOCOL);
+        yield new NoopMetricExporter();
+      }
+    };
+  }
+}
diff --git a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
index 769c7d7bf13..90cfdbff7ec 100644
--- a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
+++ b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
@@ -250,7 +250,6 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
 
   @BeforeClass
   public static void setupTestCases() {
-    OpenTelemetryConfigurator.resetForTest();
     resetExceptionIgnores();
 
     testExecutor =
@@ -283,6 +282,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
     System.setProperty("solr.filterCache.async", String.valueOf(random().nextBoolean()));
     System.setProperty("solr.http.disableCookies", Boolean.toString(rarely()));
     System.setProperty("solr.metrics.jvm.enabled", "false");
+    System.setProperty("solr.metrics.otlpExporterInterval", "1000");
 
     startTrackingSearchers();
     ignoreException("ignore_exception");
@@ -299,6 +299,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
     }
 
     ExecutorUtil.resetThreadLocalProviders();
+    OpenTelemetryConfigurator.resetForTest();
   }
 
   @AfterClass


(solr) 17/33: Close OTEL Observable instruments (#3517)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit b966470824a70fd21d310d70328180c8547a233b
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Thu Aug 28 13:52:51 2025 -0400

    Close OTEL Observable instruments (#3517)
    
    * Close observable instruments
    
    * List of closeables
    
    * Use unmodifiable instead of copy
---
 .../src/java/org/apache/solr/core/SolrCore.java    | 160 +++++++++++----------
 .../src/java/org/apache/solr/core/ZkContainer.java | 144 ++++++++++---------
 .../apache/solr/update/DirectUpdateHandler2.java   |  26 ++--
 .../src/java/org/apache/solr/update/UpdateLog.java |  49 ++++---
 .../org/apache/solr/BasicFunctionalityTest.java    |  32 ++---
 .../test/org/apache/solr/core/SolrCoreTest.java    |  34 +++--
 .../apache/solr/update/SolrIndexMetricsTest.java   |  42 +++---
 .../java/org/apache/solr/common/util/IOUtils.java  |   5 +
 8 files changed, 267 insertions(+), 225 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/core/SolrCore.java b/solr/core/src/java/org/apache/solr/core/SolrCore.java
index 5f1760673ab..ab4db7d04f2 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrCore.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java
@@ -264,6 +264,7 @@ public class SolrCore implements SolrInfoBean, Closeable {
   private AttributedLongCounter newSearcherOtherErrorsCounter;
   private AttributedLongTimer newSearcherTimer;
   private AttributedLongTimer newSearcherWarmupTimer;
+  private List<AutoCloseable> toClose;
 
   private final String metricTag = SolrMetricProducer.getUniqueMetricTag(this, null);
   private final SolrMetricsContext solrMetricsContext;
@@ -1324,6 +1325,7 @@ public class SolrCore implements SolrInfoBean, Closeable {
   @Override
   public void initializeMetrics(
       SolrMetricsContext parentContext, Attributes attributes, String scope) {
+    final List<AutoCloseable> observables = new ArrayList<>();
 
     Attributes baseSearcherAttributes =
         Attributes.builder()
@@ -1369,86 +1371,93 @@ public class SolrCore implements SolrInfoBean, Closeable {
             baseSearcherTimerMetric,
             Attributes.builder().putAll(baseSearcherAttributes).put(TYPE_ATTR, "warmup").build());
 
-    parentContext.gauge(() -> getOpenCount(), true, "refCount", Category.CORE.toString());
-
-    parentContext.observableLongGauge(
-        "solr_core_ref_count",
-        "The current number of active references to a Solr core",
-        (observableLongMeasurement -> {
-          observableLongMeasurement.record(getOpenCount(), baseGaugeCoreAttributes);
-        }));
-
-    parentContext.observableLongGauge(
-        "solr_core_disk_space",
-        "Solr core disk space metrics",
-        (observableLongMeasurement -> {
-
-          // initialize disk total / free metrics
-          Path dataDirPath = Path.of(dataDir);
-          var totalSpaceAttributes =
-              Attributes.builder()
-                  .putAll(baseGaugeCoreAttributes)
-                  .put(TYPE_ATTR, "total_space")
-                  .build();
-          var usableSpaceAttributes =
-              Attributes.builder()
-                  .putAll(baseGaugeCoreAttributes)
-                  .put(TYPE_ATTR, "usable_space")
-                  .build();
-          try {
-            observableLongMeasurement.record(
-                Files.getFileStore(dataDirPath).getTotalSpace(), totalSpaceAttributes);
-          } catch (IOException e) {
-            observableLongMeasurement.record(0L, totalSpaceAttributes);
-          }
-          try {
-            observableLongMeasurement.record(
-                Files.getFileStore(dataDirPath).getUsableSpace(), usableSpaceAttributes);
-          } catch (IOException e) {
-            observableLongMeasurement.record(0L, usableSpaceAttributes);
-          }
-        }),
-        OtelUnit.BYTES);
-
-    parentContext.observableLongGauge(
-        "solr_core_index_size",
-        "Index size for a Solr core",
-        (observableLongMeasurement -> {
-          if (!isClosed())
-            observableLongMeasurement.record(getIndexSize(), baseGaugeCoreAttributes);
-        }),
-        OtelUnit.BYTES);
-
-    parentContext.observableLongGauge(
-        "solr_core_segment_count",
-        "Number of segments in a Solr core",
-        (observableLongMeasurement -> {
-          if (isReady())
-            observableLongMeasurement.record(getSegmentCount(), baseGaugeCoreAttributes);
-        }));
+    observables.add(
+        parentContext.observableLongGauge(
+            "solr_core_ref_count",
+            "The current number of active references to a Solr core",
+            (observableLongMeasurement -> {
+              observableLongMeasurement.record(getOpenCount(), baseGaugeCoreAttributes);
+            })));
+
+    observables.add(
+        parentContext.observableLongGauge(
+            "solr_core_disk_space",
+            "Solr core disk space metrics",
+            (observableLongMeasurement -> {
+
+              // initialize disk total / free metrics
+              Path dataDirPath = Path.of(dataDir);
+              var totalSpaceAttributes =
+                  Attributes.builder()
+                      .putAll(baseGaugeCoreAttributes)
+                      .put(TYPE_ATTR, "total_space")
+                      .build();
+              var usableSpaceAttributes =
+                  Attributes.builder()
+                      .putAll(baseGaugeCoreAttributes)
+                      .put(TYPE_ATTR, "usable_space")
+                      .build();
+              try {
+                observableLongMeasurement.record(
+                    Files.getFileStore(dataDirPath).getTotalSpace(), totalSpaceAttributes);
+              } catch (IOException e) {
+                observableLongMeasurement.record(0L, totalSpaceAttributes);
+              }
+              try {
+                observableLongMeasurement.record(
+                    Files.getFileStore(dataDirPath).getUsableSpace(), usableSpaceAttributes);
+              } catch (IOException e) {
+                observableLongMeasurement.record(0L, usableSpaceAttributes);
+              }
+            }),
+            OtelUnit.BYTES));
+
+    observables.add(
+        parentContext.observableLongGauge(
+            "solr_core_index_size",
+            "Index size for a Solr core",
+            (observableLongMeasurement -> {
+              if (!isClosed())
+                observableLongMeasurement.record(getIndexSize(), baseGaugeCoreAttributes);
+            }),
+            OtelUnit.BYTES));
+
+    observables.add(
+        parentContext.observableLongGauge(
+            "solr_core_segment_count",
+            "Number of segments in a Solr core",
+            (observableLongMeasurement -> {
+              if (isReady())
+                observableLongMeasurement.record(getSegmentCount(), baseGaugeCoreAttributes);
+            })));
 
     // NOCOMMIT: Do we need these start_time metrics? I think at minimum it should be optional
     // otherwise we fall into metric bloat for something people may not care about.
-    parentContext.observableLongGauge(
-        "solr_core_start_time",
-        "Start time of a Solr core",
-        (observableLongMeasurement -> {
-          observableLongMeasurement.record(
-              startTime.getTime(),
-              Attributes.builder()
-                  .putAll(baseGaugeCoreAttributes)
-                  .put(TYPE_ATTR, "start_time")
-                  .build());
-        }));
+    observables.add(
+        parentContext.observableLongGauge(
+            "solr_core_start_time",
+            "Start time of a Solr core",
+            (observableLongMeasurement -> {
+              observableLongMeasurement.record(
+                  startTime.getTime(),
+                  Attributes.builder()
+                      .putAll(baseGaugeCoreAttributes)
+                      .put(TYPE_ATTR, "start_time")
+                      .build());
+            })));
 
     if (coreContainer.isZooKeeperAware())
-      parentContext.observableLongGauge(
-          "solr_core_is_leader",
-          "Indicates whether this Solr core is currently the leader",
-          (observableLongMeasurement -> {
-            observableLongMeasurement.record(
-                (coreDescriptor.getCloudDescriptor().isLeader()) ? 1 : 0, baseGaugeCoreAttributes);
-          }));
+      observables.add(
+          parentContext.observableLongGauge(
+              "solr_core_is_leader",
+              "Indicates whether this Solr core is currently the leader",
+              (observableLongMeasurement -> {
+                observableLongMeasurement.record(
+                    (coreDescriptor.getCloudDescriptor().isLeader()) ? 1 : 0,
+                    baseGaugeCoreAttributes);
+              })));
+
+    this.toClose = Collections.unmodifiableList(observables);
 
     // NOCOMMIT: Temporary to see metrics
     newSearcherCounter.inc();
@@ -1804,6 +1813,7 @@ public class SolrCore implements SolrInfoBean, Closeable {
         assert false : "Too many closes on SolrCore";
       } else if (count == 0) {
         doClose();
+        IOUtils.closeQuietly(toClose);
       }
     } finally {
       MDCLoggingContext.clear(); // balance out from SolrCore open with close
diff --git a/solr/core/src/java/org/apache/solr/core/ZkContainer.java b/solr/core/src/java/org/apache/solr/core/ZkContainer.java
index 3953a8f1d27..196bfd2dd7e 100644
--- a/solr/core/src/java/org/apache/solr/core/ZkContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/ZkContainer.java
@@ -23,6 +23,9 @@ import io.opentelemetry.api.common.Attributes;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.TimeoutException;
 import java.util.function.Predicate;
@@ -36,6 +39,7 @@ import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.cloud.ZooKeeperException;
 import org.apache.solr.common.util.EnvUtils;
 import org.apache.solr.common.util.ExecutorUtil;
+import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.common.util.SolrNamedThreadFactory;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.logging.MDCLoggingContext;
@@ -66,6 +70,8 @@ public class ZkContainer {
 
   private SolrMetricProducer metricProducer;
 
+  private List<AutoCloseable> toClose;
+
   public ZkContainer() {}
 
   public void initZooKeeper(final CoreContainer cc, CloudConfig config) {
@@ -149,74 +155,83 @@ public class ZkContainer {
               @Override
               public void initializeMetrics(
                   SolrMetricsContext parentContext, Attributes attributes, String scope) {
+                final List<AutoCloseable> observables = new ArrayList<>();
                 ctx = parentContext.getChildContext(this);
 
                 var metricsListener = zkController.getZkClient().getMetrics();
 
-                ctx.observableLongCounter(
-                    "solr_zk_ops",
-                    "Total number of ZooKeeper operations",
-                    measurement -> {
-                      measurement.record(
-                          metricsListener.getReads(),
-                          attributes.toBuilder().put(OPERATION_ATTR, "read").build());
-                      measurement.record(
-                          metricsListener.getDeletes(),
-                          attributes.toBuilder().put(OPERATION_ATTR, "delete").build());
-                      measurement.record(
-                          metricsListener.getWrites(),
-                          attributes.toBuilder().put(OPERATION_ATTR, "write").build());
-                      measurement.record(
-                          metricsListener.getMultiOps(),
-                          attributes.toBuilder().put(OPERATION_ATTR, "multi").build());
-                      measurement.record(
-                          metricsListener.getExistsChecks(),
-                          attributes.toBuilder().put(OPERATION_ATTR, "exists").build());
-                    });
-
-                ctx.observableLongCounter(
-                    "solr_zk_read",
-                    "Total bytes read from ZooKeeper",
-                    measurement -> {
-                      measurement.record(metricsListener.getBytesRead(), attributes);
-                    },
-                    OtelUnit.BYTES);
-
-                ctx.observableLongCounter(
-                    "solr_zk_watches_fired",
-                    "Total number of ZooKeeper watches fired",
-                    measurement -> {
-                      measurement.record(metricsListener.getWatchesFired(), attributes);
-                    });
-
-                ctx.observableLongCounter(
-                    "solr_zk_written",
-                    "Total bytes written to ZooKeeper",
-                    measurement -> {
-                      measurement.record(metricsListener.getBytesWritten());
-                    },
-                    OtelUnit.BYTES);
-
-                ctx.observableLongCounter(
-                    "solr_zk_cumulative_multi_ops_total",
-                    "Total cumulative multi-operations count",
-                    measurement -> {
-                      measurement.record(metricsListener.getCumulativeMultiOps());
-                    });
-
-                ctx.observableLongCounter(
-                    "solr_zk_child_fetches",
-                    "Total number of ZooKeeper child node fetches",
-                    measurement -> {
-                      measurement.record(metricsListener.getChildFetches());
-                    });
-
-                ctx.observableLongCounter(
-                    "solr_zk_cumulative_children_fetched",
-                    "Total cumulative children fetched count",
-                    measurement -> {
-                      measurement.record(metricsListener.getCumulativeChildrenFetched());
-                    });
+                observables.add(
+                    ctx.observableLongCounter(
+                        "solr_zk_ops",
+                        "Total number of ZooKeeper operations",
+                        measurement -> {
+                          measurement.record(
+                              metricsListener.getReads(),
+                              attributes.toBuilder().put(OPERATION_ATTR, "read").build());
+                          measurement.record(
+                              metricsListener.getDeletes(),
+                              attributes.toBuilder().put(OPERATION_ATTR, "delete").build());
+                          measurement.record(
+                              metricsListener.getWrites(),
+                              attributes.toBuilder().put(OPERATION_ATTR, "write").build());
+                          measurement.record(
+                              metricsListener.getMultiOps(),
+                              attributes.toBuilder().put(OPERATION_ATTR, "multi").build());
+                          measurement.record(
+                              metricsListener.getExistsChecks(),
+                              attributes.toBuilder().put(OPERATION_ATTR, "exists").build());
+                        }));
+
+                observables.add(
+                    ctx.observableLongCounter(
+                        "solr_zk_read",
+                        "Total bytes read from ZooKeeper",
+                        measurement -> {
+                          measurement.record(metricsListener.getBytesRead(), attributes);
+                        },
+                        OtelUnit.BYTES));
+
+                observables.add(
+                    ctx.observableLongCounter(
+                        "solr_zk_watches_fired",
+                        "Total number of ZooKeeper watches fired",
+                        measurement -> {
+                          measurement.record(metricsListener.getWatchesFired(), attributes);
+                        }));
+
+                observables.add(
+                    ctx.observableLongCounter(
+                        "solr_zk_written",
+                        "Total bytes written to ZooKeeper",
+                        measurement -> {
+                          measurement.record(metricsListener.getBytesWritten());
+                        },
+                        OtelUnit.BYTES));
+
+                observables.add(
+                    ctx.observableLongCounter(
+                        "solr_zk_cumulative_multi_ops_total",
+                        "Total cumulative multi-operations count",
+                        measurement -> {
+                          measurement.record(metricsListener.getCumulativeMultiOps());
+                        }));
+
+                observables.add(
+                    ctx.observableLongCounter(
+                        "solr_zk_child_fetches",
+                        "Total number of ZooKeeper child node fetches",
+                        measurement -> {
+                          measurement.record(metricsListener.getChildFetches());
+                        }));
+
+                observables.add(
+                    ctx.observableLongCounter(
+                        "solr_zk_cumulative_children_fetched",
+                        "Total cumulative children fetched count",
+                        measurement -> {
+                          measurement.record(metricsListener.getCumulativeChildrenFetched());
+                        }));
+                toClose = Collections.unmodifiableList(observables);
               }
 
               @Override
@@ -315,6 +330,7 @@ public class ZkContainer {
           zkServer.stop();
         }
       }
+      IOUtils.closeQuietly(toClose);
     }
   }
 
diff --git a/solr/core/src/java/org/apache/solr/update/DirectUpdateHandler2.java b/solr/core/src/java/org/apache/solr/update/DirectUpdateHandler2.java
index df77497b7d4..1ffb451dfb4 100644
--- a/solr/core/src/java/org/apache/solr/update/DirectUpdateHandler2.java
+++ b/solr/core/src/java/org/apache/solr/update/DirectUpdateHandler2.java
@@ -18,12 +18,11 @@ package org.apache.solr.update;
 
 import io.opentelemetry.api.common.AttributeKey;
 import io.opentelemetry.api.common.Attributes;
-import io.opentelemetry.api.metrics.ObservableLongCounter;
-import io.opentelemetry.api.metrics.ObservableLongGauge;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.lang.reflect.Array;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
@@ -50,6 +49,7 @@ import org.apache.solr.cloud.ZkController;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.Replica;
 import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.SolrConfig.UpdateHandlerInfo;
 import org.apache.solr.core.SolrCore;
@@ -120,9 +120,7 @@ public class DirectUpdateHandler2 extends UpdateHandler
 
   AttributedLongCounter rollbackCommands;
   AttributedLongCounter splitCommands;
-  ObservableLongGauge commitStats;
-  ObservableLongGauge updateStats;
-  ObservableLongCounter softAutoCommits;
+  List<AutoCloseable> toClose;
 
   // tracks when auto-commit should occur
   protected final CommitTracker commitTracker;
@@ -239,6 +237,7 @@ public class DirectUpdateHandler2 extends UpdateHandler
     } else {
       this.solrMetricsContext = parentContext.getChildContext(this);
     }
+    final List<AutoCloseable> observables = new ArrayList<>();
 
     var baseAttributes =
         attributes.toBuilder()
@@ -306,7 +305,7 @@ public class DirectUpdateHandler2 extends UpdateHandler
     numErrorsCumulative =
         new AttributedLongCounter(baseErrorsMetric, baseAttributes.toBuilder().build());
 
-    softAutoCommits =
+    observables.add(
         solrMetricsContext.observableLongCounter(
             "solr_core_update_auto_commits",
             "Current number of auto commits",
@@ -317,12 +316,12 @@ public class DirectUpdateHandler2 extends UpdateHandler
               observableLongMeasurement.record(
                   softCommitTracker.getCommitCount(),
                   baseAttributes.toBuilder().put(TYPE_ATTR, "soft_auto_commits").build());
-            }));
+            })));
 
     // NOCOMMIT: This might not need to be an obseravableLongGauge, but a simple long gauge. Seems
     // like a waste to constantly call this callback to get the latest value if the upper bounds
     // rarely change.
-    commitStats =
+    observables.add(
         solrMetricsContext.observableLongGauge(
             "solr_core_update_commit_stats",
             "Metrics around commits",
@@ -352,9 +351,9 @@ public class DirectUpdateHandler2 extends UpdateHandler
                     softCommitTracker.getTimeUpperBound(),
                     baseAttributes.toBuilder().put(TYPE_ATTR, "soft_auto_commit_max_time").build());
               }
-            }));
+            })));
 
-    updateStats =
+    observables.add(
         solrMetricsContext.observableLongGauge(
             "solr_core_update_docs_pending_commit",
             "Current number of documents pending commit. Value is reset to 0 on commit.",
@@ -362,7 +361,9 @@ public class DirectUpdateHandler2 extends UpdateHandler
               observableLongMeasurement.record(
                   numDocsPending.longValue(),
                   baseAttributes.toBuilder().put(OPERATION_ATTR, "docs_pending").build());
-            });
+            }));
+
+    this.toClose = Collections.unmodifiableList(observables);
 
     var baseSubmittedOpsMetric =
         solrMetricsContext.longCounter(
@@ -1020,8 +1021,7 @@ public class DirectUpdateHandler2 extends UpdateHandler
 
     commitTracker.close();
     softCommitTracker.close();
-    commitStats.close();
-
+    IOUtils.closeQuietly(toClose);
     numDocsPending.reset();
     try {
       super.close();
diff --git a/solr/core/src/java/org/apache/solr/update/UpdateLog.java b/solr/core/src/java/org/apache/solr/update/UpdateLog.java
index 661e5815346..d9ea1d24efd 100644
--- a/solr/core/src/java/org/apache/solr/update/UpdateLog.java
+++ b/solr/core/src/java/org/apache/solr/update/UpdateLog.java
@@ -268,6 +268,7 @@ public class UpdateLog implements PluginInfoInitialized, SolrMetricProducer {
   protected AttributedLongCounter applyingBufferedOpsCounter;
   protected AttributedLongCounter replayOpsCounter;
   protected AttributedLongCounter copyOverOldUpdatesCounter;
+  protected List<AutoCloseable> toClose;
   protected SolrMetricsContext solrMetricsContext;
 
   public static class LogPtr {
@@ -630,6 +631,7 @@ public class UpdateLog implements PluginInfoInitialized, SolrMetricProducer {
   @Override
   public void initializeMetrics(
       SolrMetricsContext parentContext, Attributes attributes, String scope) {
+    final List<AutoCloseable> observables = new ArrayList<>();
     solrMetricsContext = parentContext.getChildContext(this);
 
     // NOCOMMIT: We do not need a scope attribute
@@ -640,26 +642,31 @@ public class UpdateLog implements PluginInfoInitialized, SolrMetricProducer {
             .put(CATEGORY_ATTR, SolrInfoBean.Category.TLOG.toString())
             .build();
 
-    solrMetricsContext.observableLongGauge(
-        "solr_core_update_log_buffered_ops",
-        "The current number of buffered operations",
-        (observableLongMeasurement ->
-            observableLongMeasurement.record(computeBufferedOps(), baseAttributes)));
-
-    solrMetricsContext.observableLongGauge(
-        "solr_core_update_log_replay_logs_remaining",
-        "The current number of tlogs remaining to be replayed",
-        (observableLongMeasurement -> {
-          observableLongMeasurement.record(logs.size(), baseAttributes);
-        }));
-
-    solrMetricsContext.observableLongGauge(
-        "solr_core_update_log_size_remaining",
-        "The total size in bytes of all tlogs remaining to be replayed",
-        (observableLongMeasurement -> {
-          observableLongMeasurement.record(getTotalLogsSize(), baseAttributes);
-        }),
-        OtelUnit.BYTES);
+    observables.add(
+        solrMetricsContext.observableLongGauge(
+            "solr_core_update_log_buffered_ops",
+            "The current number of buffered operations",
+            (observableLongMeasurement ->
+                observableLongMeasurement.record(computeBufferedOps(), baseAttributes))));
+
+    observables.add(
+        solrMetricsContext.observableLongGauge(
+            "solr_core_update_log_replay_logs_remaining",
+            "The current number of tlogs remaining to be replayed",
+            (observableLongMeasurement -> {
+              observableLongMeasurement.record(logs.size(), baseAttributes);
+            })));
+
+    observables.add(
+        solrMetricsContext.observableLongGauge(
+            "solr_core_update_log_size_remaining",
+            "The total size in bytes of all tlogs remaining to be replayed",
+            (observableLongMeasurement -> {
+              observableLongMeasurement.record(getTotalLogsSize(), baseAttributes);
+            }),
+            OtelUnit.BYTES));
+
+    toClose = Collections.unmodifiableList(observables);
 
     // NOCOMMIT: Do we want to keep this? Metric was just state with the numeric enum value.
     // Without context this doesn't mean anything and can be very confusing. Maybe keep the numeric
@@ -1741,6 +1748,8 @@ public class UpdateLog implements PluginInfoInitialized, SolrMetricProducer {
       }
     } catch (IOException e) {
       log.warn("exception releasing tlog dir", e);
+    } finally {
+      IOUtils.closeQuietly(toClose);
     }
   }
 
diff --git a/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java b/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java
index a2d31f6ef82..91cbf47f008 100644
--- a/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java
+++ b/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java
@@ -117,23 +117,21 @@ public class BasicFunctionalityTest extends SolrTestCaseJ4 {
 
   @Test
   public void testSomeStuff() {
-    clearIndex();
-
-    SolrCore core = h.getCore();
-
-    // test that we got the expected config, not just hardcoded defaults
-    assertNotNull(core.getRequestHandler("/mock"));
-
-    // test stats call
-    var refCount =
-        SolrMetricTestUtils.getGaugeDatapoint(
-            core,
-            "solr_core_ref_count",
-            SolrMetricTestUtils.newStandaloneLabelsBuilder(core).label("category", "CORE").build());
-    assertNotNull(refCount);
-
-    assertTrue(refCount.getValue() > 0);
-
+    try (SolrCore core = h.getCoreContainer().getCore("collection1")) {
+      // test that we got the expected config, not just hardcoded defaults
+      assertNotNull(core.getRequestHandler("/mock"));
+
+      // test stats call
+      var refCount =
+          SolrMetricTestUtils.getGaugeDatapoint(
+              core,
+              "solr_core_ref_count",
+              SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+                  .label("category", "CORE")
+                  .build());
+      assertNotNull(refCount);
+      assertTrue(refCount.getValue() > 0);
+    }
     assertQ(
         "test query on empty index",
         req("qlkciyopsbgzyvkylsjhchghjrdf"),
diff --git a/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java b/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java
index 5fe145de556..5af9bfe0436 100644
--- a/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java
+++ b/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java
@@ -16,6 +16,9 @@
  */
 package org.apache.solr.core;
 
+import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
+import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
+import io.prometheus.metrics.model.snapshots.MetricSnapshots;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -33,8 +36,6 @@ import org.apache.solr.handler.ReplicationHandler;
 import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.handler.component.QueryComponent;
 import org.apache.solr.handler.component.SpellCheckComponent;
-import org.apache.solr.metrics.SolrMetricManager;
-import org.apache.solr.metrics.SolrMetricTestUtils;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestHandler;
 import org.apache.solr.response.SolrQueryResponse;
@@ -340,7 +341,6 @@ public class SolrCoreTest extends SolrTestCaseJ4 {
    */
   @Test
   public void testCoreInitDeadlockMetrics() throws Exception {
-    SolrMetricManager metricManager = h.getCoreContainer().getMetricManager();
     CoreContainer coreContainer = h.getCoreContainer();
 
     String coreName = "tmpCore";
@@ -353,16 +353,24 @@ public class SolrCoreTest extends SolrTestCaseJ4 {
     executor.execute(
         () -> {
           while (!created.get()) {
-            var datapoint =
-                SolrMetricTestUtils.getGaugeDatapoint(
-                    h.getCore(),
-                    "solr_core_index_size_bytes",
-                    SolrMetricTestUtils.newStandaloneLabelsBuilder(h.getCore())
-                        .label("category", "CORE")
-                        .build());
-
-            if (datapoint != null && datapoint.getValue() >= 0) {
-              atLeastOnePoll.set(true);
+            try {
+              PrometheusMetricReader reader =
+                  coreContainer
+                      .getMetricManager()
+                      .getPrometheusMetricReader("solr.core." + coreName);
+              if (reader != null) {
+                MetricSnapshots snapshots = reader.collect();
+                for (var snapshot : snapshots) {
+                  if (snapshot instanceof GaugeSnapshot gaugeSnapshot) {
+                    var dataPoints = gaugeSnapshot.getDataPoints();
+                    if (!dataPoints.isEmpty()) {
+                      atLeastOnePoll.compareAndSet(false, true);
+                    }
+                  }
+                }
+              }
+            } catch (Exception ignore) {
+              // Ignore in case the core may not be fully initialized
             }
 
             try {
diff --git a/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java b/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java
index 4644c851d97..09aa0489949 100644
--- a/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java
+++ b/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java
@@ -23,6 +23,7 @@ import com.codahale.metrics.Timer;
 import java.util.Map;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.SolrInputDocument;
+import org.apache.solr.core.SolrCore;
 import org.apache.solr.metrics.SolrMetricTestUtils;
 import org.apache.solr.request.SolrQueryRequest;
 import org.junit.After;
@@ -89,30 +90,25 @@ public class SolrIndexMetricsTest extends SolrTestCaseJ4 {
     System.setProperty("solr.tests.metrics.merge", "false");
     System.setProperty("solr.tests.metrics.mergeDetails", "false");
     initCore("solrconfig-indexmetrics.xml", "schema.xml");
-
     addDocs();
-
-    MetricRegistry registry =
-        h.getCoreContainer()
-            .getMetricManager()
-            .registry(h.getCore().getCoreMetricManager().getRegistryName());
-    assertNotNull(registry);
-    var indexSize =
-        SolrMetricTestUtils.getGaugeDatapoint(
-            h.getCore(),
-            "solr_core_index_size_bytes",
-            SolrMetricTestUtils.newStandaloneLabelsBuilder(h.getCore())
-                .label("category", "CORE")
-                .build());
-    var segmentSize =
-        SolrMetricTestUtils.getGaugeDatapoint(
-            h.getCore(),
-            "solr_core_segment_count",
-            SolrMetricTestUtils.newStandaloneLabelsBuilder(h.getCore())
-                .label("category", "CORE")
-                .build());
-    assertNotNull(indexSize);
-    assertNotNull(segmentSize);
+    try (SolrCore core = h.getCoreContainer().getCore("collection1")) {
+      var indexSize =
+          SolrMetricTestUtils.getGaugeDatapoint(
+              core,
+              "solr_core_index_size_bytes",
+              SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+                  .label("category", "CORE")
+                  .build());
+      var segmentSize =
+          SolrMetricTestUtils.getGaugeDatapoint(
+              core,
+              "solr_core_segment_count",
+              SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+                  .label("category", "CORE")
+                  .build());
+      assertNotNull(indexSize);
+      assertNotNull(segmentSize);
+    }
   }
 
   @Test
diff --git a/solr/solrj/src/java/org/apache/solr/common/util/IOUtils.java b/solr/solrj/src/java/org/apache/solr/common/util/IOUtils.java
index b91553a1813..93a2c5eb22e 100644
--- a/solr/solrj/src/java/org/apache/solr/common/util/IOUtils.java
+++ b/solr/solrj/src/java/org/apache/solr/common/util/IOUtils.java
@@ -23,6 +23,11 @@ import org.slf4j.LoggerFactory;
 public class IOUtils {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
+  public static void closeQuietly(Iterable<? extends AutoCloseable> closeables) {
+    if (closeables == null) return;
+    for (AutoCloseable closeable : closeables) closeQuietly(closeable);
+  }
+
   public static void closeQuietly(AutoCloseable closeable) {
     try {
       if (closeable != null) {


(solr) 23/33: Fix test failure on AbstractBasicDistributedZkTestBase (#3620)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit ebe843be934f63363484af3d6755578c3eb8304d
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Mon Sep 8 11:04:40 2025 -0400

    Fix test failure on AbstractBasicDistributedZkTestBase (#3620)
    
    Co-authored-by: Matthew Biscocho <mb...@bloomberg.net>
---
 .../cloud/AbstractBasicDistributedZkTestBase.java  | 40 ++++++++++++++--------
 1 file changed, 25 insertions(+), 15 deletions(-)

diff --git a/solr/test-framework/src/java/org/apache/solr/cloud/AbstractBasicDistributedZkTestBase.java b/solr/test-framework/src/java/org/apache/solr/cloud/AbstractBasicDistributedZkTestBase.java
index 74a02b44988..c87715d3445 100644
--- a/solr/test-framework/src/java/org/apache/solr/cloud/AbstractBasicDistributedZkTestBase.java
+++ b/solr/test-framework/src/java/org/apache/solr/cloud/AbstractBasicDistributedZkTestBase.java
@@ -17,7 +17,9 @@
 package org.apache.solr.cloud;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.lang.invoke.MethodHandles;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -42,9 +44,9 @@ import org.apache.solr.JSONTestUtil;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.client.solrj.SolrRequest;
-import org.apache.solr.client.solrj.SolrRequest.SolrRequestType;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.impl.HttpSolrClient;
+import org.apache.solr.client.solrj.impl.InputStreamResponseParser;
 import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.CoreAdminRequest.Create;
@@ -73,6 +75,7 @@ import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.params.CollectionParams.CollectionAction;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.params.UpdateParams;
 import org.apache.solr.common.util.ExecutorUtil;
 import org.apache.solr.common.util.NamedList;
@@ -1271,7 +1274,6 @@ public abstract class AbstractBasicDistributedZkTestBase extends AbstractFullDis
                     .setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true));
 
     long endCommits = getNumCommits((HttpSolrClient) clients.get(0));
-
     assertEquals(startCommits + 1L, endCommits);
   }
 
@@ -1282,22 +1284,30 @@ public abstract class AbstractBasicDistributedZkTestBase extends AbstractFullDis
             .withConnectionTimeout(15000, TimeUnit.MILLISECONDS)
             .withSocketTimeout(60000, TimeUnit.MILLISECONDS)
             .build()) {
-      ModifiableSolrParams params = new ModifiableSolrParams();
-      // params.set("qt", "/admin/metrics?prefix=UPDATE.updateHandler&registry=solr.core." +
-      // collection);
-      params.set("prefix", "UPDATE.updateHandler");
-      params.set("registry", "solr.core." + collection);
-      // use generic request to avoid extra processing of queries
       var req =
           new GenericSolrRequest(
-              SolrRequest.METHOD.GET, "/admin/metrics", SolrRequestType.ADMIN, params);
+              SolrRequest.METHOD.GET,
+              "/admin/metrics",
+              SolrRequest.SolrRequestType.ADMIN,
+              SolrParams.of("wt", "prometheus"));
+      req.setResponseParser(new InputStreamResponseParser("prometheus"));
+
       NamedList<Object> resp = client.request(req);
-      NamedList<?> metrics = (NamedList<?>) resp.get("metrics");
-      NamedList<?> uhandlerCat = (NamedList<?>) metrics.getVal(0);
-      @SuppressWarnings({"unchecked"})
-      Map<String, Object> commits =
-          (Map<String, Object>) uhandlerCat.get("UPDATE.updateHandler.commits");
-      return (Long) commits.get("count");
+      try (InputStream in = (InputStream) resp.get("stream")) {
+        String output = new String(in.readAllBytes(), StandardCharsets.UTF_8);
+        String metricName = "solr_core_update_commit_ops";
+
+        return (long)
+            output
+                .lines()
+                .filter(
+                    l ->
+                        l.startsWith(metricName)
+                            && l.contains("collection=\"" + collection + "\"")
+                            && l.contains("ops=\"commits\""))
+                .mapToDouble(s -> Double.parseDouble(s.substring(s.lastIndexOf(" "))))
+                .sum();
+      }
     }
   }
 


(solr) 05/33: SOLR-17806: Migrate SolrCore metrics to OTEL (#3430)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit d3e23eac4ceb7228194c57c6fe07801df8ee11ef
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Thu Jul 24 14:09:53 2025 -0400

    SOLR-17806: Migrate SolrCore metrics to OTEL (#3430)
    
    * Migrate solrCore metrics to OTEL
    
    * Cleanup
    
    * Bad apple tests
    
    * Change description
    
    * Make changes from comments
    
    * Cleanup attributes builders
---
 .../placement/impl/CollectionMetricsBuilder.java   |   1 +
 .../src/java/org/apache/solr/core/SolrCore.java    | 244 +++++++++++++--------
 .../apache/solr/metrics/SolrMetricProducer.java    |   1 +
 .../solr/metrics/reporters/SolrJmxReporter.java    |   2 +
 .../solr/metrics/reporters/SolrSlf4jReporter.java  |   2 +
 .../org/apache/solr/BasicFunctionalityTest.java    |  20 +-
 .../impl/PlacementPluginIntegrationTest.java       |   5 +
 .../test/org/apache/solr/core/SolrCoreTest.java    |  22 +-
 .../solr/handler/admin/MetricsHandlerTest.java     |  30 +--
 .../apache/solr/metrics/SolrMetricTestUtils.java   |  67 ++++++
 .../solr/metrics/SolrMetricsIntegrationTest.java   |   5 +
 .../metrics/reporters/SolrJmxReporterTest.java     |   4 +
 .../metrics/reporters/SolrSlf4jReporterTest.java   |   4 +
 .../response/TestPrometheusResponseWriter.java     |   4 +-
 .../TestPrometheusResponseWriterCloud.java         |   3 +-
 .../apache/solr/update/SolrIndexMetricsTest.java   |  26 ++-
 .../SolrStandaloneScraperBasicAuthTest.java        |   2 +
 .../scraper/SolrStandaloneScraperTest.java         |   3 +
 ...bstractCollectionsAPIDistributedZkTestBase.java |   3 +
 19 files changed, 298 insertions(+), 150 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/impl/CollectionMetricsBuilder.java b/solr/core/src/java/org/apache/solr/cluster/placement/impl/CollectionMetricsBuilder.java
index 7fac922899b..53c714cf9bf 100644
--- a/solr/core/src/java/org/apache/solr/cluster/placement/impl/CollectionMetricsBuilder.java
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/impl/CollectionMetricsBuilder.java
@@ -29,6 +29,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /** Builder class for constructing instances of {@link CollectionMetrics}. */
+// NOCOMMIT: Need to migrate this to an OTEL collection Metrics builder
 public class CollectionMetricsBuilder {
 
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
diff --git a/solr/core/src/java/org/apache/solr/core/SolrCore.java b/solr/core/src/java/org/apache/solr/core/SolrCore.java
index 34d0fbdc4a3..825e8b11094 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrCore.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java
@@ -18,9 +18,10 @@ package org.apache.solr.core;
 
 import static org.apache.solr.common.params.CommonParams.PATH;
 import static org.apache.solr.handler.admin.MetricsHandler.PROMETHEUS_METRICS_WT;
+import static org.apache.solr.metrics.SolrCoreMetricManager.COLLECTION_ATTR;
+import static org.apache.solr.metrics.SolrCoreMetricManager.CORE_ATTR;
+import static org.apache.solr.metrics.SolrCoreMetricManager.SHARD_ATTR;
 
-import com.codahale.metrics.Counter;
-import com.codahale.metrics.Timer;
 import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.Caffeine;
 import io.opentelemetry.api.common.Attributes;
@@ -82,7 +83,6 @@ import org.apache.lucene.store.IndexOutput;
 import org.apache.lucene.store.LockObtainFailedException;
 import org.apache.lucene.util.ResourceLoader;
 import org.apache.solr.client.solrj.impl.JavaBinResponseParser;
-import org.apache.solr.cloud.CloudDescriptor;
 import org.apache.solr.cloud.RecoveryStrategy;
 import org.apache.solr.cloud.ZkSolrResourceLoader;
 import org.apache.solr.common.SolrException;
@@ -117,6 +117,8 @@ import org.apache.solr.logging.MDCLoggingContext;
 import org.apache.solr.metrics.SolrCoreMetricManager;
 import org.apache.solr.metrics.SolrMetricProducer;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.instruments.AttributedLongCounter;
+import org.apache.solr.metrics.otel.instruments.AttributedLongTimer;
 import org.apache.solr.pkg.PackageListeners;
 import org.apache.solr.pkg.PackagePluginHolder;
 import org.apache.solr.pkg.SolrPackageLoader;
@@ -168,7 +170,6 @@ import org.apache.solr.update.processor.UpdateRequestProcessorChain.ProcessorInf
 import org.apache.solr.update.processor.UpdateRequestProcessorFactory;
 import org.apache.solr.util.IOFunction;
 import org.apache.solr.util.IndexOutputOutputStream;
-import org.apache.solr.util.NumberUtils;
 import org.apache.solr.util.PropertiesInputStream;
 import org.apache.solr.util.RefCounted;
 import org.apache.solr.util.TestInjection;
@@ -254,13 +255,14 @@ public class SolrCore implements SolrInfoBean, Closeable {
   private final ReentrantLock
       snapshotDelLock; // A lock instance to guard against concurrent deletions.
 
-  private Timer newSearcherTimer;
-  private Timer newSearcherWarmupTimer;
-  private Counter newSearcherCounter;
-  private Counter newSearcherMaxReachedCounter;
-  private Counter newSearcherOtherErrorsCounter;
   private volatile boolean newSearcherReady = false;
 
+  private AttributedLongCounter newSearcherCounter;
+  private AttributedLongCounter newSearcherMaxReachedCounter;
+  private AttributedLongCounter newSearcherOtherErrorsCounter;
+  private AttributedLongTimer newSearcherTimer;
+  private AttributedLongTimer newSearcherWarmupTimer;
+
   private final String metricTag = SolrMetricProducer.getUniqueMetricTag(this, null);
   private final SolrMetricsContext solrMetricsContext;
 
@@ -1105,8 +1107,21 @@ public class SolrCore implements SolrInfoBean, Closeable {
       setLatestSchema(schema);
 
       // initialize core metrics
-      // TODO SOLR-17458: Add Otel
-      initializeMetrics(solrMetricsContext, Attributes.empty(), "");
+      if (coreContainer.isZooKeeperAware()) {
+        initializeMetrics(
+            solrMetricsContext,
+            Attributes.builder()
+                .put(COLLECTION_ATTR, coreDescriptor.getCollectionName())
+                .put(CORE_ATTR, coreDescriptor.getName())
+                .put(SHARD_ATTR, coreDescriptor.getCloudDescriptor().getShardId())
+                .build(),
+            "");
+      } else {
+        initializeMetrics(
+            solrMetricsContext,
+            Attributes.builder().put(CORE_ATTR, coreDescriptor.getName()).build(),
+            "");
+      }
 
       // init pluggable circuit breakers, after metrics because some circuit breakers use metrics
       initPlugins(null, CircuitBreaker.class);
@@ -1114,7 +1129,7 @@ public class SolrCore implements SolrInfoBean, Closeable {
       SolrFieldCacheBean solrFieldCacheBean = new SolrFieldCacheBean();
       // this is registered at the CONTAINER level because it's not core-specific - for now we
       // also register it here for back-compat
-      // TODO SOLR-17458: Add Otel
+      // NOCOMMIT SOLR-17458: Add Otel
       solrFieldCacheBean.initializeMetrics(solrMetricsContext, Attributes.empty(), "core");
       infoRegistry.put("fieldCache", solrFieldCacheBean);
 
@@ -1305,92 +1320,140 @@ public class SolrCore implements SolrInfoBean, Closeable {
     return coreMetricManager;
   }
 
-  // TODO SOLR-17458: Migrate to Otel
   @Override
   public void initializeMetrics(
       SolrMetricsContext parentContext, Attributes attributes, String scope) {
-    newSearcherCounter = parentContext.counter("new", Category.SEARCHER.toString());
-    newSearcherTimer = parentContext.timer("time", Category.SEARCHER.toString(), "new");
-    newSearcherWarmupTimer = parentContext.timer("warmup", Category.SEARCHER.toString(), "new");
+
+    Attributes baseSearcherAttributes =
+        Attributes.builder()
+            .putAll(attributes)
+            .put(CATEGORY_ATTR, Category.SEARCHER.toString())
+            .build();
+    Attributes baseGaugeCoreAttributes =
+        Attributes.builder()
+            .putAll(attributes)
+            .put(CATEGORY_ATTR, Category.CORE.toString())
+            .build();
+
+    var baseSearcherTimerMetric =
+        parentContext.longHistogram("solr_searcher_timer", "Timer for opening new searchers", "ms");
+
+    newSearcherCounter =
+        new AttributedLongCounter(
+            parentContext.longCounter(
+                "solr_core_searcher_new", "Total number of new searchers opened"),
+            baseSearcherAttributes);
+
     newSearcherMaxReachedCounter =
-        parentContext.counter("maxReached", Category.SEARCHER.toString(), "new");
+        new AttributedLongCounter(
+            parentContext.longCounter(
+                "solr_core_searcher_warming_max",
+                "Total number of maximum concurrent warming searchers reached"),
+            baseSearcherAttributes);
+
     newSearcherOtherErrorsCounter =
-        parentContext.counter("errors", Category.SEARCHER.toString(), "new");
-
-    parentContext.gauge(
-        () -> name == null ? parentContext.nullString() : name,
-        true,
-        "coreName",
-        Category.CORE.toString());
-    parentContext.gauge(() -> startTime, true, "startTime", Category.CORE.toString());
+        new AttributedLongCounter(
+            parentContext.longCounter(
+                "solr_core_searcher_errors", "Total number of searcher errors"),
+            baseSearcherAttributes);
+
+    newSearcherTimer =
+        new AttributedLongTimer(
+            baseSearcherTimerMetric,
+            Attributes.builder().putAll(baseSearcherAttributes).put(TYPE_ATTR, "new").build());
+
+    newSearcherWarmupTimer =
+        new AttributedLongTimer(
+            baseSearcherTimerMetric,
+            Attributes.builder().putAll(baseSearcherAttributes).put(TYPE_ATTR, "warmup").build());
+
     parentContext.gauge(() -> getOpenCount(), true, "refCount", Category.CORE.toString());
-    parentContext.gauge(
-        () -> getInstancePath().toString(), true, "instanceDir", Category.CORE.toString());
-    parentContext.gauge(
-        () -> isClosed() ? parentContext.nullString() : getIndexDir(),
-        true,
-        "indexDir",
-        Category.CORE.toString());
-    parentContext.gauge(
-        () -> isClosed() ? parentContext.nullNumber() : getIndexSize(),
-        true,
-        "sizeInBytes",
-        Category.INDEX.toString());
-    parentContext.gauge(
-        () -> isClosed() ? parentContext.nullString() : NumberUtils.readableSize(getIndexSize()),
-        true,
-        "size",
-        Category.INDEX.toString());
-    parentContext.gauge(
-        () -> isReady() ? getSegmentCount() : parentContext.nullNumber(),
-        true,
-        "segments",
-        Category.INDEX.toString());
-
-    final CloudDescriptor cd = getCoreDescriptor().getCloudDescriptor();
-    if (cd != null) {
-      parentContext.gauge(cd::getCollectionName, true, "collection", Category.CORE.toString());
-      parentContext.gauge(cd::getShardId, true, "shard", Category.CORE.toString());
-      parentContext.gauge(cd::isLeader, true, "isLeader", Category.CORE.toString());
-      parentContext.gauge(
-          () -> String.valueOf(cd.getLastPublished()),
-          true,
-          "replicaState",
-          Category.CORE.toString());
-    }
-
-    // initialize disk total / free metrics
-    Path dataDirPath = Path.of(dataDir);
-    parentContext.gauge(
-        () -> {
+
+    parentContext.observableLongGauge(
+        "solr_core_ref_count",
+        "The current number of active references to a Solr core",
+        (observableLongMeasurement -> {
+          observableLongMeasurement.record(getOpenCount(), baseGaugeCoreAttributes);
+        }));
+
+    parentContext.observableLongGauge(
+        "solr_core_disk_space",
+        "Solr core disk space metrics",
+        (observableLongMeasurement -> {
+
+          // initialize disk total / free metrics
+          Path dataDirPath = Path.of(dataDir);
+          var totalSpaceAttributes =
+              Attributes.builder()
+                  .putAll(baseGaugeCoreAttributes)
+                  .put(TYPE_ATTR, "total_space")
+                  .build();
+          var usableSpaceAttributes =
+              Attributes.builder()
+                  .putAll(baseGaugeCoreAttributes)
+                  .put(TYPE_ATTR, "usable_space")
+                  .build();
           try {
-            return Files.getFileStore(dataDirPath).getTotalSpace();
+            observableLongMeasurement.record(
+                Files.getFileStore(dataDirPath).getTotalSpace(), totalSpaceAttributes);
           } catch (IOException e) {
-            return 0L;
+            observableLongMeasurement.record(0L, totalSpaceAttributes);
           }
-        },
-        true,
-        "totalSpace",
-        Category.CORE.toString(),
-        "fs");
-    parentContext.gauge(
-        () -> {
           try {
-            return Files.getFileStore(dataDirPath).getUsableSpace();
+            observableLongMeasurement.record(
+                Files.getFileStore(dataDirPath).getUsableSpace(), usableSpaceAttributes);
           } catch (IOException e) {
-            return 0L;
+            observableLongMeasurement.record(0L, usableSpaceAttributes);
           }
-        },
-        true,
-        "usableSpace",
-        Category.CORE.toString(),
-        "fs");
-    parentContext.gauge(
-        () -> dataDirPath.toAbsolutePath().toString(),
-        true,
-        "path",
-        Category.CORE.toString(),
-        "fs");
+        }),
+        "By");
+
+    parentContext.observableLongGauge(
+        "solr_core_index_size",
+        "Index size for a Solr core",
+        (observableLongMeasurement -> {
+          if (!isClosed())
+            observableLongMeasurement.record(getIndexSize(), baseGaugeCoreAttributes);
+        }),
+        "By");
+
+    parentContext.observableLongGauge(
+        "solr_core_segment_count",
+        "Number of segments in a Solr core",
+        (observableLongMeasurement -> {
+          if (isReady())
+            observableLongMeasurement.record(getSegmentCount(), baseGaugeCoreAttributes);
+        }));
+
+    // NOCOMMIT: Do we need these start_time metrics? I think at minimum it should be optional
+    // otherwise we fall into metric bloat for something people may not care about.
+    parentContext.observableLongGauge(
+        "solr_core_start_time",
+        "Start time of a Solr core",
+        (observableLongMeasurement -> {
+          observableLongMeasurement.record(
+              startTime.getTime(),
+              Attributes.builder()
+                  .putAll(baseGaugeCoreAttributes)
+                  .put(TYPE_ATTR, "start_time")
+                  .build());
+        }));
+
+    if (coreContainer.isZooKeeperAware())
+      parentContext.observableLongGauge(
+          "solr_core_is_leader",
+          "Indicates whether this Solr core is currently the leader",
+          (observableLongMeasurement -> {
+            observableLongMeasurement.record(
+                (coreDescriptor.getCloudDescriptor().isLeader()) ? 1 : 0, baseGaugeCoreAttributes);
+          }));
+
+    // NOCOMMIT: Temporary to see metrics
+    newSearcherCounter.inc();
+    newSearcherMaxReachedCounter.inc();
+    newSearcherOtherErrorsCounter.inc();
+    newSearcherTimer.start().stop();
+    newSearcherWarmupTimer.start().stop();
   }
 
   public String getMetricTag() {
@@ -2585,7 +2648,7 @@ public class SolrCore implements SolrInfoBean, Closeable {
     boolean success = false;
 
     openSearcherLock.lock();
-    Timer.Context timerContext = newSearcherTimer.time();
+    AttributedLongTimer.MetricTimer timerContext = newSearcherTimer.start();
     try {
       searchHolder = openNewSearcher(updateHandlerReopens, false);
       // the searchHolder will be incremented once already (and it will eventually be assigned to
@@ -2627,7 +2690,7 @@ public class SolrCore implements SolrInfoBean, Closeable {
           future =
               searcherExecutor.submit(
                   () -> {
-                    Timer.Context warmupContext = newSearcherWarmupTimer.time();
+                    AttributedLongTimer.MetricTimer warmupContext = newSearcherWarmupTimer.start();
                     try {
                       newSearcher.warm(currSearcher);
                     } catch (Throwable e) {
@@ -2636,7 +2699,7 @@ public class SolrCore implements SolrInfoBean, Closeable {
                         throw (Error) e;
                       }
                     } finally {
-                      warmupContext.close();
+                      warmupContext.stop();
                     }
                     return null;
                   });
@@ -2720,8 +2783,7 @@ public class SolrCore implements SolrInfoBean, Closeable {
       throw new SolrException(ErrorCode.SERVER_ERROR, e);
     } finally {
 
-      timerContext.close();
-
+      timerContext.stop();
       if (!success) {
         newSearcherOtherErrorsCounter.inc();
         synchronized (searcherLock) {
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java
index 6e06f59c338..f6079511ab0 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java
@@ -24,6 +24,7 @@ import java.io.IOException;
 public interface SolrMetricProducer extends AutoCloseable {
 
   public static final AttributeKey<String> TYPE_ATTR = AttributeKey.stringKey("type");
+  public static final AttributeKey<String> CATEGORY_ATTR = AttributeKey.stringKey("category");
 
   /**
    * Unique metric tag identifies components with the same life-cycle, which should be registered /
diff --git a/solr/core/src/java/org/apache/solr/metrics/reporters/SolrJmxReporter.java b/solr/core/src/java/org/apache/solr/metrics/reporters/SolrJmxReporter.java
index 6790a577457..bbcaa8a494a 100644
--- a/solr/core/src/java/org/apache/solr/metrics/reporters/SolrJmxReporter.java
+++ b/solr/core/src/java/org/apache/solr/metrics/reporters/SolrJmxReporter.java
@@ -37,6 +37,8 @@ import org.slf4j.LoggerFactory;
  * <p>NOTE: {@link com.codahale.metrics.jmx.JmxReporter} that this class uses exports only newly
  * added metrics (it doesn't process already existing metrics in a registry)
  */
+// NOCOMMIT: This JMX reporter looks to be wrapped over a Dropwizard registry. We need to migrate
+// this to OTEL. Maybe we can look at available otel shims for JMX?
 public class SolrJmxReporter extends FilteringSolrMetricReporter {
 
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
diff --git a/solr/core/src/java/org/apache/solr/metrics/reporters/SolrSlf4jReporter.java b/solr/core/src/java/org/apache/solr/metrics/reporters/SolrSlf4jReporter.java
index 346e3e8588e..117d414085f 100644
--- a/solr/core/src/java/org/apache/solr/metrics/reporters/SolrSlf4jReporter.java
+++ b/solr/core/src/java/org/apache/solr/metrics/reporters/SolrSlf4jReporter.java
@@ -49,6 +49,8 @@ import org.slf4j.MDC;
  *       <code>solr.jvm</code>, <code>solr.core</code>, etc
  * </ul>
  */
+// NOCOMMIT: Will we still be supporting this? Need to understand how Slf4j works and if it is even
+// possible to wrap OTEL into this like dropwizard.
 public class SolrSlf4jReporter extends FilteringSolrMetricReporter {
 
   @SuppressWarnings("unused") // we need this to pass validate-source-patterns
diff --git a/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java b/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java
index 025db3d529d..a2d31f6ef82 100644
--- a/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java
+++ b/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java
@@ -16,8 +16,6 @@
  */
 package org.apache.solr;
 
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Metric;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.StringWriter;
@@ -40,7 +38,7 @@ import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.handler.RequestHandlerBase;
-import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.metrics.SolrMetricTestUtils;
 import org.apache.solr.request.LocalSolrQueryRequest;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestHandler;
@@ -127,14 +125,14 @@ public class BasicFunctionalityTest extends SolrTestCaseJ4 {
     assertNotNull(core.getRequestHandler("/mock"));
 
     // test stats call
-    SolrMetricManager manager = core.getCoreContainer().getMetricManager();
-    String registry = core.getCoreMetricManager().getRegistryName();
-    Map<String, Metric> metrics = manager.registry(registry).getMetrics();
-    assertTrue(metrics.containsKey("CORE.coreName"));
-    assertTrue(metrics.containsKey("CORE.refCount"));
-    @SuppressWarnings({"unchecked"})
-    Gauge<Number> g = (Gauge<Number>) metrics.get("CORE.refCount");
-    assertTrue(g.getValue().intValue() > 0);
+    var refCount =
+        SolrMetricTestUtils.getGaugeDatapoint(
+            core,
+            "solr_core_ref_count",
+            SolrMetricTestUtils.newStandaloneLabelsBuilder(core).label("category", "CORE").build());
+    assertNotNull(refCount);
+
+    assertTrue(refCount.getValue() > 0);
 
     assertQ(
         "test query on empty index",
diff --git a/solr/core/src/test/org/apache/solr/cluster/placement/impl/PlacementPluginIntegrationTest.java b/solr/core/src/test/org/apache/solr/cluster/placement/impl/PlacementPluginIntegrationTest.java
index 38f6e0ae8e2..d3dc25f2e1e 100644
--- a/solr/core/src/test/org/apache/solr/cluster/placement/impl/PlacementPluginIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/cluster/placement/impl/PlacementPluginIntegrationTest.java
@@ -385,7 +385,10 @@ public class PlacementPluginIntegrationTest extends SolrCloudTestCase {
   // this functionality relies on System.getProperty which we cannot set on individual
   // nodes in a mini cluster. For this reason this test is very basic - see
   // AffinityPlacementFactoryTest for a more comprehensive test.
+  // NOCOMMIT: This test fails because of CollectionMetricsBuilder. Need to dive deeper into what
+  // this is and if we need to shim otel into this metrics map.
   @Test
+  @BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17458")
   public void testNodeTypeIntegration() throws Exception {
     // this functionality relies on System.getProperty which we cannot set on individual
     // nodes in a mini cluster.
@@ -437,7 +440,9 @@ public class PlacementPluginIntegrationTest extends SolrCloudTestCase {
     System.clearProperty(AffinityPlacementConfig.NODE_TYPE_SYSPROP);
   }
 
+  // NOCOMMIT: This test needs to be fixed after migrating the collection metrics builder
   @Test
+  @BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17458")
   public void testAttributeFetcherImpl() throws Exception {
     CollectionAdminResponse rsp =
         CollectionAdminRequest.createCollection(COLLECTION, "conf", 2, 2)
diff --git a/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java b/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java
index ba0e89a0804..5fe145de556 100644
--- a/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java
+++ b/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java
@@ -16,8 +16,6 @@
  */
 package org.apache.solr.core;
 
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.MetricFilter;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -36,6 +34,7 @@ import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.handler.component.QueryComponent;
 import org.apache.solr.handler.component.SpellCheckComponent;
 import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.metrics.SolrMetricTestUtils;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestHandler;
 import org.apache.solr.response.SolrQueryResponse;
@@ -354,15 +353,16 @@ public class SolrCoreTest extends SolrTestCaseJ4 {
     executor.execute(
         () -> {
           while (!created.get()) {
-            var metrics =
-                metricManager.getMetrics(
-                    "solr.core." + coreName,
-                    MetricFilter.startsWith(SolrInfoBean.Category.INDEX.toString()));
-            for (var m : metrics.values()) {
-              if (m instanceof Gauge) {
-                var v = ((Gauge<?>) m).getValue();
-                atLeastOnePoll.compareAndSet(false, v != null);
-              }
+            var datapoint =
+                SolrMetricTestUtils.getGaugeDatapoint(
+                    h.getCore(),
+                    "solr_core_index_size_bytes",
+                    SolrMetricTestUtils.newStandaloneLabelsBuilder(h.getCore())
+                        .label("category", "CORE")
+                        .build());
+
+            if (datapoint != null && datapoint.getValue() >= 0) {
+              atLeastOnePoll.set(true);
             }
 
             try {
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 07f71176591..af27e7d1a6e 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
@@ -48,7 +48,6 @@ import org.junit.BeforeClass;
 import org.junit.Test;
 
 /** Test for {@link MetricsHandler} */
-// NOCOMMIT SOLR-17785: Lets move this to SolrCloudTestCase
 public class MetricsHandlerTest extends SolrTestCaseJ4 {
   @BeforeClass
   public static void beforeClass() throws Exception {
@@ -87,7 +86,11 @@ public class MetricsHandlerTest extends SolrTestCaseJ4 {
     }
   }
 
+  // NOCOMMIT: This test does a bunch of /admin/metrics calls with various params, with various
+  // filters and parameters. We have not migrated all the metrics to otel yet or even created any
+  // filters. Once that is done, we should revisit this test and assert the prometheus response.
   @Test
+  @BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17458")
   public void test() throws Exception {
     MetricsHandler handler = new MetricsHandler(h.getCoreContainer());
 
@@ -357,31 +360,6 @@ public class MetricsHandlerTest extends SolrTestCaseJ4 {
     handler.close();
   }
 
-  @Test
-  public void testCompact() throws Exception {
-    MetricsHandler handler = new MetricsHandler(h.getCoreContainer());
-
-    SolrQueryResponse resp = new SolrQueryResponse();
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            CommonParams.WT,
-            "json",
-            MetricsHandler.COMPACT_PARAM,
-            "true"),
-        resp);
-    NamedList<?> values = resp.getValues();
-    assertNotNull(values.get("metrics"));
-    values = (NamedList<?>) values.get("metrics");
-    NamedList<?> nl = (NamedList<?>) values.get("solr.core.collection1");
-    assertNotNull(nl);
-    Object o = nl.get("SEARCHER.new.errors");
-    assertNotNull(o); // counter type
-    assertTrue(o instanceof Number);
-    handler.close();
-  }
-
   @Test
   public void testPropertyFilter() throws Exception {
     assertQ(req("*:*"), "//result[@numFound='0']");
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java b/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java
index d22e520d207..9da3314ad2a 100644
--- a/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java
@@ -18,11 +18,19 @@ package org.apache.solr.metrics;
 
 import com.codahale.metrics.Counter;
 import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
+import io.prometheus.metrics.model.snapshots.CounterSnapshot;
+import io.prometheus.metrics.model.snapshots.DataPointSnapshot;
+import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
+import io.prometheus.metrics.model.snapshots.Labels;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Random;
 import org.apache.lucene.tests.util.TestUtil;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrInfoBean;
 
 public final class SolrMetricTestUtils {
@@ -121,4 +129,63 @@ public final class SolrMetricTestUtils {
       }
     };
   }
+
+  public static DataPointSnapshot getDataPointSnapshot(
+      PrometheusMetricReader reader, String metricName, Labels labels) {
+    return reader.collect().stream()
+        .filter(ms -> ms.getMetadata().getPrometheusName().equals(metricName))
+        .findFirst()
+        .flatMap(
+            ms ->
+                ms.getDataPoints().stream().filter(dp -> dp.getLabels().equals(labels)).findFirst())
+        .orElse(null);
+  }
+
+  public static Labels.Builder getCloudLabelsBase(SolrCore core) {
+    return Labels.builder()
+        .label("collection", core.getCoreDescriptor().getCloudDescriptor().getCollectionName())
+        .label("shard", core.getCoreDescriptor().getCloudDescriptor().getShardId())
+        .label("core", core.getName())
+        .label(
+            "replica",
+            Utils.parseMetricsReplicaName(
+                core.getCoreDescriptor().getCollectionName(), core.getName()))
+        .label("otel_scope_name", "org.apache.solr");
+  }
+
+  public static Labels.Builder newStandaloneLabelsBuilder(SolrCore core) {
+    return newStandaloneLabelsBuilder(core.getName());
+  }
+
+  public static Labels.Builder newStandaloneLabelsBuilder(String coreName) {
+    return Labels.builder().label("core", coreName).label("otel_scope_name", "org.apache.solr");
+  }
+
+  public static PrometheusMetricReader getPrometheusMetricReader(SolrCore core) {
+    return getPrometheusMetricReader(
+        core.getCoreContainer(), core.getCoreMetricManager().getRegistryName());
+  }
+
+  public static PrometheusMetricReader getPrometheusMetricReader(
+      CoreContainer container, String registryName) {
+    return container.getMetricManager().getPrometheusMetricReader(registryName);
+  }
+
+  private static <T> T getDatapoint(
+      SolrCore core, String metricName, Labels labels, Class<T> snapshotType) {
+
+    var reader = getPrometheusMetricReader(core);
+
+    return snapshotType.cast(SolrMetricTestUtils.getDataPointSnapshot(reader, metricName, labels));
+  }
+
+  public static GaugeSnapshot.GaugeDataPointSnapshot getGaugeDatapoint(
+      SolrCore core, String metricName, Labels labels) {
+    return getDatapoint(core, metricName, labels, GaugeSnapshot.GaugeDataPointSnapshot.class);
+  }
+
+  public static CounterSnapshot.CounterDataPointSnapshot getCounterDatapoint(
+      SolrCore core, String metricName, Labels labels) {
+    return getDatapoint(core, metricName, labels, CounterSnapshot.CounterDataPointSnapshot.class);
+  }
 }
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java b/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java
index e1e178e0ae3..e6e7e7c949a 100644
--- a/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java
@@ -29,6 +29,7 @@ import java.util.Map;
 import java.util.Random;
 import java.util.Set;
 import org.apache.http.client.HttpClient;
+import org.apache.lucene.tests.util.LuceneTestCase;
 import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.solrj.SolrClient;
@@ -50,6 +51,10 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
+// NOCOMMIT: Test fails because of the @After assert on index path. Was going to migrate to just
+// check the registry for the core is deleted but this test does a rename operation which otel has
+// not addressed yet. Need to migrate the rename operation to otel first.
+@LuceneTestCase.BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17458")
 public class SolrMetricsIntegrationTest extends SolrTestCaseJ4 {
   private static final int MAX_ITERATIONS = 20;
   private static final String CORE_NAME = "metrics_integration";
diff --git a/solr/core/src/test/org/apache/solr/metrics/reporters/SolrJmxReporterTest.java b/solr/core/src/test/org/apache/solr/metrics/reporters/SolrJmxReporterTest.java
index 3a8f518c53b..25e433b934e 100644
--- a/solr/core/src/test/org/apache/solr/metrics/reporters/SolrJmxReporterTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/reporters/SolrJmxReporterTest.java
@@ -28,6 +28,7 @@ import javax.management.MBeanServer;
 import javax.management.MBeanServerFactory;
 import javax.management.ObjectInstance;
 import javax.management.ObjectName;
+import org.apache.lucene.tests.util.LuceneTestCase;
 import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.params.CoreAdminParams;
@@ -46,6 +47,9 @@ import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+// NOCOMMIT: All tests failing because of migration for some core metrics missing from JMX. See
+// comment on SolrJmxReporter for what we could do.
+@LuceneTestCase.BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17458")
 public class SolrJmxReporterTest extends SolrTestCaseJ4 {
 
   private static final int MAX_ITERATIONS = 20;
diff --git a/solr/core/src/test/org/apache/solr/metrics/reporters/SolrSlf4jReporterTest.java b/solr/core/src/test/org/apache/solr/metrics/reporters/SolrSlf4jReporterTest.java
index ed554448f99..f76f4678dbf 100644
--- a/solr/core/src/test/org/apache/solr/metrics/reporters/SolrSlf4jReporterTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/reporters/SolrSlf4jReporterTest.java
@@ -22,6 +22,7 @@ import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Map;
+import org.apache.lucene.tests.util.LuceneTestCase;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.SolrDocumentList;
 import org.apache.solr.core.CoreContainer;
@@ -37,6 +38,9 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /** */
+// NOCOMMIT: Need to see if we are still supporting this and if it is possible to wrap otel metrics
+// into slf4j like dropwizard does.
+@LuceneTestCase.BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17458")
 public class SolrSlf4jReporterTest extends SolrTestCaseJ4 {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
diff --git a/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriter.java b/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriter.java
index a0010476393..6d818157b6f 100644
--- a/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriter.java
+++ b/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriter.java
@@ -108,9 +108,7 @@ public class TestPrometheusResponseWriter extends SolrTestCaseJ4 {
               // Skip standard OTEL metric
               return;
             }
-            assertTrue(
-                "All metrics should start with 'solr_metrics_'",
-                actualMetric.startsWith("solr_metrics_"));
+            assertTrue("All metrics should start with 'solr_'", actualMetric.startsWith("solr_"));
             try {
               Float.parseFloat(actualValue);
             } catch (NumberFormatException e) {
diff --git a/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriterCloud.java b/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriterCloud.java
index 4ed29e43199..0c12b80ce32 100644
--- a/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriterCloud.java
+++ b/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriterCloud.java
@@ -91,8 +91,7 @@ public class TestPrometheusResponseWriterCloud extends SolrCloudTestCase {
                           && line.contains("collection=\"collection1\"")
                           && line.contains("core=\"collection1_shard1_replica_n1\"")
                           && line.contains("replica=\"replica_n1\"")
-                          && line.contains("shard=\"shard1\"")
-                          && line.contains("type=\"requests\"")));
+                          && line.contains("shard=\"shard1\"")));
     }
   }
 
diff --git a/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java b/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java
index 55bc52cbf6d..4644c851d97 100644
--- a/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java
+++ b/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java
@@ -23,6 +23,7 @@ import com.codahale.metrics.Timer;
 import java.util.Map;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.SolrInputDocument;
+import org.apache.solr.metrics.SolrMetricTestUtils;
 import org.apache.solr.request.SolrQueryRequest;
 import org.junit.After;
 import org.junit.Test;
@@ -67,8 +68,10 @@ public class SolrIndexMetricsTest extends SolrTestCaseJ4 {
 
     Map<String, Metric> metrics = registry.getMetrics();
 
+    // NOCOMMIT: As we migrate more metrics to OTEL, this will need to migrate to check prometheus
+    // reader instead
     assertEquals(
-        13, metrics.entrySet().stream().filter(e -> e.getKey().startsWith("INDEX")).count());
+        10, metrics.entrySet().stream().filter(e -> e.getKey().startsWith("INDEX")).count());
 
     // check basic index meters
     Timer timer = (Timer) metrics.get("INDEX.merge.minor");
@@ -94,11 +97,22 @@ public class SolrIndexMetricsTest extends SolrTestCaseJ4 {
             .getMetricManager()
             .registry(h.getCore().getCoreMetricManager().getRegistryName());
     assertNotNull(registry);
-
-    Map<String, Metric> metrics = registry.getMetrics();
-    // INDEX.size, INDEX.sizeInBytes, INDEX.segmentCount
-    assertEquals(
-        3, metrics.entrySet().stream().filter(e -> e.getKey().startsWith("INDEX")).count());
+    var indexSize =
+        SolrMetricTestUtils.getGaugeDatapoint(
+            h.getCore(),
+            "solr_core_index_size_bytes",
+            SolrMetricTestUtils.newStandaloneLabelsBuilder(h.getCore())
+                .label("category", "CORE")
+                .build());
+    var segmentSize =
+        SolrMetricTestUtils.getGaugeDatapoint(
+            h.getCore(),
+            "solr_core_segment_count",
+            SolrMetricTestUtils.newStandaloneLabelsBuilder(h.getCore())
+                .label("category", "CORE")
+                .build());
+    assertNotNull(indexSize);
+    assertNotNull(segmentSize);
   }
 
   @Test
diff --git a/solr/prometheus-exporter/src/test/org/apache/solr/prometheus/scraper/SolrStandaloneScraperBasicAuthTest.java b/solr/prometheus-exporter/src/test/org/apache/solr/prometheus/scraper/SolrStandaloneScraperBasicAuthTest.java
index b08e771e7d3..93aca188fc7 100644
--- a/solr/prometheus-exporter/src/test/org/apache/solr/prometheus/scraper/SolrStandaloneScraperBasicAuthTest.java
+++ b/solr/prometheus-exporter/src/test/org/apache/solr/prometheus/scraper/SolrStandaloneScraperBasicAuthTest.java
@@ -40,6 +40,8 @@ import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Test;
 
+// Prometheus Exporter will be deprecated
+@LuceneTestCase.BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17458")
 public class SolrStandaloneScraperBasicAuthTest extends SolrTestCaseJ4 {
 
   @ClassRule public static final SolrJettyTestRule solrRule = new SolrJettyTestRule();
diff --git a/solr/prometheus-exporter/src/test/org/apache/solr/prometheus/scraper/SolrStandaloneScraperTest.java b/solr/prometheus-exporter/src/test/org/apache/solr/prometheus/scraper/SolrStandaloneScraperTest.java
index 7f756c0fb3e..e563efa85b9 100644
--- a/solr/prometheus-exporter/src/test/org/apache/solr/prometheus/scraper/SolrStandaloneScraperTest.java
+++ b/solr/prometheus-exporter/src/test/org/apache/solr/prometheus/scraper/SolrStandaloneScraperTest.java
@@ -43,6 +43,9 @@ import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Test;
 
+// NOCOMMIT: This test is broken from OTEL migration and the /admin/plugins endpoint. Placing
+// BadApple test but this must be fixed before this feature gets merged to a release branch
+@LuceneTestCase.BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17458")
 public class SolrStandaloneScraperTest extends SolrTestCaseJ4 {
 
   @ClassRule public static final SolrJettyTestRule solrRule = new SolrJettyTestRule();
diff --git a/solr/test-framework/src/java/org/apache/solr/cloud/api/collections/AbstractCollectionsAPIDistributedZkTestBase.java b/solr/test-framework/src/java/org/apache/solr/cloud/api/collections/AbstractCollectionsAPIDistributedZkTestBase.java
index dcbefbf3e16..0d17b9b1558 100644
--- a/solr/test-framework/src/java/org/apache/solr/cloud/api/collections/AbstractCollectionsAPIDistributedZkTestBase.java
+++ b/solr/test-framework/src/java/org/apache/solr/cloud/api/collections/AbstractCollectionsAPIDistributedZkTestBase.java
@@ -38,6 +38,7 @@ import java.util.concurrent.TimeUnit;
 import javax.management.MBeanServer;
 import javax.management.MBeanServerFactory;
 import javax.management.ObjectName;
+import org.apache.lucene.tests.util.LuceneTestCase;
 import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.client.api.model.CoreStatusResponse;
 import org.apache.solr.client.solrj.SolrClient;
@@ -389,7 +390,9 @@ public abstract class AbstractCollectionsAPIDistributedZkTestBase extends SolrCl
     }
   }
 
+  // NOCOMMIT: Failing from JMX reporter
   @Test
+  @LuceneTestCase.BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17458")
   public void testCollectionsAPI() throws Exception {
 
     // create new collections rapid fire


(solr) 06/33: SOLR-17806: Migrate DirectUpdateHandler2 metrics to OTEL (#3417)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 8f34855115769842920d5dfecb5a060cb2075c71
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Tue Jul 29 16:01:59 2025 -0400

    SOLR-17806: Migrate DirectUpdateHandler2 metrics to OTEL (#3417)
    
    * Migrate direct update handler
    
    * Fix up metrics
    
    * UNCOMMIT ME BEFORE PUSHING
    
    * Revert requestHandlerBase
    
    * Fix broken tests
    
    * Cleanup
    
    * Move commit ops to normal counter
    
    * Add new submitted and committed metrics
    
    * Move methods around
    
    * Fix tests for submitted and committed
    
    * Use toBuilder() instead of putAll()
    
    * Don't append scope as attribute
---
 .../apache/solr/metrics/SolrCoreMetricManager.java |  14 +-
 .../apache/solr/metrics/SolrMetricProducer.java    |   1 +
 .../apache/solr/metrics/SolrMetricsContext.java    |  15 +
 .../apache/solr/update/DirectUpdateHandler2.java   | 319 +++++++++++++-------
 .../apache/solr/cloud/TestPullReplicaWithAuth.java |   3 +
 .../org/apache/solr/cloud/TestTlogReplica.java     |  46 +--
 .../apache/solr/metrics/SolrMetricTestUtils.java   |   2 +-
 .../solr/update/DirectUpdateHandlerTest.java       | 323 +++++++++++++++------
 solr/webapp/web/js/angular/controllers/plugins.js  |   2 +
 9 files changed, 502 insertions(+), 223 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
index 9d79ada8a85..a01183da9ad 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
@@ -146,18 +146,16 @@ public class SolrCoreMetricManager implements Closeable {
               + producer);
     }
 
-    // NOCOMMIT SOLR-17458: These attributes may not work for standalone mode
-    // use deprecated method for back-compat, remove in 9.0
-    producer.initializeMetrics(
-        solrMetricsContext,
+    // NOCOMMIT SOLR-17458: These attributes may not work for standalone mode and maybe make the
+    // scope attribute optional
+    var attributesBuilder =
         Attributes.builder()
             .put(CORE_ATTR, core.getCoreDescriptor().getName())
             .put(COLLECTION_ATTR, collectionName)
             .put(SHARD_ATTR, shardName)
-            .put(REPLICA_ATTR, replicaName)
-            .put((scope.startsWith("/")) ? HANDLER_ATTR : SCOPE_ATTR, scope)
-            .build(),
-        scope);
+            .put(REPLICA_ATTR, replicaName);
+    if (scope.startsWith("/")) attributesBuilder.put(HANDLER_ATTR, scope);
+    producer.initializeMetrics(solrMetricsContext, attributesBuilder.build(), scope);
   }
 
   /** Return the registry used by this SolrCore. */
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java
index f6079511ab0..e2b433108e6 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java
@@ -25,6 +25,7 @@ public interface SolrMetricProducer extends AutoCloseable {
 
   public static final AttributeKey<String> TYPE_ATTR = AttributeKey.stringKey("type");
   public static final AttributeKey<String> CATEGORY_ATTR = AttributeKey.stringKey("category");
+  public static final AttributeKey<String> OPERATION_ATTR = AttributeKey.stringKey("ops");
 
   /**
    * Unique metric tag identifies components with the same life-cycle, which should be registered /
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java
index 222ce2e0e8a..1601bae36a3 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java
@@ -32,6 +32,7 @@ import io.opentelemetry.api.metrics.LongHistogram;
 import io.opentelemetry.api.metrics.LongUpDownCounter;
 import io.opentelemetry.api.metrics.ObservableDoubleGauge;
 import io.opentelemetry.api.metrics.ObservableDoubleMeasurement;
+import io.opentelemetry.api.metrics.ObservableLongCounter;
 import io.opentelemetry.api.metrics.ObservableLongGauge;
 import io.opentelemetry.api.metrics.ObservableLongMeasurement;
 import java.util.Map;
@@ -233,6 +234,20 @@ public class SolrMetricsContext {
         registryName, metricName, description, callback, unit);
   }
 
+  public ObservableLongCounter observableLongCounter(
+      String metricName, String description, Consumer<ObservableLongMeasurement> callback) {
+    return observableLongCounter(metricName, description, callback, null);
+  }
+
+  public ObservableLongCounter observableLongCounter(
+      String metricName,
+      String description,
+      Consumer<ObservableLongMeasurement> callback,
+      String unit) {
+    return metricManager.observableLongCounter(
+        registryName, metricName, description, callback, unit);
+  }
+
   /**
    * Convenience method for {@link SolrMetricManager#meter(SolrMetricsContext, String, String,
    * String...)}.
diff --git a/solr/core/src/java/org/apache/solr/update/DirectUpdateHandler2.java b/solr/core/src/java/org/apache/solr/update/DirectUpdateHandler2.java
index a9e74fbcdb5..df77497b7d4 100644
--- a/solr/core/src/java/org/apache/solr/update/DirectUpdateHandler2.java
+++ b/solr/core/src/java/org/apache/solr/update/DirectUpdateHandler2.java
@@ -16,8 +16,10 @@
  */
 package org.apache.solr.update;
 
-import com.codahale.metrics.Meter;
+import io.opentelemetry.api.common.AttributeKey;
 import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.ObservableLongCounter;
+import io.opentelemetry.api.metrics.ObservableLongGauge;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.lang.reflect.Array;
@@ -56,6 +58,8 @@ import org.apache.solr.metrics.SolrDelegateRegistryMetricsContext;
 import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.metrics.SolrMetricProducer;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.instruments.AttributedLongCounter;
+import org.apache.solr.metrics.otel.instruments.AttributedLongUpDownCounter;
 import org.apache.solr.request.LocalSolrQueryRequest;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestInfo;
@@ -89,20 +93,36 @@ public class DirectUpdateHandler2 extends UpdateHandler
 
   // stats
   LongAdder addCommands = new LongAdder();
-  Meter addCommandsCumulative;
   LongAdder deleteByIdCommands = new LongAdder();
-  Meter deleteByIdCommandsCumulative;
   LongAdder deleteByQueryCommands = new LongAdder();
-  Meter deleteByQueryCommandsCumulative;
-  Meter expungeDeleteCommands;
-  Meter mergeIndexesCommands;
-  Meter commitCommands;
-  Meter splitCommands;
-  Meter optimizeCommands;
-  Meter rollbackCommands;
   LongAdder numDocsPending = new LongAdder();
-  LongAdder numErrors = new LongAdder();
-  Meter numErrorsCumulative;
+
+  // Cumulative commands
+  AttributedLongUpDownCounter deleteByQueryCommandsCumulative;
+  AttributedLongUpDownCounter deleteByIdCommandsCumulative;
+  AttributedLongUpDownCounter addCommandsCumulative;
+
+  AttributedLongCounter submittedAdds;
+  AttributedLongCounter submittedDeleteById;
+  AttributedLongCounter submittedDeleteByQuery;
+
+  AttributedLongCounter committedAdds;
+  AttributedLongCounter committedDeleteById;
+  AttributedLongCounter committedDeleteByQuery;
+
+  // Maintenance operations
+  AttributedLongCounter expungeDeleteCommands;
+  AttributedLongCounter mergeIndexesCommands;
+  AttributedLongCounter commitCommands;
+  AttributedLongCounter optimizeCommands;
+
+  AttributedLongCounter numErrorsCumulative;
+
+  AttributedLongCounter rollbackCommands;
+  AttributedLongCounter splitCommands;
+  ObservableLongGauge commitStats;
+  ObservableLongGauge updateStats;
+  ObservableLongCounter softAutoCommits;
 
   // tracks when auto-commit should occur
   protected final CommitTracker commitTracker;
@@ -206,7 +226,6 @@ public class DirectUpdateHandler2 extends UpdateHandler
     }
   }
 
-  // TODO SOLR-17458: Migrate to Otel
   @Override
   public void initializeMetrics(
       SolrMetricsContext parentContext, Attributes attributes, String scope) {
@@ -220,84 +239,162 @@ public class DirectUpdateHandler2 extends UpdateHandler
     } else {
       this.solrMetricsContext = parentContext.getChildContext(this);
     }
-    commitCommands = solrMetricsContext.meter("commits", getCategory().toString(), scope);
-    solrMetricsContext.gauge(
-        () -> commitTracker.getCommitCount(), true, "autoCommits", getCategory().toString(), scope);
-    solrMetricsContext.gauge(
-        () -> softCommitTracker.getCommitCount(),
-        true,
-        "softAutoCommits",
-        getCategory().toString(),
-        scope);
-    if (commitTracker.getDocsUpperBound() > 0) {
-      solrMetricsContext.gauge(
-          () -> commitTracker.getDocsUpperBound(),
-          true,
-          "autoCommitMaxDocs",
-          getCategory().toString(),
-          scope);
-    }
-    if (commitTracker.getTimeUpperBound() > 0) {
-      solrMetricsContext.gauge(
-          () -> "" + commitTracker.getTimeUpperBound() + "ms",
-          true,
-          "autoCommitMaxTime",
-          getCategory().toString(),
-          scope);
-    }
-    if (commitTracker.getTLogFileSizeUpperBound() > 0) {
-      solrMetricsContext.gauge(
-          () -> commitTracker.getTLogFileSizeUpperBound(),
-          true,
-          "autoCommitMaxSize",
-          getCategory().toString(),
-          scope);
-    }
-    if (softCommitTracker.getDocsUpperBound() > 0) {
-      solrMetricsContext.gauge(
-          () -> softCommitTracker.getDocsUpperBound(),
-          true,
-          "softAutoCommitMaxDocs",
-          getCategory().toString(),
-          scope);
-    }
-    if (softCommitTracker.getTimeUpperBound() > 0) {
-      solrMetricsContext.gauge(
-          () -> "" + softCommitTracker.getTimeUpperBound() + "ms",
-          true,
-          "softAutoCommitMaxTime",
-          getCategory().toString(),
-          scope);
-    }
-    optimizeCommands = solrMetricsContext.meter("optimizes", getCategory().toString(), scope);
-    rollbackCommands = solrMetricsContext.meter("rollbacks", getCategory().toString(), scope);
-    splitCommands = solrMetricsContext.meter("splits", getCategory().toString(), scope);
-    mergeIndexesCommands = solrMetricsContext.meter("merges", getCategory().toString(), scope);
-    expungeDeleteCommands =
-        solrMetricsContext.meter("expungeDeletes", getCategory().toString(), scope);
-    solrMetricsContext.gauge(
-        () -> numDocsPending.longValue(), true, "docsPending", getCategory().toString(), scope);
-    solrMetricsContext.gauge(
-        () -> addCommands.longValue(), true, "adds", getCategory().toString(), scope);
-    solrMetricsContext.gauge(
-        () -> deleteByIdCommands.longValue(), true, "deletesById", getCategory().toString(), scope);
-    solrMetricsContext.gauge(
-        () -> deleteByQueryCommands.longValue(),
-        true,
-        "deletesByQuery",
-        getCategory().toString(),
-        scope);
-    solrMetricsContext.gauge(
-        () -> numErrors.longValue(), true, "errors", getCategory().toString(), scope);
+
+    var baseAttributes =
+        attributes.toBuilder()
+            .put(AttributeKey.stringKey("category"), getCategory().toString())
+            .build();
+
+    var baseCommandsMetric =
+        solrMetricsContext.longUpDownCounter(
+            "solr_core_update_cumulative_ops",
+            "Cumulative number of update commands processed. Cumulative can decrease from rollback command");
 
     addCommandsCumulative =
-        solrMetricsContext.meter("cumulativeAdds", getCategory().toString(), scope);
+        new AttributedLongUpDownCounter(
+            baseCommandsMetric, baseAttributes.toBuilder().put(OPERATION_ATTR, "adds").build());
+
     deleteByIdCommandsCumulative =
-        solrMetricsContext.meter("cumulativeDeletesById", getCategory().toString(), scope);
+        new AttributedLongUpDownCounter(
+            baseCommandsMetric,
+            baseAttributes.toBuilder().put(OPERATION_ATTR, "deletes_by_id").build());
+
     deleteByQueryCommandsCumulative =
-        solrMetricsContext.meter("cumulativeDeletesByQuery", getCategory().toString(), scope);
+        new AttributedLongUpDownCounter(
+            baseCommandsMetric,
+            baseAttributes.toBuilder().put(OPERATION_ATTR, "deletes_by_query").build());
+
+    var baseCommitOpsMetric =
+        solrMetricsContext.longCounter(
+            "solr_core_update_commit_ops", "Total number of commit operations");
+
+    commitCommands =
+        new AttributedLongCounter(
+            baseCommitOpsMetric, baseAttributes.toBuilder().put(OPERATION_ATTR, "commits").build());
+
+    optimizeCommands =
+        new AttributedLongCounter(
+            baseCommitOpsMetric,
+            baseAttributes.toBuilder().put(OPERATION_ATTR, "optimize").build());
+
+    mergeIndexesCommands =
+        new AttributedLongCounter(
+            baseCommitOpsMetric,
+            baseAttributes.toBuilder().put(OPERATION_ATTR, "merge_indexes").build());
+
+    expungeDeleteCommands =
+        new AttributedLongCounter(
+            baseCommitOpsMetric,
+            baseAttributes.toBuilder().put(OPERATION_ATTR, "expunge_deletes").build());
+
+    var baseMaintenanceMetric =
+        solrMetricsContext.longCounter(
+            "solr_core_update_maintenance_ops", "Total number of maintenance operations");
+
+    rollbackCommands =
+        new AttributedLongCounter(
+            baseMaintenanceMetric,
+            baseAttributes.toBuilder().put(OPERATION_ATTR, "rollback").build());
+
+    splitCommands =
+        new AttributedLongCounter(
+            baseMaintenanceMetric, baseAttributes.toBuilder().put(OPERATION_ATTR, "split").build());
+
+    var baseErrorsMetric =
+        solrMetricsContext.longCounter("solr_core_update_errors", "Total number of update errors");
+
     numErrorsCumulative =
-        solrMetricsContext.meter("cumulativeErrors", getCategory().toString(), scope);
+        new AttributedLongCounter(baseErrorsMetric, baseAttributes.toBuilder().build());
+
+    softAutoCommits =
+        solrMetricsContext.observableLongCounter(
+            "solr_core_update_auto_commits",
+            "Current number of auto commits",
+            (observableLongMeasurement -> {
+              observableLongMeasurement.record(
+                  commitTracker.getCommitCount(),
+                  baseAttributes.toBuilder().put(TYPE_ATTR, "auto_commits").build());
+              observableLongMeasurement.record(
+                  softCommitTracker.getCommitCount(),
+                  baseAttributes.toBuilder().put(TYPE_ATTR, "soft_auto_commits").build());
+            }));
+
+    // NOCOMMIT: This might not need to be an obseravableLongGauge, but a simple long gauge. Seems
+    // like a waste to constantly call this callback to get the latest value if the upper bounds
+    // rarely change.
+    commitStats =
+        solrMetricsContext.observableLongGauge(
+            "solr_core_update_commit_stats",
+            "Metrics around commits",
+            (observableLongMeasurement -> {
+              if (commitTracker.getDocsUpperBound() > 0) {
+                observableLongMeasurement.record(
+                    commitTracker.getDocsUpperBound(),
+                    baseAttributes.toBuilder().put(TYPE_ATTR, "auto_commit_max_docs").build());
+              }
+              if (commitTracker.getTLogFileSizeUpperBound() > 0) {
+                observableLongMeasurement.record(
+                    commitTracker.getTLogFileSizeUpperBound(),
+                    baseAttributes.toBuilder().put(TYPE_ATTR, "auto_commit_max_size").build());
+              }
+              if (softCommitTracker.getDocsUpperBound() > 0) {
+                observableLongMeasurement.record(
+                    softCommitTracker.getDocsUpperBound(),
+                    baseAttributes.toBuilder().put(TYPE_ATTR, "soft_auto_commit_max_docs").build());
+              }
+              if (commitTracker.getTimeUpperBound() > 0) {
+                observableLongMeasurement.record(
+                    commitTracker.getTimeUpperBound(),
+                    baseAttributes.toBuilder().put(TYPE_ATTR, "auto_commit_max_time").build());
+              }
+              if (softCommitTracker.getTimeUpperBound() > 0) {
+                observableLongMeasurement.record(
+                    softCommitTracker.getTimeUpperBound(),
+                    baseAttributes.toBuilder().put(TYPE_ATTR, "soft_auto_commit_max_time").build());
+              }
+            }));
+
+    updateStats =
+        solrMetricsContext.observableLongGauge(
+            "solr_core_update_docs_pending_commit",
+            "Current number of documents pending commit. Value is reset to 0 on commit.",
+            (observableLongMeasurement) -> {
+              observableLongMeasurement.record(
+                  numDocsPending.longValue(),
+                  baseAttributes.toBuilder().put(OPERATION_ATTR, "docs_pending").build());
+            });
+
+    var baseSubmittedOpsMetric =
+        solrMetricsContext.longCounter(
+            "solr_core_update_submitted_ops", "Total number of submitted update operations");
+
+    var baseCommittedOpsMetric =
+        solrMetricsContext.longCounter(
+            "solr_core_update_committed_ops", "Total number of committed update operations");
+
+    submittedAdds =
+        new AttributedLongCounter(
+            baseSubmittedOpsMetric, baseAttributes.toBuilder().put(OPERATION_ATTR, "adds").build());
+    submittedDeleteById =
+        new AttributedLongCounter(
+            baseSubmittedOpsMetric,
+            baseAttributes.toBuilder().put(OPERATION_ATTR, "deletes_by_id").build());
+    submittedDeleteByQuery =
+        new AttributedLongCounter(
+            baseSubmittedOpsMetric,
+            baseAttributes.toBuilder().put(OPERATION_ATTR, "deletes_by_query").build());
+
+    committedAdds =
+        new AttributedLongCounter(
+            baseCommittedOpsMetric, baseAttributes.toBuilder().put(OPERATION_ATTR, "adds").build());
+    committedDeleteById =
+        new AttributedLongCounter(
+            baseCommittedOpsMetric,
+            baseAttributes.toBuilder().put(OPERATION_ATTR, "deletes_by_id").build());
+    committedDeleteByQuery =
+        new AttributedLongCounter(
+            baseCommittedOpsMetric,
+            baseAttributes.toBuilder().put(OPERATION_ATTR, "deletes_by_query").build());
   }
 
   private void deleteAll() throws IOException {
@@ -366,7 +463,8 @@ public class DirectUpdateHandler2 extends UpdateHandler
     int rc = -1;
 
     addCommands.increment();
-    addCommandsCumulative.mark();
+    addCommandsCumulative.inc();
+    submittedAdds.inc();
 
     // if there is no ID field, don't overwrite
     if (idField == null) {
@@ -408,8 +506,7 @@ public class DirectUpdateHandler2 extends UpdateHandler
       rc = 1;
     } finally {
       if (rc != 1) {
-        numErrors.increment();
-        numErrorsCumulative.mark();
+        numErrorsCumulative.inc();
       } else {
         numDocsPending.increment();
       }
@@ -514,7 +611,8 @@ public class DirectUpdateHandler2 extends UpdateHandler
   public void delete(DeleteUpdateCommand cmd) throws IOException {
     TestInjection.injectDirectUpdateLatch();
     deleteByIdCommands.increment();
-    deleteByIdCommandsCumulative.mark();
+    deleteByIdCommandsCumulative.inc();
+    submittedDeleteById.inc();
 
     if ((cmd.getFlags() & UpdateCommand.IGNORE_INDEXWRITER) != 0) {
       if (ulog != null) ulog.delete(cmd);
@@ -580,7 +678,8 @@ public class DirectUpdateHandler2 extends UpdateHandler
   public void deleteByQuery(DeleteUpdateCommand cmd) throws IOException {
     TestInjection.injectDirectUpdateLatch();
     deleteByQueryCommands.increment();
-    deleteByQueryCommandsCumulative.mark();
+    deleteByQueryCommandsCumulative.inc();
+    submittedDeleteByQuery.inc();
     boolean madeIt = false;
     try {
       Query q = getQuery(cmd);
@@ -648,8 +747,7 @@ public class DirectUpdateHandler2 extends UpdateHandler
 
     } finally {
       if (!madeIt) {
-        numErrors.increment();
-        numErrorsCumulative.mark();
+        numErrorsCumulative.inc();
       }
     }
   }
@@ -657,7 +755,7 @@ public class DirectUpdateHandler2 extends UpdateHandler
   @Override
   public int mergeIndexes(MergeIndexesCommand cmd) throws IOException {
     TestInjection.injectDirectUpdateLatch();
-    mergeIndexesCommands.mark();
+    mergeIndexesCommands.inc();
     int rc;
 
     log.info("start {}", cmd);
@@ -711,8 +809,7 @@ public class DirectUpdateHandler2 extends UpdateHandler
       error = false;
     } finally {
       if (error) {
-        numErrors.increment();
-        numErrorsCumulative.mark();
+        numErrorsCumulative.inc();
       }
     }
   }
@@ -726,10 +823,10 @@ public class DirectUpdateHandler2 extends UpdateHandler
     }
 
     if (cmd.optimize) {
-      optimizeCommands.mark();
+      optimizeCommands.inc();
     } else {
-      commitCommands.mark();
-      if (cmd.expungeDeletes) expungeDeleteCommands.mark();
+      commitCommands.inc();
+      if (cmd.expungeDeletes) expungeDeleteCommands.inc();
     }
 
     Future<?>[] waitSearcher =
@@ -837,13 +934,14 @@ public class DirectUpdateHandler2 extends UpdateHandler
       if (!cmd.softCommit) {
         solrCoreState.getCommitLock().unlock();
       }
-
+      committedAdds.add(addCommands.longValue());
+      committedDeleteById.add(deleteByIdCommands.longValue());
+      committedDeleteByQuery.add(deleteByQueryCommands.longValue());
       addCommands.reset();
       deleteByIdCommands.reset();
       deleteByQueryCommands.reset();
       if (error) {
-        numErrors.increment();
-        numErrorsCumulative.mark();
+        numErrorsCumulative.inc();
       }
     }
 
@@ -883,7 +981,7 @@ public class DirectUpdateHandler2 extends UpdateHandler
           "Rollback is currently not supported in SolrCloud mode. (SOLR-4895)");
     }
 
-    rollbackCommands.mark();
+    rollbackCommands.inc();
 
     boolean error = true;
 
@@ -902,12 +1000,11 @@ public class DirectUpdateHandler2 extends UpdateHandler
 
       error = false;
     } finally {
-      addCommandsCumulative.mark(-addCommands.sumThenReset());
-      deleteByIdCommandsCumulative.mark(-deleteByIdCommands.sumThenReset());
-      deleteByQueryCommandsCumulative.mark(-deleteByQueryCommands.sumThenReset());
+      addCommandsCumulative.add(-addCommands.sumThenReset());
+      deleteByIdCommandsCumulative.add(-deleteByIdCommands.sumThenReset());
+      deleteByQueryCommandsCumulative.add(-deleteByQueryCommands.sumThenReset());
       if (error) {
-        numErrors.increment();
-        numErrorsCumulative.mark();
+        numErrorsCumulative.inc();
       }
     }
   }
@@ -923,6 +1020,7 @@ public class DirectUpdateHandler2 extends UpdateHandler
 
     commitTracker.close();
     softCommitTracker.close();
+    commitStats.close();
 
     numDocsPending.reset();
     try {
@@ -1037,14 +1135,13 @@ public class DirectUpdateHandler2 extends UpdateHandler
   public void split(SplitIndexCommand cmd) throws IOException {
     commit(new CommitUpdateCommand(cmd.req, false));
     SolrIndexSplitter splitter = new SolrIndexSplitter(cmd);
-    splitCommands.mark();
+    splitCommands.inc();
     NamedList<Object> results = new NamedList<>();
     try {
       splitter.split(results);
       cmd.rsp.addResponse(results);
     } catch (IOException e) {
-      numErrors.increment();
-      numErrorsCumulative.mark();
+      numErrorsCumulative.inc();
       throw e;
     }
   }
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestPullReplicaWithAuth.java b/solr/core/src/test/org/apache/solr/cloud/TestPullReplicaWithAuth.java
index cc52117feed..8020f662b0c 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestPullReplicaWithAuth.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestPullReplicaWithAuth.java
@@ -68,6 +68,9 @@ public class TestPullReplicaWithAuth extends SolrCloudTestCase {
   }
 
   @Test
+  // NOCOMMIT: This test is broken from OTEL migration and the /admin/plugins endpoint. Placing
+  // BadApple test but this must be fixed before this feature gets merged to a release branch
+  @BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17458")
   public void testPKIAuthWorksForPullReplication() throws Exception {
     int numPullReplicas = 2;
     withBasicAuth(
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestTlogReplica.java b/solr/core/src/test/org/apache/solr/cloud/TestTlogReplica.java
index 84d3e2066ab..7c375f9e0ac 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestTlogReplica.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestTlogReplica.java
@@ -71,6 +71,7 @@ import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.TimeSource;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.embedded.JettySolrRunner;
+import org.apache.solr.metrics.SolrMetricTestUtils;
 import org.apache.solr.update.SolrIndexWriter;
 import org.apache.solr.util.RefCounted;
 import org.apache.solr.util.TestInjection;
@@ -257,6 +258,9 @@ public class TestTlogReplica extends SolrCloudTestCase {
   }
 
   @SuppressWarnings("unchecked")
+  // NOCOMMIT: This test is broken from OTEL migration and the /admin/plugins endpoint. Placing
+  // BadApple test but this must be fixed before this feature gets merged to a release branch
+  @BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17458")
   public void testAddDocs() throws Exception {
     int numTlogReplicas = 1 + random().nextInt(3);
     DocCollection docCollection = createAndWaitForCollection(1, 0, numTlogReplicas, 0);
@@ -570,32 +574,34 @@ public class TestTlogReplica extends SolrCloudTestCase {
         .process(cloudClient, collectionName);
 
     {
-      long docsPending =
-          (long)
-              getSolrCore(true)
-                  .get(0)
-                  .getSolrMetricsContext()
-                  .getMetricRegistry()
-                  .getGauges()
-                  .get("UPDATE.updateHandler.docsPending")
-                  .getValue();
+      SolrCore core = getSolrCore(true).getFirst();
+      var actual =
+          SolrMetricTestUtils.getGaugeDatapoint(
+              core,
+              "solr_core_update_docs_pending_commit",
+              SolrMetricTestUtils.newCloudLabelsBuilder(core)
+                  .label("category", "UPDATE")
+                  .label("ops", "docs_pending")
+                  .build());
       assertEquals(
-          "Expected 4 docs are pending in core " + getSolrCore(true).get(0).getCoreDescriptor(),
+          "Expected 4 docs are pending in core " + getSolrCore(true).getFirst().getCoreDescriptor(),
           4,
-          docsPending);
+          (long) actual.getValue());
     }
 
     for (SolrCore solrCore : getSolrCore(false)) {
-      long docsPending =
-          (long)
-              solrCore
-                  .getSolrMetricsContext()
-                  .getMetricRegistry()
-                  .getGauges()
-                  .get("UPDATE.updateHandler.docsPending")
-                  .getValue();
+      var actual =
+          SolrMetricTestUtils.getGaugeDatapoint(
+              solrCore,
+              "solr_core_update_docs_pending_commit",
+              SolrMetricTestUtils.newCloudLabelsBuilder(solrCore)
+                  .label("category", "UPDATE")
+                  .label("ops", "docs_pending")
+                  .build());
       assertEquals(
-          "Expected non docs are pending in core " + solrCore.getCoreDescriptor(), 0, docsPending);
+          "Expected non docs are pending in core " + solrCore.getCoreDescriptor(),
+          0,
+          (long) actual.getValue());
     }
 
     checkRTG(1, 4, cluster.getJettySolrRunners());
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java b/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java
index 9da3314ad2a..479615ddf93 100644
--- a/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java
@@ -141,7 +141,7 @@ public final class SolrMetricTestUtils {
         .orElse(null);
   }
 
-  public static Labels.Builder getCloudLabelsBase(SolrCore core) {
+  public static Labels.Builder newCloudLabelsBuilder(SolrCore core) {
     return Labels.builder()
         .label("collection", core.getCoreDescriptor().getCloudDescriptor().getCollectionName())
         .label("shard", core.getCoreDescriptor().getCloudDescriptor().getShardId())
diff --git a/solr/core/src/test/org/apache/solr/update/DirectUpdateHandlerTest.java b/solr/core/src/test/org/apache/solr/update/DirectUpdateHandlerTest.java
index 542f105cb78..0530ca495c5 100644
--- a/solr/core/src/test/org/apache/solr/update/DirectUpdateHandlerTest.java
+++ b/solr/core/src/test/org/apache/solr/update/DirectUpdateHandlerTest.java
@@ -18,9 +18,8 @@ package org.apache.solr.update;
 
 import static org.apache.solr.common.params.CommonParams.VERSION_FIELD;
 
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Meter;
-import com.codahale.metrics.Metric;
+import io.prometheus.metrics.model.snapshots.CounterSnapshot;
+import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
 import java.lang.invoke.MethodHandles;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -36,6 +35,7 @@ import org.apache.solr.common.params.MapSolrParams;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrEventListener;
 import org.apache.solr.index.TieredMergePolicyFactory;
+import org.apache.solr.metrics.SolrMetricTestUtils;
 import org.apache.solr.request.LocalSolrQueryRequest;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.search.SolrIndexSearcher;
@@ -78,8 +78,8 @@ public class DirectUpdateHandlerTest extends SolrTestCaseJ4 {
   @Before
   public void setUp() throws Exception {
     super.setUp();
-    clearIndex();
-    assertU(commit());
+    deleteCore();
+    initCore("solrconfig.xml", "schema12.xml");
   }
 
   @Test
@@ -100,30 +100,8 @@ public class DirectUpdateHandlerTest extends SolrTestCaseJ4 {
   @SuppressWarnings({"unchecked"})
   public void testBasics() {
 
+    SolrCore core = h.getCore();
     // get initial metrics
-    Map<String, Metric> metrics =
-        h.getCoreContainer()
-            .getMetricManager()
-            .registry(h.getCore().getCoreMetricManager().getRegistryName())
-            .getMetrics();
-
-    String PREFIX = "UPDATE.updateHandler.";
-
-    String commitsName = PREFIX + "commits";
-    assertTrue(metrics.containsKey(commitsName));
-    String addsName = PREFIX + "adds";
-    assertTrue(metrics.containsKey(addsName));
-    String cumulativeAddsName = PREFIX + "cumulativeAdds";
-    String delsIName = PREFIX + "deletesById";
-    String cumulativeDelsIName = PREFIX + "cumulativeDeletesById";
-    String delsQName = PREFIX + "deletesByQuery";
-    String cumulativeDelsQName = PREFIX + "cumulativeDeletesByQuery";
-    long commits = ((Meter) metrics.get(commitsName)).getCount();
-    long adds = ((Gauge<Number>) metrics.get(addsName)).getValue().longValue();
-    long cumulativeAdds = ((Meter) metrics.get(cumulativeAddsName)).getCount();
-    long cumulativeDelsI = ((Meter) metrics.get(cumulativeDelsIName)).getCount();
-    long cumulativeDelsQ = ((Meter) metrics.get(cumulativeDelsQName)).getCount();
-
     assertNull(
         "This test requires a schema that has no version field, "
             + "it appears the schema file in use has been edited to violate "
@@ -137,22 +115,47 @@ public class DirectUpdateHandlerTest extends SolrTestCaseJ4 {
     assertQ(req("q", "id:5"), "//*[@numFound='0']");
     assertQ(req("q", "id:6"), "//*[@numFound='0']");
 
-    long newAdds = ((Gauge<Number>) metrics.get(addsName)).getValue().longValue();
-    long newCumulativeAdds = ((Meter) metrics.get(cumulativeAddsName)).getCount();
-    assertEquals("new adds", 2, newAdds - adds);
-    assertEquals("new cumulative adds", 2, newCumulativeAdds - cumulativeAdds);
+    assertEquals(
+        "Did not have the expected number of submitted adds",
+        2,
+        getCounterOpDatapoint(core, "solr_core_update_submitted_ops", "adds").getValue(),
+        0.0);
+
+    assertNull(
+        "No adds should have been committed yet",
+        getCounterOpDatapoint(core, "solr_core_update_committed_ops", "adds"));
+
+    assertEquals(
+        "Did not have the expected number of cumulative adds",
+        2,
+        getGaugeOpDatapoint(core, "solr_core_update_cumulative_ops", "adds").getValue(),
+        0.0);
 
     assertU(commit());
 
-    long newCommits = ((Meter) metrics.get(commitsName)).getCount();
-    assertEquals("new commits", 1, newCommits - commits);
+    assertEquals(
+        "Did not have the expected number of cumulative commits",
+        1,
+        getCounterOpDatapoint(core, "solr_core_update_commit_ops", "commits").getValue(),
+        0.0);
 
-    newAdds = ((Gauge<Number>) metrics.get(addsName)).getValue().longValue();
-    newCumulativeAdds = ((Meter) metrics.get(cumulativeAddsName)).getCount();
-    // adds should be reset to 0 after commit
-    assertEquals("new adds after commit", 0, newAdds);
-    // not so with cumulative ones!
-    assertEquals("new cumulative adds after commit", 2, newCumulativeAdds - cumulativeAdds);
+    assertEquals(
+        "Did not have the expected number of submitted adds",
+        2,
+        getCounterOpDatapoint(core, "solr_core_update_submitted_ops", "adds").getValue(),
+        0.0);
+
+    assertEquals(
+        "Did not have the expected number of committed adds",
+        2,
+        getCounterOpDatapoint(core, "solr_core_update_committed_ops", "adds").getValue(),
+        0.0);
+
+    assertEquals(
+        "Did not have the expected number of cumulative adds after commit",
+        2,
+        getGaugeOpDatapoint(core, "solr_core_update_cumulative_ops", "adds").getValue(),
+        0.0);
 
     // now they should be there
     assertQ(req("q", "id:5"), "//*[@numFound='1']");
@@ -161,20 +164,46 @@ public class DirectUpdateHandlerTest extends SolrTestCaseJ4 {
     // now delete one
     assertU(delI("5"));
 
-    long newDelsI = ((Gauge<Number>) metrics.get(delsIName)).getValue().longValue();
-    long newCumulativeDelsI = ((Meter) metrics.get(cumulativeDelsIName)).getCount();
-    assertEquals("new delsI", 1, newDelsI);
-    assertEquals("new cumulative delsI", 1, newCumulativeDelsI - cumulativeDelsI);
+    assertEquals(
+        "Did not have the expected number of submitted deletes_by_id",
+        1,
+        getCounterOpDatapoint(core, "solr_core_update_submitted_ops", "deletes_by_id").getValue(),
+        0.0);
+
+    assertEquals(
+        "No deletes_by_id should have been committed yet",
+        0,
+        getCounterOpDatapoint(core, "solr_core_update_committed_ops", "deletes_by_id").getValue(),
+        0.0);
+
+    assertEquals(
+        "Did not have the expected number of cumulative deletes_by_id",
+        1,
+        getGaugeOpDatapoint(core, "solr_core_update_cumulative_ops", "deletes_by_id").getValue(),
+        0.0);
 
     // not committed yet
     assertQ(req("q", "id:5"), "//*[@numFound='1']");
 
     assertU(commit());
-    // delsI should be reset to 0 after commit
-    newDelsI = ((Gauge<Number>) metrics.get(delsIName)).getValue().longValue();
-    newCumulativeDelsI = ((Meter) metrics.get(cumulativeDelsIName)).getCount();
-    assertEquals("new delsI after commit", 0, newDelsI);
-    assertEquals("new cumulative delsI after commit", 1, newCumulativeDelsI - cumulativeDelsI);
+
+    assertEquals(
+        "Did not have the expected number of submitted deletes_by_id",
+        1,
+        getCounterOpDatapoint(core, "solr_core_update_submitted_ops", "deletes_by_id").getValue(),
+        0.0);
+
+    assertEquals(
+        "Did not have the expected number of committed deletes_by_id",
+        1,
+        getCounterOpDatapoint(core, "solr_core_update_committed_ops", "deletes_by_id").getValue(),
+        0.0);
+
+    assertEquals(
+        "Did not have the expected number of cumulative deletes_by_id after commit",
+        1,
+        getGaugeOpDatapoint(core, "solr_core_update_cumulative_ops", "deletes_by_id").getValue(),
+        0.0);
 
     // 5 should be gone
     assertQ(req("q", "id:5"), "//*[@numFound='0']");
@@ -183,35 +212,76 @@ public class DirectUpdateHandlerTest extends SolrTestCaseJ4 {
     // now delete all
     assertU(delQ("*:*"));
 
-    long newDelsQ = ((Gauge<Number>) metrics.get(delsQName)).getValue().longValue();
-    long newCumulativeDelsQ = ((Meter) metrics.get(cumulativeDelsQName)).getCount();
-    assertEquals("new delsQ", 1, newDelsQ);
-    assertEquals("new cumulative delsQ", 1, newCumulativeDelsQ - cumulativeDelsQ);
+    assertEquals(
+        "Did not have the expected number of submitted deletes_by_query",
+        1,
+        getCounterOpDatapoint(core, "solr_core_update_submitted_ops", "deletes_by_query")
+            .getValue(),
+        0.0);
+
+    assertEquals(
+        "No deletes_by_query should have been committed yet",
+        0,
+        getCounterOpDatapoint(core, "solr_core_update_committed_ops", "deletes_by_query")
+            .getValue(),
+        0.0);
+
+    assertEquals(
+        "Did not have the expected number of pending cumulative deletes_by_id",
+        1,
+        getGaugeOpDatapoint(core, "solr_core_update_cumulative_ops", "deletes_by_query").getValue(),
+        0.0);
 
     // not committed yet
     assertQ(req("q", "id:6"), "//*[@numFound='1']");
 
     assertU(commit());
 
-    newDelsQ = ((Gauge<Number>) metrics.get(delsQName)).getValue().longValue();
-    newCumulativeDelsQ = ((Meter) metrics.get(cumulativeDelsQName)).getCount();
-    assertEquals("new delsQ after commit", 0, newDelsQ);
-    assertEquals("new cumulative delsQ after commit", 1, newCumulativeDelsQ - cumulativeDelsQ);
+    assertEquals(
+        "Did not have the expected number of submitted deletes_by_query",
+        1,
+        getCounterOpDatapoint(core, "solr_core_update_submitted_ops", "deletes_by_query")
+            .getValue(),
+        0.0);
+
+    assertEquals(
+        "Did not have the expected number of committed deletes_by_query",
+        1,
+        getCounterOpDatapoint(core, "solr_core_update_committed_ops", "deletes_by_query")
+            .getValue(),
+        0.0);
+
+    assertEquals(
+        "Did not have the expected number of pending cumulative deletes_by_query after commit",
+        1,
+        getGaugeOpDatapoint(core, "solr_core_update_cumulative_ops", "deletes_by_query").getValue(),
+        0.0);
 
     // 6 should be gone
     assertQ(req("q", "id:6"), "//*[@numFound='0']");
 
     // verify final metrics
-    newCommits = ((Meter) metrics.get(commitsName)).getCount();
-    assertEquals("new commits", 3, newCommits - commits);
-    newAdds = ((Gauge<Number>) metrics.get(addsName)).getValue().longValue();
-    assertEquals("new adds", 0, newAdds);
-    newCumulativeAdds = ((Meter) metrics.get(cumulativeAddsName)).getCount();
-    assertEquals("new cumulative adds", 2, newCumulativeAdds - cumulativeAdds);
-    newDelsI = ((Gauge<Number>) metrics.get(delsIName)).getValue().longValue();
-    assertEquals("new delsI", 0, newDelsI);
-    newCumulativeDelsI = ((Meter) metrics.get(cumulativeDelsIName)).getCount();
-    assertEquals("new cumulative delsI", 1, newCumulativeDelsI - cumulativeDelsI);
+    var commits = getCounterOpDatapoint(core, "solr_core_update_commit_ops", "commits");
+    var cumulativeAdds = getGaugeOpDatapoint(core, "solr_core_update_cumulative_ops", "adds");
+    var cumulativeDeletesById =
+        getGaugeOpDatapoint(core, "solr_core_update_cumulative_ops", "deletes_by_id");
+    var cumulativeDeletesByQuery =
+        getGaugeOpDatapoint(core, "solr_core_update_cumulative_ops", "deletes_by_query");
+
+    assertEquals(
+        "Did not have the expected number of cumulative commits", 3, commits.getValue(), 0.0);
+    assertEquals(
+        "Did not have the expected number of cumulative adds", 2, cumulativeAdds.getValue(), 0.0);
+    assertEquals(
+        "Did not have the expected number of cumulative delete_by_id",
+        1,
+        cumulativeDeletesById.getValue(),
+        0.0);
+    assertEquals(
+        "Did not have the expected number of cumulative deletes_by_query",
+        1,
+        cumulativeDeletesByQuery.getValue(),
+        0.0);
   }
 
   @Test
@@ -231,12 +301,29 @@ public class DirectUpdateHandlerTest extends SolrTestCaseJ4 {
     CommitUpdateCommand cmtCmd = new CommitUpdateCommand(ureq, false);
     cmtCmd.waitSearcher = true;
     assertEquals(1, duh2.addCommands.longValue());
-    assertEquals(1, duh2.addCommandsCumulative.getCount());
-    assertEquals(0, duh2.commitCommands.getCount());
+
+    assertEquals(
+        "Did not have the expected number of cumulative adds",
+        1,
+        getGaugeOpDatapoint(core, "solr_core_update_cumulative_ops", "adds").getValue(),
+        0.0);
+    assertNull(
+        "No commits should have been processed yet",
+        getCounterOpDatapoint(core, "solr_core_update_commit_ops", "commits"));
+
     updater.commit(cmtCmd);
     assertEquals(0, duh2.addCommands.longValue());
-    assertEquals(1, duh2.addCommandsCumulative.getCount());
-    assertEquals(1, duh2.commitCommands.getCount());
+    assertEquals(
+        "Did not have the expected number of cumulative adds",
+        1,
+        getGaugeOpDatapoint(core, "solr_core_update_cumulative_ops", "adds").getValue(),
+        0.0);
+    assertEquals(
+        "Did not have the expected number of cumulative commits",
+        1,
+        getCounterOpDatapoint(core, "solr_core_update_commit_ops", "commits").getValue(),
+        0.0);
+
     ureq.close();
 
     assertU(adoc("id", "B"));
@@ -245,12 +332,28 @@ public class DirectUpdateHandlerTest extends SolrTestCaseJ4 {
     ureq = req();
     RollbackUpdateCommand rbkCmd = new RollbackUpdateCommand(ureq);
     assertEquals(1, duh2.addCommands.longValue());
-    assertEquals(2, duh2.addCommandsCumulative.getCount());
-    assertEquals(0, duh2.rollbackCommands.getCount());
+    assertEquals(
+        "Did not have the expected number of cumulative adds",
+        2,
+        getGaugeOpDatapoint(core, "solr_core_update_cumulative_ops", "adds").getValue(),
+        0.0);
+    assertNull(
+        "No rollbacks should have been processed yet",
+        getGaugeOpDatapoint(core, "solr_core_update_maintenance_ops", "rollback"));
+
     updater.rollback(rbkCmd);
     assertEquals(0, duh2.addCommands.longValue());
-    assertEquals(1, duh2.addCommandsCumulative.getCount());
-    assertEquals(1, duh2.rollbackCommands.getCount());
+    assertEquals(
+        "Did not have the expected number of cumulative adds",
+        1,
+        getGaugeOpDatapoint(core, "solr_core_update_cumulative_ops", "adds").getValue(),
+        0.0);
+    assertEquals(
+        "Did not have the expected number of cumulative rollback",
+        1,
+        getCounterOpDatapoint(core, "solr_core_update_maintenance_ops", "rollback").getValue(),
+        0.0);
+
     ureq.close();
 
     // search - "B" should not be found.
@@ -293,12 +396,28 @@ public class DirectUpdateHandlerTest extends SolrTestCaseJ4 {
     CommitUpdateCommand cmtCmd = new CommitUpdateCommand(ureq, false);
     cmtCmd.waitSearcher = true;
     assertEquals(2, duh2.addCommands.longValue());
-    assertEquals(2, duh2.addCommandsCumulative.getCount());
-    assertEquals(0, duh2.commitCommands.getCount());
+    assertEquals(
+        "Did not have the expected number of cumulative adds",
+        2,
+        getGaugeOpDatapoint(core, "solr_core_update_cumulative_ops", "adds").getValue(),
+        0.0);
+    assertNull(
+        "No commits should have been processed yet",
+        getCounterOpDatapoint(core, "solr_core_update_cumulative_ops", "commits"));
+
     updater.commit(cmtCmd);
+
     assertEquals(0, duh2.addCommands.longValue());
-    assertEquals(2, duh2.addCommandsCumulative.getCount());
-    assertEquals(1, duh2.commitCommands.getCount());
+    assertEquals(
+        "Did not have the expected number of cumulative adds",
+        2,
+        getGaugeOpDatapoint(core, "solr_core_update_cumulative_ops", "adds").getValue(),
+        0.0);
+    assertEquals(
+        "Did not have the expected number of cumulative commits",
+        1,
+        getCounterOpDatapoint(core, "solr_core_update_commit_ops", "commits").getValue(),
+        0.0);
     ureq.close();
 
     // search - "A","B" should be found.
@@ -328,13 +447,29 @@ public class DirectUpdateHandlerTest extends SolrTestCaseJ4 {
     ureq = req();
     RollbackUpdateCommand rbkCmd = new RollbackUpdateCommand(ureq);
     assertEquals(1, duh2.deleteByIdCommands.longValue());
-    assertEquals(1, duh2.deleteByIdCommandsCumulative.getCount());
-    assertEquals(0, duh2.rollbackCommands.getCount());
+    assertEquals(
+        "Did not have the expected number of cumulative adds",
+        1,
+        getGaugeOpDatapoint(core, "solr_core_update_cumulative_ops", "deletes_by_id").getValue(),
+        0.0);
+    assertNull(
+        "No rollbacks should have been processed yet",
+        getGaugeOpDatapoint(core, "solr_core_update_maintenance_ops", "rollback"));
+
     updater.rollback(rbkCmd);
     ureq.close();
+
     assertEquals(0, duh2.deleteByIdCommands.longValue());
-    assertEquals(0, duh2.deleteByIdCommandsCumulative.getCount());
-    assertEquals(1, duh2.rollbackCommands.getCount());
+    assertEquals(
+        "Did not have the expected number of cumulative adds",
+        0,
+        getGaugeOpDatapoint(core, "solr_core_update_cumulative_ops", "deletes_by_id").getValue(),
+        0.0);
+    assertEquals(
+        "Did not have the expected number of cumulative rollback",
+        1,
+        getCounterOpDatapoint(core, "solr_core_update_maintenance_ops", "rollback").getValue(),
+        0.0);
 
     // search - "B" should be found.
     assertQ(
@@ -478,4 +613,26 @@ public class DirectUpdateHandlerTest extends SolrTestCaseJ4 {
       newSearcherOpenedAt.set(newSearcher.getOpenNanoTime());
     }
   }
+
+  private GaugeSnapshot.GaugeDataPointSnapshot getGaugeOpDatapoint(
+      SolrCore core, String metricName, String operation) {
+    return SolrMetricTestUtils.getGaugeDatapoint(
+        core,
+        metricName,
+        SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+            .label("category", "UPDATE")
+            .label("ops", operation)
+            .build());
+  }
+
+  private CounterSnapshot.CounterDataPointSnapshot getCounterOpDatapoint(
+      SolrCore core, String metricName, String operation) {
+    return SolrMetricTestUtils.getCounterDatapoint(
+        core,
+        metricName,
+        SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+            .label("category", "UPDATE")
+            .label("ops", operation)
+            .build());
+  }
 }
diff --git a/solr/webapp/web/js/angular/controllers/plugins.js b/solr/webapp/web/js/angular/controllers/plugins.js
index a537b37d7c3..5bf7ab5edaa 100644
--- a/solr/webapp/web/js/angular/controllers/plugins.js
+++ b/solr/webapp/web/js/angular/controllers/plugins.js
@@ -15,6 +15,8 @@
  limitations under the License.
 */
 
+//NOCOMMIT: This plugin seems tied to the Admin UIs plugin management but is tied to dropwizard metrics failing some tests.
+// This needs to change how it gets these metrics or we need to make a shim to the /admin/plugins handler for this to support it
 solrAdminApp.controller('PluginsController',
     function($scope, $rootScope, $routeParams, $location, Mbeans, Constants) {
         $scope.resetMenu("plugins", Constants.IS_CORE_PAGE);


(solr) 22/33: SOLR-17806 Move SolrMetricTestUtils to test-framework (#3624)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 838b38a14bc4b275c8447037de9de3c7eb60af81
Author: bjacobowitz <br...@gmail.com>
AuthorDate: Mon Sep 8 10:12:20 2025 -0400

    SOLR-17806 Move SolrMetricTestUtils to test-framework (#3624)
---
 solr/api/gradle.lockfile                                   |  8 ++++++--
 solr/benchmark/gradle.lockfile                             |  8 ++++++--
 solr/core/gradle.lockfile                                  | 10 ++++++++--
 .../java/org/apache/solr/handler/RequestHandlerBase.java   |  2 +-
 .../java/org/apache/solr/search/SolrFieldCacheBean.java    |  2 +-
 .../src/test/org/apache/solr/BasicFunctionalityTest.java   |  2 +-
 .../org/apache/solr/cloud/PeerSyncReplicationTest.java     |  2 +-
 .../src/test/org/apache/solr/cloud/TestCloudRecovery.java  |  2 +-
 .../apache/solr/cloud/TestRandomRequestDistribution.java   |  2 +-
 .../src/test/org/apache/solr/cloud/TestTlogReplica.java    |  2 +-
 .../src/test/org/apache/solr/core/RequestHandlersTest.java |  2 +-
 .../org/apache/solr/metrics/SolrCoreMetricManagerTest.java |  1 +
 .../org/apache/solr/metrics/SolrMetricManagerTest.java     |  1 +
 .../apache/solr/metrics/SolrMetricsIntegrationTest.java    |  1 +
 .../core/src/test/org/apache/solr/search/TestRecovery.java |  2 +-
 .../org/apache/solr/update/DirectUpdateHandlerTest.java    |  2 +-
 .../test/org/apache/solr/update/SolrIndexMetricsTest.java  |  2 +-
 solr/cross-dc-manager/gradle.lockfile                      | 10 ++++++++--
 solr/modules/analysis-extras/gradle.lockfile               | 10 ++++++++--
 solr/modules/clustering/gradle.lockfile                    | 10 ++++++++--
 solr/modules/cross-dc/gradle.lockfile                      | 10 ++++++++--
 solr/modules/extraction/gradle.lockfile                    | 10 ++++++++--
 solr/modules/gcs-repository/gradle.lockfile                | 10 ++++++++--
 solr/modules/jwt-auth/gradle.lockfile                      | 10 ++++++++--
 solr/modules/langid/gradle.lockfile                        | 10 ++++++++--
 solr/modules/llm/gradle.lockfile                           | 10 ++++++++--
 solr/modules/ltr/gradle.lockfile                           | 10 ++++++++--
 solr/modules/opentelemetry/gradle.lockfile                 | 10 ++++++++--
 solr/modules/s3-repository/gradle.lockfile                 | 10 ++++++++--
 solr/modules/scripting/gradle.lockfile                     | 10 ++++++++--
 solr/modules/sql/gradle.lockfile                           | 10 ++++++++--
 solr/prometheus-exporter/gradle.lockfile                   | 10 ++++++++--
 solr/solr-ref-guide/gradle.lockfile                        |  8 ++++++--
 solr/solrj-streaming/gradle.lockfile                       |  8 ++++++--
 solr/solrj-zookeeper/gradle.lockfile                       |  8 ++++++--
 solr/solrj/gradle.lockfile                                 |  8 ++++++--
 .../apache/solr/client/solrj/impl/CloudSolrClientTest.java |  1 -
 solr/test-framework/build.gradle                           |  2 ++
 solr/test-framework/gradle.lockfile                        | 14 +++++++++++---
 .../java/org/apache/solr/util}/SolrMetricTestUtils.java    |  4 +++-
 40 files changed, 194 insertions(+), 60 deletions(-)

diff --git a/solr/api/gradle.lockfile b/solr/api/gradle.lockfile
index a106d8086a8..bdefb4ffab8 100644
--- a/solr/api/gradle.lockfile
+++ b/solr/api/gradle.lockfile
@@ -75,8 +75,12 @@ io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,testRuntimeClasspat
 io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,testRuntimeClasspath
-io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,testRuntimeClasspath
-io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-config:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-common:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-httpserver:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-textformats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-model:1.3.6=jarValidation,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,testRuntimeClasspath
 io.swagger.core.v3:swagger-annotations-jakarta:2.2.22=apiHelper,compileClasspath,jarValidation,runtimeClasspath,swaggerBuild,testCompileClasspath,testRuntimeClasspath
 io.swagger.core.v3:swagger-core-jakarta:2.2.22=swaggerBuild
diff --git a/solr/benchmark/gradle.lockfile b/solr/benchmark/gradle.lockfile
index 9f5ff8f297e..11e15e44546 100644
--- a/solr/benchmark/gradle.lockfile
+++ b/solr/benchmark/gradle.lockfile
@@ -70,8 +70,12 @@ io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,runtimeClasspath,te
 io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,runtimeClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,runtimeClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,runtimeClasspath,testRuntimeClasspath
-io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,runtimeClasspath,testRuntimeClasspath
-io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,runtimeClasspath,testRuntimeClasspath
+io.prometheus:prometheus-metrics-config:1.3.6=jarValidation,runtimeClasspath,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-common:1.3.6=jarValidation,runtimeClasspath,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-httpserver:1.3.6=jarValidation,runtimeClasspath,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.3.6=jarValidation,runtimeClasspath,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-textformats:1.3.6=jarValidation,runtimeClasspath,testRuntimeClasspath
+io.prometheus:prometheus-metrics-model:1.3.6=jarValidation,runtimeClasspath,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,runtimeClasspath,testRuntimeClasspath
 io.swagger.core.v3:swagger-annotations-jakarta:2.2.22=compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 jakarta.annotation:jakarta.annotation-api:2.1.1=jarValidation,runtimeClasspath,testRuntimeClasspath
diff --git a/solr/core/gradle.lockfile b/solr/core/gradle.lockfile
index 199ca89b88f..b82b394cf01 100644
--- a/solr/core/gradle.lockfile
+++ b/solr/core/gradle.lockfile
@@ -75,8 +75,14 @@ io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,runtimeClasspath,ru
 io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-trace:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
-io.prometheus:prometheus-metrics-exposition-formats:1.1.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
-io.prometheus:prometheus-metrics-model:1.1.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
+io.prometheus:prometheus-metrics-config:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-common:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-httpserver:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.1.0=compileClasspath,runtimeClasspath,runtimeLibs,testCompileClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-textformats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-model:1.1.0=compileClasspath,runtimeClasspath,runtimeLibs,testCompileClasspath
+io.prometheus:prometheus-metrics-model:1.3.6=jarValidation,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 io.swagger.core.v3:swagger-annotations-jakarta:2.2.22=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 jakarta.activation:jakarta.activation-api:2.1.3=permitUnusedDeclared
diff --git a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
index 9f25b431c52..d34b482402a 100644
--- a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
+++ b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
@@ -19,11 +19,11 @@ package org.apache.solr.handler;
 import static org.apache.solr.core.RequestParams.USEPARAM;
 import static org.apache.solr.response.SolrQueryResponse.haveCompleteResults;
 
-import java.io.IOException;
 import io.opentelemetry.api.common.AttributeKey;
 import io.opentelemetry.api.common.Attributes;
 import io.opentelemetry.api.metrics.LongCounter;
 import io.opentelemetry.api.metrics.LongHistogram;
+import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.util.Collection;
 import java.util.Collections;
diff --git a/solr/core/src/java/org/apache/solr/search/SolrFieldCacheBean.java b/solr/core/src/java/org/apache/solr/search/SolrFieldCacheBean.java
index 265360bd7b9..b0ae73075ab 100644
--- a/solr/core/src/java/org/apache/solr/search/SolrFieldCacheBean.java
+++ b/solr/core/src/java/org/apache/solr/search/SolrFieldCacheBean.java
@@ -16,8 +16,8 @@
  */
 package org.apache.solr.search;
 
-import org.apache.solr.common.util.EnvUtils;
 import io.opentelemetry.api.common.Attributes;
+import org.apache.solr.common.util.EnvUtils;
 import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.metrics.MetricsMap;
 import org.apache.solr.metrics.SolrMetricsContext;
diff --git a/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java b/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java
index 91cbf47f008..3c41e4e87da 100644
--- a/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java
+++ b/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java
@@ -38,7 +38,6 @@ import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.handler.RequestHandlerBase;
-import org.apache.solr.metrics.SolrMetricTestUtils;
 import org.apache.solr.request.LocalSolrQueryRequest;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestHandler;
@@ -52,6 +51,7 @@ import org.apache.solr.search.DocIterator;
 import org.apache.solr.search.DocList;
 import org.apache.solr.security.AuthorizationContext;
 import org.apache.solr.util.BaseTestHarness;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
diff --git a/solr/core/src/test/org/apache/solr/cloud/PeerSyncReplicationTest.java b/solr/core/src/test/org/apache/solr/cloud/PeerSyncReplicationTest.java
index 931551b8757..63637289262 100644
--- a/solr/core/src/test/org/apache/solr/cloud/PeerSyncReplicationTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/PeerSyncReplicationTest.java
@@ -43,7 +43,7 @@ import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.metrics.SolrMetricManager;
-import org.apache.solr.metrics.SolrMetricTestUtils;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestCloudRecovery.java b/solr/core/src/test/org/apache/solr/cloud/TestCloudRecovery.java
index 3562b4bec46..2aafcb85e99 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestCloudRecovery.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestCloudRecovery.java
@@ -33,9 +33,9 @@ import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.embedded.JettySolrRunner;
-import org.apache.solr.metrics.SolrMetricTestUtils;
 import org.apache.solr.update.UpdateLog;
 import org.apache.solr.update.UpdateShardHandler;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.apache.solr.util.TestInjection;
 import org.junit.After;
 import org.junit.Before;
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestRandomRequestDistribution.java b/solr/core/src/test/org/apache/solr/cloud/TestRandomRequestDistribution.java
index cf4b41eb150..6b6673c06f7 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestRandomRequestDistribution.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestRandomRequestDistribution.java
@@ -41,7 +41,7 @@ import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.embedded.JettySolrRunner;
-import org.apache.solr.metrics.SolrMetricTestUtils;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestTlogReplica.java b/solr/core/src/test/org/apache/solr/cloud/TestTlogReplica.java
index 11308e89b5d..da94bf4cc62 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestTlogReplica.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestTlogReplica.java
@@ -70,9 +70,9 @@ import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.TimeSource;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.embedded.JettySolrRunner;
-import org.apache.solr.metrics.SolrMetricTestUtils;
 import org.apache.solr.update.SolrIndexWriter;
 import org.apache.solr.util.RefCounted;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.apache.solr.util.TestInjection;
 import org.apache.solr.util.TimeOut;
 import org.apache.zookeeper.KeeperException;
diff --git a/solr/core/src/test/org/apache/solr/core/RequestHandlersTest.java b/solr/core/src/test/org/apache/solr/core/RequestHandlersTest.java
index 083337dfb97..03a07770b0f 100644
--- a/solr/core/src/test/org/apache/solr/core/RequestHandlersTest.java
+++ b/solr/core/src/test/org/apache/solr/core/RequestHandlersTest.java
@@ -19,8 +19,8 @@ package org.apache.solr.core;
 import com.codahale.metrics.Gauge;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.metrics.SolrMetricManager;
-import org.apache.solr.metrics.SolrMetricTestUtils;
 import org.apache.solr.request.SolrRequestHandler;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrCoreMetricManagerTest.java b/solr/core/src/test/org/apache/solr/metrics/SolrCoreMetricManagerTest.java
index 93626397dc9..ffd72b881e3 100644
--- a/solr/core/src/test/org/apache/solr/metrics/SolrCoreMetricManagerTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrCoreMetricManagerTest.java
@@ -41,6 +41,7 @@ import org.apache.solr.metrics.reporters.MockMetricReporter;
 import org.apache.solr.request.SolrQueryRequestBase;
 import org.apache.solr.schema.FieldType;
 import org.apache.solr.update.CommitUpdateCommand;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java b/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
index 1d324ab7de4..683ed940733 100644
--- a/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
@@ -47,6 +47,7 @@ import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.PluginInfo;
 import org.apache.solr.core.SolrInfoBean;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.junit.Before;
 import org.junit.Test;
 
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java b/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java
index 117f02936c4..d8edadd7209 100644
--- a/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java
@@ -39,6 +39,7 @@ import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.core.SolrXmlConfig;
 import org.apache.solr.embedded.JettySolrRunner;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.apache.solr.util.TestHarness;
 import org.hamcrest.number.OrderingComparison;
 import org.junit.After;
diff --git a/solr/core/src/test/org/apache/solr/search/TestRecovery.java b/solr/core/src/test/org/apache/solr/search/TestRecovery.java
index b4ad7161251..115682cbc27 100644
--- a/solr/core/src/test/org/apache/solr/search/TestRecovery.java
+++ b/solr/core/src/test/org/apache/solr/search/TestRecovery.java
@@ -44,12 +44,12 @@ import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.util.TimeSource;
 import org.apache.solr.common.util.Utils;
 import org.apache.solr.metrics.SolrMetricManager;
-import org.apache.solr.metrics.SolrMetricTestUtils;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.update.UpdateHandler;
 import org.apache.solr.update.UpdateLog;
 import org.apache.solr.update.processor.DistributedUpdateProcessor.DistribPhase;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.apache.solr.util.TestInjection;
 import org.apache.solr.util.TimeOut;
 import org.hamcrest.FeatureMatcher;
diff --git a/solr/core/src/test/org/apache/solr/update/DirectUpdateHandlerTest.java b/solr/core/src/test/org/apache/solr/update/DirectUpdateHandlerTest.java
index 0530ca495c5..ee774416e48 100644
--- a/solr/core/src/test/org/apache/solr/update/DirectUpdateHandlerTest.java
+++ b/solr/core/src/test/org/apache/solr/update/DirectUpdateHandlerTest.java
@@ -35,11 +35,11 @@ import org.apache.solr.common.params.MapSolrParams;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrEventListener;
 import org.apache.solr.index.TieredMergePolicyFactory;
-import org.apache.solr.metrics.SolrMetricTestUtils;
 import org.apache.solr.request.LocalSolrQueryRequest;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.search.SolrIndexSearcher;
 import org.apache.solr.util.LogLevel;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
diff --git a/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java b/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java
index 6b0e96e77af..2a40735ef40 100644
--- a/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java
+++ b/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java
@@ -24,8 +24,8 @@ import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrInfoBean;
-import org.apache.solr.metrics.SolrMetricTestUtils;
 import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.junit.After;
 import org.junit.Test;
 
diff --git a/solr/cross-dc-manager/gradle.lockfile b/solr/cross-dc-manager/gradle.lockfile
index 9f2a5c5cb62..6bf0109d189 100644
--- a/solr/cross-dc-manager/gradle.lockfile
+++ b/solr/cross-dc-manager/gradle.lockfile
@@ -87,8 +87,14 @@ io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,runtimeClasspath,ru
 io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.prometheus:prometheus-metrics-config:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-common:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-httpserver:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.1.0=runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-exposition-formats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-textformats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-model:1.1.0=runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-model:1.3.6=jarValidation,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.swagger.core.v3:swagger-annotations-jakarta:2.2.22=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 jakarta.annotation:jakarta.annotation-api:2.1.1=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/analysis-extras/gradle.lockfile b/solr/modules/analysis-extras/gradle.lockfile
index ddf54fa8334..aea208ebcbf 100644
--- a/solr/modules/analysis-extras/gradle.lockfile
+++ b/solr/modules/analysis-extras/gradle.lockfile
@@ -72,8 +72,14 @@ io.opentelemetry:opentelemetry-sdk-logs:1.53.0=apiHelper,jarValidation,runtimeCl
 io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-trace:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-exposition-formats:1.1.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-model:1.1.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.prometheus:prometheus-metrics-config:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-common:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-httpserver:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.1.0=apiHelper,runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-exposition-formats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-textformats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-model:1.1.0=apiHelper,runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-model:1.3.6=jarValidation,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.swagger.core.v3:swagger-annotations-jakarta:2.2.22=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 jakarta.annotation:jakarta.annotation-api:2.1.1=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/clustering/gradle.lockfile b/solr/modules/clustering/gradle.lockfile
index 1cdd529ed37..8c950bdd034 100644
--- a/solr/modules/clustering/gradle.lockfile
+++ b/solr/modules/clustering/gradle.lockfile
@@ -70,8 +70,14 @@ io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,runtimeClasspath,ru
 io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.prometheus:prometheus-metrics-config:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-common:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-httpserver:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.1.0=runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-exposition-formats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-textformats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-model:1.1.0=runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-model:1.3.6=jarValidation,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.swagger.core.v3:swagger-annotations-jakarta:2.2.22=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 jakarta.annotation:jakarta.annotation-api:2.1.1=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/cross-dc/gradle.lockfile b/solr/modules/cross-dc/gradle.lockfile
index 5388ff79f4f..f54c28c5dfc 100644
--- a/solr/modules/cross-dc/gradle.lockfile
+++ b/solr/modules/cross-dc/gradle.lockfile
@@ -71,8 +71,14 @@ io.opentelemetry:opentelemetry-sdk-logs:1.53.0=apiHelper,jarValidation,runtimeCl
 io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-trace:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-exposition-formats:1.1.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-model:1.1.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.prometheus:prometheus-metrics-config:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-common:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-httpserver:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.1.0=apiHelper,runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-exposition-formats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-textformats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-model:1.1.0=apiHelper,runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-model:1.3.6=jarValidation,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.swagger.core.v3:swagger-annotations-jakarta:2.2.22=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 jakarta.annotation:jakarta.annotation-api:2.1.1=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/extraction/gradle.lockfile b/solr/modules/extraction/gradle.lockfile
index b8f5d733a27..dfc67bbf342 100644
--- a/solr/modules/extraction/gradle.lockfile
+++ b/solr/modules/extraction/gradle.lockfile
@@ -98,8 +98,14 @@ io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,runtimeClasspath,ru
 io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.prometheus:prometheus-metrics-config:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-common:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-httpserver:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.1.0=runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-exposition-formats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-textformats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-model:1.1.0=runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-model:1.3.6=jarValidation,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.swagger.core.v3:swagger-annotations-jakarta:2.2.22=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 jakarta.activation:jakarta.activation-api:1.2.2=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/modules/gcs-repository/gradle.lockfile b/solr/modules/gcs-repository/gradle.lockfile
index e22fe9840ac..544c741d8ae 100644
--- a/solr/modules/gcs-repository/gradle.lockfile
+++ b/solr/modules/gcs-repository/gradle.lockfile
@@ -121,8 +121,14 @@ io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=apiHelper,jarValidation,runtim
 io.opentelemetry:opentelemetry-sdk-trace:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.perfmark:perfmark-api:0.27.0=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-exposition-formats:1.1.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-model:1.1.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.prometheus:prometheus-metrics-config:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-common:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-httpserver:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.1.0=apiHelper,runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-exposition-formats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-textformats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-model:1.1.0=apiHelper,runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-model:1.3.6=jarValidation,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.swagger.core.v3:swagger-annotations-jakarta:2.2.22=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 jakarta.annotation:jakarta.annotation-api:2.1.1=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/jwt-auth/gradle.lockfile b/solr/modules/jwt-auth/gradle.lockfile
index e44398aeeba..88772eaf17e 100644
--- a/solr/modules/jwt-auth/gradle.lockfile
+++ b/solr/modules/jwt-auth/gradle.lockfile
@@ -84,8 +84,14 @@ io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,runtimeClasspath,ru
 io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.prometheus:prometheus-metrics-config:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-common:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-httpserver:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.1.0=runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-exposition-formats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-textformats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-model:1.1.0=runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-model:1.3.6=jarValidation,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.swagger.core.v3:swagger-annotations-jakarta:2.2.22=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 jakarta.annotation:jakarta.annotation-api:2.1.1=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/langid/gradle.lockfile b/solr/modules/langid/gradle.lockfile
index ec261f84302..2b52e6abf02 100644
--- a/solr/modules/langid/gradle.lockfile
+++ b/solr/modules/langid/gradle.lockfile
@@ -71,8 +71,14 @@ io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,runtimeClasspath,ru
 io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.prometheus:prometheus-metrics-config:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-common:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-httpserver:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.1.0=runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-exposition-formats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-textformats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-model:1.1.0=runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-model:1.3.6=jarValidation,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.swagger.core.v3:swagger-annotations-jakarta:2.2.22=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 jakarta.annotation:jakarta.annotation-api:2.1.1=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/llm/gradle.lockfile b/solr/modules/llm/gradle.lockfile
index 5f301fcb1d5..eb0f21cf02d 100644
--- a/solr/modules/llm/gradle.lockfile
+++ b/solr/modules/llm/gradle.lockfile
@@ -85,8 +85,14 @@ io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,runtimeClasspath,ru
 io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.prometheus:prometheus-metrics-config:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-common:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-httpserver:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.1.0=runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-exposition-formats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-textformats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-model:1.1.0=runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-model:1.3.6=jarValidation,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.swagger.core.v3:swagger-annotations-jakarta:2.2.22=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 jakarta.annotation:jakarta.annotation-api:2.1.1=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/ltr/gradle.lockfile b/solr/modules/ltr/gradle.lockfile
index 1c05c0fdc00..61a53133e8a 100644
--- a/solr/modules/ltr/gradle.lockfile
+++ b/solr/modules/ltr/gradle.lockfile
@@ -70,8 +70,14 @@ io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,runtimeClasspath,ru
 io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.prometheus:prometheus-metrics-config:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-common:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-httpserver:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.1.0=runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-exposition-formats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-textformats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-model:1.1.0=runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-model:1.3.6=jarValidation,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.swagger.core.v3:swagger-annotations-jakarta:2.2.22=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 jakarta.annotation:jakarta.annotation-api:2.1.1=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/opentelemetry/gradle.lockfile b/solr/modules/opentelemetry/gradle.lockfile
index 182700eea53..9e62cfe1990 100644
--- a/solr/modules/opentelemetry/gradle.lockfile
+++ b/solr/modules/opentelemetry/gradle.lockfile
@@ -101,8 +101,14 @@ io.opentelemetry:opentelemetry-sdk-testing:1.53.0=jarValidation,testCompileClass
 io.opentelemetry:opentelemetry-sdk-trace:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 io.perfmark:perfmark-api:0.27.0=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.prometheus:prometheus-metrics-config:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-common:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-httpserver:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.1.0=runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-exposition-formats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-textformats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-model:1.1.0=runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-model:1.3.6=jarValidation,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.swagger.core.v3:swagger-annotations-jakarta:2.2.22=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 jakarta.annotation:jakarta.annotation-api:2.1.1=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/s3-repository/gradle.lockfile b/solr/modules/s3-repository/gradle.lockfile
index 2f0dbdb4b09..7774a5b3a4f 100644
--- a/solr/modules/s3-repository/gradle.lockfile
+++ b/solr/modules/s3-repository/gradle.lockfile
@@ -87,8 +87,14 @@ io.opentelemetry:opentelemetry-sdk-logs:1.53.0=apiHelper,jarValidation,runtimeCl
 io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-trace:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk:1.53.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-exposition-formats:1.1.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-model:1.1.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.prometheus:prometheus-metrics-config:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-common:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-httpserver:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.1.0=apiHelper,runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-exposition-formats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-textformats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-model:1.1.0=apiHelper,runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-model:1.3.6=jarValidation,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.swagger.core.v3:swagger-annotations-jakarta:2.2.22=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 jakarta.activation:jakarta.activation-api:2.1.3=jarValidation,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/modules/scripting/gradle.lockfile b/solr/modules/scripting/gradle.lockfile
index 5ad8e1ed9af..8db04cfb975 100644
--- a/solr/modules/scripting/gradle.lockfile
+++ b/solr/modules/scripting/gradle.lockfile
@@ -70,8 +70,14 @@ io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,runtimeClasspath,ru
 io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.prometheus:prometheus-metrics-config:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-common:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-httpserver:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.1.0=runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-exposition-formats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-textformats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-model:1.1.0=runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-model:1.3.6=jarValidation,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.swagger.core.v3:swagger-annotations-jakarta:2.2.22=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 jakarta.annotation:jakarta.annotation-api:2.1.1=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/sql/gradle.lockfile b/solr/modules/sql/gradle.lockfile
index f7d2ba572f6..a4c455939e2 100644
--- a/solr/modules/sql/gradle.lockfile
+++ b/solr/modules/sql/gradle.lockfile
@@ -73,8 +73,14 @@ io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,runtimeClasspath,ru
 io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
+io.prometheus:prometheus-metrics-config:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-common:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-httpserver:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.1.0=runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-exposition-formats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-textformats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-model:1.1.0=runtimeClasspath,runtimeLibs,solrPlatformLibs
+io.prometheus:prometheus-metrics-model:1.3.6=jarValidation,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 io.swagger.core.v3:swagger-annotations-jakarta:2.2.22=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 jakarta.annotation:jakarta.annotation-api:2.1.1=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/prometheus-exporter/gradle.lockfile b/solr/prometheus-exporter/gradle.lockfile
index 1549a84a17a..3f224e849dc 100644
--- a/solr/prometheus-exporter/gradle.lockfile
+++ b/solr/prometheus-exporter/gradle.lockfile
@@ -70,8 +70,14 @@ io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,solrPlatformLibs,te
 io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,solrPlatformLibs,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,solrPlatformLibs,testRuntimeClasspath
-io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,solrPlatformLibs,testRuntimeClasspath
+io.prometheus:prometheus-metrics-config:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-common:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-httpserver:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.1.0=solrPlatformLibs
+io.prometheus:prometheus-metrics-exposition-formats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-textformats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-model:1.1.0=solrPlatformLibs
+io.prometheus:prometheus-metrics-model:1.3.6=jarValidation,testRuntimeClasspath
 io.prometheus:simpleclient:0.16.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 io.prometheus:simpleclient_common:0.16.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 io.prometheus:simpleclient_httpserver:0.16.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/solr-ref-guide/gradle.lockfile b/solr/solr-ref-guide/gradle.lockfile
index ea526d855d7..51e81c33171 100644
--- a/solr/solr-ref-guide/gradle.lockfile
+++ b/solr/solr-ref-guide/gradle.lockfile
@@ -69,8 +69,12 @@ io.opentelemetry:opentelemetry-sdk-logs:1.53.0=testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-trace:1.53.0=testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk:1.53.0=testRuntimeClasspath
-io.prometheus:prometheus-metrics-exposition-formats:1.1.0=testRuntimeClasspath
-io.prometheus:prometheus-metrics-model:1.1.0=testRuntimeClasspath
+io.prometheus:prometheus-metrics-config:1.3.6=testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-common:1.3.6=testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-httpserver:1.3.6=testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.3.6=testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-textformats:1.3.6=testRuntimeClasspath
+io.prometheus:prometheus-metrics-model:1.3.6=testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=testRuntimeClasspath
 io.swagger.core.v3:swagger-annotations-jakarta:2.2.22=testCompileClasspath,testRuntimeClasspath
 jakarta.annotation:jakarta.annotation-api:2.1.1=testRuntimeClasspath
diff --git a/solr/solrj-streaming/gradle.lockfile b/solr/solrj-streaming/gradle.lockfile
index 51cd7f859cf..11b6f28057c 100644
--- a/solr/solrj-streaming/gradle.lockfile
+++ b/solr/solrj-streaming/gradle.lockfile
@@ -79,8 +79,12 @@ io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,testRuntimeClasspat
 io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,testRuntimeClasspath
-io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,testRuntimeClasspath
-io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-config:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-common:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-httpserver:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-textformats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-model:1.3.6=jarValidation,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,testRuntimeClasspath
 io.swagger.core.v3:swagger-annotations-jakarta:2.2.22=compileClasspath,jarValidation,permitTestUsedUndeclared,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 jakarta.annotation:jakarta.annotation-api:2.1.1=jarValidation,testRuntimeClasspath
diff --git a/solr/solrj-zookeeper/gradle.lockfile b/solr/solrj-zookeeper/gradle.lockfile
index e9d944d9340..c5b981bba2e 100644
--- a/solr/solrj-zookeeper/gradle.lockfile
+++ b/solr/solrj-zookeeper/gradle.lockfile
@@ -84,8 +84,12 @@ io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,testRuntimeClasspat
 io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,testRuntimeClasspath
-io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,testRuntimeClasspath
-io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-config:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-common:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-httpserver:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-textformats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-model:1.3.6=jarValidation,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,testRuntimeClasspath
 io.swagger.core.v3:swagger-annotations-jakarta:2.2.22=compileClasspath,jarValidation,permitTestUsedUndeclared,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 jakarta.annotation:jakarta.annotation-api:2.1.1=jarValidation,testRuntimeClasspath
diff --git a/solr/solrj/gradle.lockfile b/solr/solrj/gradle.lockfile
index 56efd7d9a9c..942b6868569 100644
--- a/solr/solrj/gradle.lockfile
+++ b/solr/solrj/gradle.lockfile
@@ -79,8 +79,12 @@ io.opentelemetry:opentelemetry-sdk-logs:1.53.0=jarValidation,testRuntimeClasspat
 io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=jarValidation,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-trace:1.53.0=jarValidation,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk:1.53.0=jarValidation,testRuntimeClasspath
-io.prometheus:prometheus-metrics-exposition-formats:1.1.0=jarValidation,testRuntimeClasspath
-io.prometheus:prometheus-metrics-model:1.1.0=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-config:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-common:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-httpserver:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-textformats:1.3.6=jarValidation,testRuntimeClasspath
+io.prometheus:prometheus-metrics-model:1.3.6=jarValidation,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=jarValidation,testRuntimeClasspath
 io.swagger.core.v3:swagger-annotations-jakarta:2.2.22=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 jakarta.annotation:jakarta.annotation-api:2.1.1=jarValidation,testRuntimeClasspath
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientTest.java
index aa4c8ae5b61..9fb3bc4a9b0 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientTest.java
@@ -39,7 +39,6 @@ import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrJMetricTestUtils;
 import org.apache.solr.client.solrj.SolrQuery;
-import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrRequest.METHOD;
 import org.apache.solr.client.solrj.SolrRequest.SolrRequestType;
 import org.apache.solr.client.solrj.SolrServerException;
diff --git a/solr/test-framework/build.gradle b/solr/test-framework/build.gradle
index c7b5002cd1d..d5ed939e79c 100644
--- a/solr/test-framework/build.gradle
+++ b/solr/test-framework/build.gradle
@@ -72,6 +72,8 @@ dependencies {
   implementation libs.apache.httpcomponents.httpclient
   implementation libs.apache.httpcomponents.httpcore
   implementation libs.opentelemetry.api
+  implementation libs.opentelemetry.exporter.prometheus
+  implementation libs.prometheus.metrics.model
 
   implementation libs.jakarta.servlet.api
   implementation libs.eclipse.jetty.server
diff --git a/solr/test-framework/gradle.lockfile b/solr/test-framework/gradle.lockfile
index 1c87cc8e6db..9fbdc8045c3 100644
--- a/solr/test-framework/gradle.lockfile
+++ b/solr/test-framework/gradle.lockfile
@@ -64,15 +64,23 @@ io.opentelemetry:opentelemetry-api:1.53.0=apiHelper,compileClasspath,jarValidati
 io.opentelemetry:opentelemetry-common:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-context:1.53.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-exporter-common:1.50.0=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
-io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.50.0=compileClasspath,testCompileClasspath
 io.opentelemetry:opentelemetry-sdk-common:1.53.0=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.50.0=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-logs:1.53.0=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.50.0=compileClasspath,testCompileClasspath
 io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk-trace:1.53.0=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
 io.opentelemetry:opentelemetry-sdk:1.53.0=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
-io.prometheus:prometheus-metrics-exposition-formats:1.1.0=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
-io.prometheus:prometheus-metrics-model:1.1.0=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
+io.prometheus:prometheus-metrics-config:1.3.6=jarValidation,runtimeClasspath,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-common:1.3.6=jarValidation,runtimeClasspath,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exporter-httpserver:1.3.6=jarValidation,runtimeClasspath,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.1.0=apiHelper
+io.prometheus:prometheus-metrics-exposition-formats:1.3.6=jarValidation,runtimeClasspath,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-textformats:1.3.6=jarValidation,runtimeClasspath,testRuntimeClasspath
+io.prometheus:prometheus-metrics-model:1.1.0=apiHelper,compileClasspath,testCompileClasspath
+io.prometheus:prometheus-metrics-model:1.3.6=jarValidation,runtimeClasspath,testRuntimeClasspath
 io.sgr:s2-geometry-library-java:1.0.0=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
 io.swagger.core.v3:swagger-annotations-jakarta:2.2.22=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 jakarta.annotation:jakarta.annotation-api:2.1.1=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java b/solr/test-framework/src/java/org/apache/solr/util/SolrMetricTestUtils.java
similarity index 98%
rename from solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java
rename to solr/test-framework/src/java/org/apache/solr/util/SolrMetricTestUtils.java
index 425aa45468e..3de85bf3018 100644
--- a/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java
+++ b/solr/test-framework/src/java/org/apache/solr/util/SolrMetricTestUtils.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.solr.metrics;
+package org.apache.solr.util;
 
 import com.codahale.metrics.Counter;
 import io.opentelemetry.api.common.AttributeKey;
@@ -35,6 +35,8 @@ import org.apache.solr.common.util.Utils;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrInfoBean;
+import org.apache.solr.metrics.SolrMetricProducer;
+import org.apache.solr.metrics.SolrMetricsContext;
 
 public final class SolrMetricTestUtils {
 


(solr) 29/33: SOLR-17828: Remove Prometheus exporter (#3470)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit e02bd5367a9c59dbf1ed81eabee7cf2b8df7cf74
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Thu Sep 25 13:35:57 2025 -0400

    SOLR-17828: Remove Prometheus exporter (#3470)
    
    * Remove prometheus exporter from project
    
    * Add to major Solr 10 changes page
    
    * Delete licenses
    
    * Merge solr 10 changes bullet point
    
    * Add OTEL collector link
---
 .github/labeler.yml                                |    6 -
 .github/workflows/bin-solr-test.yml                |    1 -
 .github/workflows/docker-test.yml                  |    1 -
 dev-tools/scripts/smokeTestRelease.py              |    4 +-
 gradle/maven/defaults-maven.gradle                 |    1 -
 gradle/solr/packaging.gradle                       |    4 +-
 settings.gradle                                    |    1 -
 solr/README.adoc                                   |    3 -
 solr/docker/build.gradle                           |    6 -
 solr/docker/templates/Dockerfile.body.template     |    5 +-
 solr/licenses/jackson-jq-0.0.13.jar.sha1           |    1 -
 solr/licenses/simpleclient-0.16.0.jar.sha1         |    1 -
 solr/licenses/simpleclient_common-0.16.0.jar.sha1  |    1 -
 .../simpleclient_httpserver-0.16.0.jar.sha1        |    1 -
 solr/packaging/README.txt                          |    4 -
 solr/packaging/build.gradle                        |   12 -
 solr/packaging/gradle.lockfile                     |    2 +-
 solr/packaging/test/bats_helper.bash               |    2 +-
 solr/prometheus-exporter/README.md                 |   40 -
 solr/prometheus-exporter/bin/solr-exporter         |  152 -
 solr/prometheus-exporter/bin/solr-exporter.cmd     |  119 -
 .../conf/grafana-solr-dashboard.json               | 7864 --------------------
 solr/prometheus-exporter/conf/log4j2.xml           |   40 -
 .../conf/solr-exporter-config.xml                  | 1134 ---
 .../prometheus/collector/CollectionsCollector.java |   37 -
 .../solr/prometheus/collector/MetricCollector.java |   22 -
 .../solr/prometheus/collector/MetricSamples.java   |   86 -
 .../prometheus/collector/MetricsCollector.java     |   41 -
 .../collector/MetricsCollectorFactory.java         |   71 -
 .../solr/prometheus/collector/PingCollector.java   |   47 -
 .../collector/SchedulerMetricsCollector.java       |  125 -
 .../solr/prometheus/collector/SearchCollector.java |   37 -
 .../solr/prometheus/collector/package-info.java    |   19 -
 .../exporter/CachedPrometheusCollector.java        |   38 -
 .../prometheus/exporter/MetricsConfiguration.java  |  185 -
 .../prometheus/exporter/MetricsQueryTemplate.java  |  129 -
 .../exporter/PrometheusExporterSettings.java       |   90 -
 .../prometheus/exporter/SolrClientFactory.java     |   87 -
 .../solr/prometheus/exporter/SolrExporter.java     |  391 -
 .../exporter/SolrScrapeConfiguration.java          |  125 -
 .../solr/prometheus/exporter/package-info.java     |   19 -
 .../solr/prometheus/scraper/SolrCloudScraper.java  |  152 -
 .../prometheus/scraper/SolrStandaloneScraper.java  |  109 -
 .../solr/prometheus/scraper/package-info.java      |   19 -
 solr/prometheus-exporter/src/java/overview.html    |   26 -
 ...theus-solr-exporter-integration-test-config.xml | 1741 -----
 ...rometheus-solr-exporter-scraper-test-config.xml |  173 -
 .../test-files/conf/test-config-with-templates.xml |  222 -
 .../src/test-files/exampledocs/gb18030-example.xml |   32 -
 .../src/test-files/exampledocs/hd.xml              |   56 -
 .../src/test-files/exampledocs/ipod_other.xml      |   60 -
 .../src/test-files/exampledocs/ipod_video.xml      |   40 -
 .../src/test-files/exampledocs/manufacturers.xml   |   75 -
 .../src/test-files/exampledocs/mem.xml             |   77 -
 .../src/test-files/exampledocs/money.xml           |   65 -
 .../src/test-files/exampledocs/monitor.xml         |   34 -
 .../src/test-files/exampledocs/monitor2.xml        |   33 -
 .../src/test-files/exampledocs/mp500.xml           |   43 -
 .../src/test-files/exampledocs/sample.html         |   13 -
 .../src/test-files/exampledocs/sd500.xml           |   38 -
 .../src/test-files/exampledocs/solr.xml            |   38 -
 .../src/test-files/exampledocs/utf8-example.xml    |   41 -
 .../src/test-files/exampledocs/vidcard.xml         |   62 -
 solr/prometheus-exporter/src/test-files/log4j2.xml |   40 -
 .../src/test-files/query-metrics.json              |   91 -
 .../solr/collection1/conf/managed-schema.xml       |  413 -
 .../solr/collection1/conf/solrconfig.xml           |  177 -
 .../test-files/solr/collection1/conf/stopwords.txt |   14 -
 .../test-files/solr/collection1/conf/synonyms.txt  |   29 -
 .../prometheus/PrometheusExporterTestBase.java     |   74 -
 .../prometheus/collector/MetricSamplesTest.java    |  133 -
 .../exporter/MetricsQueryTemplateTest.java         |  134 -
 .../solr/prometheus/exporter/SolrExporterTest.java |   29 -
 .../prometheus/exporter/SolrExporterTestBase.java  |  140 -
 .../org/apache/solr/prometheus/utils/Helpers.java  |   69 -
 .../deployment-guide/pages/installing-solr.adoc    |    9 +-
 .../pages/major-changes-in-solr-10.adoc            |    2 +-
 77 files changed, 11 insertions(+), 15446 deletions(-)

diff --git a/.github/labeler.yml b/.github/labeler.yml
index beb19670e48..ed1221dd7cc 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -98,12 +98,6 @@ admin-ui:
           - solr/webapp/**
           - solr/ui/**
 
-# Add 'prometheus-exporter' label
-prometheus-exporter:
-  - changed-files:
-      - any-glob-to-any-file:
-          - solr/prometheus-exporter/**
-
 # Add labels for changes to solr modules
 module:analysis-extras:
   - changed-files:
diff --git a/.github/workflows/bin-solr-test.yml b/.github/workflows/bin-solr-test.yml
index 7684da03375..8bb4e6fea47 100644
--- a/.github/workflows/bin-solr-test.yml
+++ b/.github/workflows/bin-solr-test.yml
@@ -9,7 +9,6 @@ on:
       - 'solr/bin/**'
       - 'solr/packaging/**'
       - 'solr/core/src/java/org/apache/solr/cli/**'
-      - 'solr/prometheus-exporter/**'
 
 jobs:
   test:
diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml
index c36cda412d4..47856e7e305 100644
--- a/.github/workflows/docker-test.yml
+++ b/.github/workflows/docker-test.yml
@@ -7,7 +7,6 @@ on:
     paths:
       - '.github/workflows/docker-test.yml'
       - 'solr/bin/**'
-      - 'solr/prometheus-exporter/bin/**'
       - 'solr/docker/**'
       - 'solr/packaging/**'
 
diff --git a/dev-tools/scripts/smokeTestRelease.py b/dev-tools/scripts/smokeTestRelease.py
index 38fa01f872c..9ff1e24774c 100755
--- a/dev-tools/scripts/smokeTestRelease.py
+++ b/dev-tools/scripts/smokeTestRelease.py
@@ -636,7 +636,7 @@ def verifyUnpacked(java, artifact, unpackPath, gitRevision, version, testArgs):
     expected_src_root_folders = ['build-tools', 'dev-docs', 'dev-tools', 'gradle', 'help', 'solr']
     expected_src_root_files = ['build.gradle', 'gradlew', 'gradlew.bat', 'settings.gradle', 'settings-gradle.lockfile', 'versions.lock']
     expected_src_solr_files = ['build.gradle']
-    expected_src_solr_folders = ['benchmark',  'bin', 'modules', 'api', 'core', 'cross-dc-manager', 'docker', 'documentation', 'example', 'licenses', 'packaging', 'distribution', 'prometheus-exporter', 'server', 'solr-ref-guide', 'solrj', 'solrj-streaming', 'solrj-zookeeper', 'test-framework', 'webapp', '.gitignore', '.gitattributes']
+    expected_src_solr_folders = ['benchmark',  'bin', 'modules', 'api', 'core', 'cross-dc-manager', 'docker', 'documentation', 'example', 'licenses', 'packaging', 'distribution', 'server', 'solr-ref-guide', 'solrj', 'solrj-streaming', 'solrj-zookeeper', 'test-framework', 'webapp', '.gitignore', '.gitattributes']
     is_in_list(in_root_folder, expected_src_root_folders)
     is_in_list(in_root_folder, expected_src_root_files)
     is_in_list(in_solr_folder, expected_src_solr_folders)
@@ -646,7 +646,7 @@ def verifyUnpacked(java, artifact, unpackPath, gitRevision, version, testArgs):
   elif isSlim:
     is_in_list(in_root_folder, ['bin', 'docker', 'docs', 'example', 'licenses', 'server', 'lib'])
   else:
-    is_in_list(in_root_folder, ['bin', 'modules', 'cross-dc-manager', 'docker', 'prometheus-exporter', 'docs', 'example', 'licenses', 'server', 'lib'])
+    is_in_list(in_root_folder, ['bin', 'modules', 'cross-dc-manager', 'docker', 'docs', 'example', 'licenses', 'server', 'lib'])
 
   if len(in_root_folder) > 0:
     raise RuntimeError('solr: unexpected files/dirs in artifact %s: %s' % (artifact, in_root_folder))
diff --git a/gradle/maven/defaults-maven.gradle b/gradle/maven/defaults-maven.gradle
index beba30c6a9f..7e4954a0498 100644
--- a/gradle/maven/defaults-maven.gradle
+++ b/gradle/maven/defaults-maven.gradle
@@ -30,7 +30,6 @@ configure(rootProject) {
         ":solr:solrj",
         ":solr:solrj-streaming",
         ":solr:solrj-zookeeper",
-        ":solr:prometheus-exporter",
         ":solr:test-framework",
     ] + project(":solr:modules").getChildProjects().values().path
 
diff --git a/gradle/solr/packaging.gradle b/gradle/solr/packaging.gradle
index 1b5325f908b..d8988555dad 100644
--- a/gradle/solr/packaging.gradle
+++ b/gradle/solr/packaging.gradle
@@ -36,11 +36,11 @@
 // I don't know how to untie these two cleanly.
 //
 
-configure(allprojects.findAll {project -> project.path.startsWith(":solr:modules:") || project.path == ":solr:prometheus-exporter" || project.path == ":solr:cross-dc-manager" }) {
+configure(allprojects.findAll {project -> project.path.startsWith(":solr:modules:") || project.path == ":solr:cross-dc-manager" }) {
   plugins.withType(JavaPlugin) {
     project.ext {
       packagingDir = file("${buildDir}/packaging")
-      if (project.path.startsWith(":solr:prometheus-exporter") || project.path.startsWith(":solr:cross-dc-manager")) {
+      if (project.path.startsWith(":solr:cross-dc-manager")) {
         deps = packagingDir
       } else {
         deps = file("${packagingDir}/${project.name}")
diff --git a/settings.gradle b/settings.gradle
index 4a16234cc52..2dd9363de8b 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -65,7 +65,6 @@ include "solr:documentation"
 include "solr:packaging"
 include "solr:distribution"
 include "solr:docker"
-include "solr:prometheus-exporter"
 
 def disableUiModuleValue = providers.gradleProperty('disableUiModule').orNull
 gradle.ext.withUiModule = disableUiModuleValue == null || disableUiModuleValue != 'true'
diff --git a/solr/README.adoc b/solr/README.adoc
index 8ddabdffa92..6b77d17288c 100644
--- a/solr/README.adoc
+++ b/solr/README.adoc
@@ -55,9 +55,6 @@ Contains modules to extend the functionality of Solr.
 link:packaging/[]::
 Gradle project to build the binary release of Solr.
 
-link:prometheus-exporter/[]::
-Contains a separate application to monitor Solr instances and export Prometheus metrics.
-
 link:server/[]::
 Gradle project that builds the Jetty server to run Solr.
 
diff --git a/solr/docker/build.gradle b/solr/docker/build.gradle
index bda15db92f1..9d0f8b02aa3 100644
--- a/solr/docker/build.gradle
+++ b/solr/docker/build.gradle
@@ -224,11 +224,6 @@ task testDocker(type: TestDockerImageTask, dependsOn: tasks.dockerBuild) {
   // include/exclude options are designed for people who know their customizations will break some tests
   testCasesInclude.value(Arrays.asList(propertyOrEnvOrDefault("solr.docker.tests.include", "SOLR_DOCKER_TESTS_INCLUDE", ",").split(",")))
   var excludeCases = new HashSet(Arrays.asList(propertyOrEnvOrDefault("solr.docker.tests.exclude", "SOLR_DOCKER_TESTS_EXCLUDE", ",").split(",")))
-  if (isImageSlim()) {
-    // The slim image does not contain the prometheus-exporter.
-    excludeCases.add("prometheus-exporter-standalone")
-    excludeCases.add("prometheus-exporter-cloud")
-  }
   testCasesExclude.value(excludeCases)
 }
 
@@ -549,7 +544,6 @@ task testDockerfileOfficialSlim(type: TestDockerImageTask, dependsOn: configurat
   // This test does not respect the include/exclude properties that `testDocker` does.
   // All docker tests will be run, no matter the properties specified.
   testCasesInclude.empty()
-  testCasesExclude = ['prometheus-exporter-cloud', 'prometheus-exporter-standalone']
 }
 
 task testDockerfileOfficial {
diff --git a/solr/docker/templates/Dockerfile.body.template b/solr/docker/templates/Dockerfile.body.template
index 4d9d537419a..ce7c292ed56 100644
--- a/solr/docker/templates/Dockerfile.body.template
+++ b/solr/docker/templates/Dockerfile.body.template
@@ -39,7 +39,7 @@ ENV SOLR_USER="solr" \
     SOLR_UID="8983" \
     SOLR_GROUP="solr" \
     SOLR_GID="8983" \
-    PATH="/opt/solr/bin:/opt/solr/docker/scripts:/opt/solr/prometheus-exporter/bin:/opt/solr/cross-dc-manager/bin:$PATH" \
+    PATH="/opt/solr/cross-dc-manager/bin:$PATH" \
     SOLR_INCLUDE=/etc/default/solr.in.sh \
     SOLR_HOME=/var/solr/data \
     SOLR_PID_DIR=/var/solr \
@@ -66,8 +66,7 @@ RUN set -ex; \
   chmod 0664 /etc/default/solr.in.sh; \
   mkdir -p -m0770 /var/solr; \
   chown -R "$SOLR_USER:0" /var/solr; \
-  test ! -e /opt/solr/modules || ln -s /opt/solr/modules /opt/solr/contrib; \
-  test ! -e /opt/solr/prometheus-exporter || ln -s /opt/solr/prometheus-exporter /opt/solr/modules/prometheus-exporter;
+  test ! -e /opt/solr/modules || ln -s /opt/solr/modules /opt/solr/contrib;
 
 RUN set -ex; \
     apt-get update; \
diff --git a/solr/licenses/jackson-jq-0.0.13.jar.sha1 b/solr/licenses/jackson-jq-0.0.13.jar.sha1
deleted file mode 100644
index b20dc335fe4..00000000000
--- a/solr/licenses/jackson-jq-0.0.13.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-66479e34ce27a86a924bb5f2b1556db3e117f591
diff --git a/solr/licenses/simpleclient-0.16.0.jar.sha1 b/solr/licenses/simpleclient-0.16.0.jar.sha1
deleted file mode 100644
index 837c12a3bef..00000000000
--- a/solr/licenses/simpleclient-0.16.0.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-28b0eaf7c500c506976da8d0fc9cad6c278e8d87
diff --git a/solr/licenses/simpleclient_common-0.16.0.jar.sha1 b/solr/licenses/simpleclient_common-0.16.0.jar.sha1
deleted file mode 100644
index 4b9f4251f45..00000000000
--- a/solr/licenses/simpleclient_common-0.16.0.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-a09a8c790a20309b942a9fdbfe77da22407096e6
diff --git a/solr/licenses/simpleclient_httpserver-0.16.0.jar.sha1 b/solr/licenses/simpleclient_httpserver-0.16.0.jar.sha1
deleted file mode 100644
index c4a863ea39b..00000000000
--- a/solr/licenses/simpleclient_httpserver-0.16.0.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-2c23f53ca22d7d8885fc4522ddcadcfe7f01a783
diff --git a/solr/packaging/README.txt b/solr/packaging/README.txt
index 83cf0c3fef0..8c5a69cbd5e 100644
--- a/solr/packaging/README.txt
+++ b/solr/packaging/README.txt
@@ -76,10 +76,6 @@ modules/
   Contains modules to extend the functionality of Solr.
   Libraries for these modules can be found under modules/*/lib
 
-prometheus-exporter/
-  (Not included in the "slim" release)
-  Contains a separate application to monitor Solr instances and export Prometheus metrics
-
 docker/
   Contains a Dockerfile to build a Docker image using the source or binary distribution.
   `docker/scripts` contains scripts that the Docker image uses to manage Solr.
diff --git a/solr/packaging/build.gradle b/solr/packaging/build.gradle
index 913aab3a6c0..21f765322b6 100644
--- a/solr/packaging/build.gradle
+++ b/solr/packaging/build.gradle
@@ -37,7 +37,6 @@ ext {
 configurations {
   modules
   example
-  prometheusExporter
   crossDcManager
   server
   docs
@@ -53,8 +52,6 @@ dependencies {
     modules project(path: module, configuration: "packaging")
   }
 
-  prometheusExporter project(path: ":solr:prometheus-exporter", configuration: "packaging")
-
   crossDcManager project(path: ":solr:cross-dc-manager", configuration: "packaging")
 
   example project(path: ":solr:example", configuration: "packaging")
@@ -142,15 +139,6 @@ distributions {
         into "modules"
       })
 
-      from(configurations.prometheusExporter, {
-        into "prometheus-exporter"
-        filesMatching([
-                "bin/*",
-        ]) { copy ->
-          copy.setMode(0755)
-        }
-      })
-
       from(configurations.crossDcManager, {
         into "cross-dc-manager"
         filesMatching([
diff --git a/solr/packaging/gradle.lockfile b/solr/packaging/gradle.lockfile
index 8dca76ebfe3..a947eb7ac54 100644
--- a/solr/packaging/gradle.lockfile
+++ b/solr/packaging/gradle.lockfile
@@ -1,4 +1,4 @@
 # This is a Gradle generated file for dependency locking.
 # Manual edits can break the build and are not advised.
 # This file is expected to be part of source control.
-empty=crossDcManager,docker,docs,example,jarValidation,modules,prometheusExporter,server,solrFullTgz,solrFullTgzSignature,solrSlimTgz,solrSlimTgzSignature
+empty=crossDcManager,docker,docs,example,jarValidation,modules,server,solrFullTgz,solrFullTgzSignature,solrSlimTgz,solrSlimTgzSignature
diff --git a/solr/packaging/test/bats_helper.bash b/solr/packaging/test/bats_helper.bash
index ac7c8a6fdf3..23c3629bea6 100644
--- a/solr/packaging/test/bats_helper.bash
+++ b/solr/packaging/test/bats_helper.bash
@@ -33,7 +33,7 @@ common_setup() {
     load "${BATS_LIB_PREFIX}/bats-assert/load.bash"
     load "${BATS_LIB_PREFIX}/bats-file/load.bash"
 
-    PATH="${SOLR_TIP:-.}/bin:${SOLR_TIP:-.}/prometheus-exporter/bin:$PATH"
+    PATH="${SOLR_TIP:-.}/bin:$PATH"
     export SOLR_ULIMIT_CHECKS=false
 }
 
diff --git a/solr/prometheus-exporter/README.md b/solr/prometheus-exporter/README.md
deleted file mode 100644
index c1c9b2f3f2d..00000000000
--- a/solr/prometheus-exporter/README.md
+++ /dev/null
@@ -1,40 +0,0 @@
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one or more
-  contributor license agreements.  See the NOTICE file distributed with
-  this work for additional information regarding copyright ownership.
-  The ASF licenses this file to You under the Apache License, Version 2.0
-  (the "License"); you may not use this file except in compliance with
-  the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
--->
-
-Welcome to Apache Solr Prometheus Exporter
-========
-
-Apache Solr Prometheus Exporter (solr-exporter) provides a way for you to expose metrics for Solr to Prometheus.
-
-# Getting Started With Solr Prometheus Exporter
-
-For information on how to get started with solr-exporter please see:
- * [Solr Reference Guide's section on Monitoring Solr with Prometheus and Grafana](https://solr.apache.org/guide/solr/latest/deployment-guide/monitoring-with-prometheus-and-grafana.html)
-
-# Docker
-
-The Solr Prometheus Exporter can be run via the official or local Solr docker image.
-Please refer to the `docker` directory's `README.md` for information on building the image
-and the [Solr Reference Guide](https://solr.apache.org/guide/solr/latest/deployment-guide/solr-in-docker.html) for information on using the image.
-
-The `solr-exporter` script is available on the path by default in the Docker image, so the Prometheus Exporter can be run using:
-
-```bash
-docker run <image> solr-exporter
-```
-
-The environment variables and command line arguments that the Prometheus Exporter accepts can be used the same way in the Docker image.
diff --git a/solr/prometheus-exporter/bin/solr-exporter b/solr/prometheus-exporter/bin/solr-exporter
deleted file mode 100755
index 20377c4b171..00000000000
--- a/solr/prometheus-exporter/bin/solr-exporter
+++ /dev/null
@@ -1,152 +0,0 @@
-#!/usr/bin/env bash
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-BASEDIR=`dirname $0`/..
-BASEDIR=`(cd "$BASEDIR"; pwd)`
-
-# OS specific support.  $var _must_ be set to either true or false.
-cygwin=false;
-darwin=false;
-case "`uname`" in
-  CYGWIN*) cygwin=true ;;
-  Darwin*) darwin=true
-           if [ -z "$JAVA_VERSION" ] ; then
-             JAVA_VERSION="CurrentJDK"
-           else
-             echo "Using Java version: $JAVA_VERSION"
-           fi
-           if [ -z "$JAVA_HOME" ] ; then
-             JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/${JAVA_VERSION}/Home
-           fi
-           ;;
-esac
-
-if [ -z "$JAVA_HOME" ] ; then
-  if [ -r /etc/gentoo-release ] ; then
-    JAVA_HOME=`java-config --jre-home`
-  fi
-fi
-
-# For Cygwin, ensure paths are in UNIX format before anything is touched
-if $cygwin ; then
-  [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
-  [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
-fi
-
-# If a specific java binary isn't specified search for the standard 'java' binary
-if [ -z "$JAVACMD" ] ; then
-  if [ -n "$JAVA_HOME"  ] ; then
-    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
-      # IBM's JDK on AIX uses strange locations for the executables
-      JAVACMD="$JAVA_HOME/jre/sh/java"
-    else
-      JAVACMD="$JAVA_HOME/bin/java"
-    fi
-  else
-    JAVACMD=`which java`
-  fi
-fi
-
-if [ ! -x "$JAVACMD" ] ; then
-  echo "Error: JAVA_HOME is not defined correctly."
-  echo "  We cannot execute $JAVACMD"
-  exit 1
-fi
-
-if [ -z "$REPO" ]
-then
-  REPO="$BASEDIR"/lib
-fi
-
-CLASSPATH=$CLASSPATH_PREFIX:"$BASEDIR/conf":"$REPO/*":"$BASEDIR/../server/solr-webapp/webapp/WEB-INF/lib/*":"$BASEDIR/../server/lib/ext/*"
-
-# Memory settings
-JAVA_MEM_OPTS=
-if [ -z "$JAVA_HEAP" ] && [ -n "$JAVA_MEM" ]; then
-  JAVA_MEM_OPTS="$JAVA_MEM"
-else
-  JAVA_HEAP="${JAVA_HEAP:-512m}"
-  JAVA_MEM_OPTS="-Xms$JAVA_HEAP -Xmx$JAVA_HEAP"
-fi
-
-# define default GC_TUNE
-if [ -z ${GC_TUNE+x} ]; then
-    GC_TUNE='-XX:+UseG1GC'
-else
-    GC_TUNE="$GC_TUNE"
-fi
-
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin; then
-  [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
-  [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
-  [ -n "$HOME" ] && HOME=`cygpath --path --windows "$HOME"`
-  [ -n "$BASEDIR" ] && BASEDIR=`cygpath --path --windows "$BASEDIR"`
-  [ -n "$REPO" ] && REPO=`cygpath --path --windows "$REPO"`
-fi
-
-# Convert Environment Variables to Command Line Options
-EXPORTER_ARGS=()
-
-if [[ -n "$CONFIG_FILE" ]]; then
-  EXPORTER_ARGS+=(--config-file "$CONFIG_FILE")
-fi
-
-if [[ -n "$PORT" ]]; then
-  EXPORTER_ARGS+=(-p "$PORT")
-fi
-
-if [[ -n "$SCRAPE_INTERVAL" ]]; then
-  EXPORTER_ARGS+=(--scrape-interval "$SCRAPE_INTERVAL")
-fi
-
-if [[ -n "$NUM_THREADS" ]]; then
-  EXPORTER_ARGS+=(--num-threads "$NUM_THREADS")
-fi
-
-if [[ -n "$ZK_HOST" ]]; then
-  EXPORTER_ARGS+=(--zk-host "$ZK_HOST")
-elif [[ -n "$SOLR_URL" ]]; then
-  EXPORTER_ARGS+=(--solr-url "$SOLR_URL")
-fi
-
-if [[ -n "$CLUSTER_ID" ]]; then
-  EXPORTER_ARGS+=(--cluster-id "$CLUSTER_ID")
-fi
-
-if [[ -n "$CREDENTIALS" ]]; then
-  EXPORTER_ARGS+=(--credentials "$CREDENTIALS")
-fi
-
-if [[ -n "$SSL_ENABLED" ]]; then
-  EXPORTER_ARGS+=(--ssl-enabled "$SSL_ENABLED")
-fi
-
-exec "$JAVACMD" \
-  $JAVA_MEM_OPTS \
-  $GC_TUNE \
-  $JAVA_OPTS \
-  $ZK_CREDS_AND_ACLS \
-  -classpath "$CLASSPATH" \
-  -Dapp.name="solr-exporter" \
-  -Dapp.pid="$$" \
-  -Dapp.repo="$REPO" \
-  -Dbasedir="$BASEDIR" \
-  org.apache.solr.prometheus.exporter.SolrExporter \
-  "${EXPORTER_ARGS[@]}" \
-  "$@"
diff --git a/solr/prometheus-exporter/bin/solr-exporter.cmd b/solr/prometheus-exporter/bin/solr-exporter.cmd
deleted file mode 100644
index c55133db616..00000000000
--- a/solr/prometheus-exporter/bin/solr-exporter.cmd
+++ /dev/null
@@ -1,119 +0,0 @@
-@REM
-@REM  Licensed to the Apache Software Foundation (ASF) under one or more
-@REM  contributor license agreements.  See the NOTICE file distributed with
-@REM  this work for additional information regarding copyright ownership.
-@REM  The ASF licenses this file to You under the Apache License, Version 2.0
-@REM  (the "License"); you may not use this file except in compliance with
-@REM  the License.  You may obtain a copy of the License at
-@REM
-@REM      http://www.apache.org/licenses/LICENSE-2.0
-@REM
-@REM  Unless required by applicable law or agreed to in writing, software
-@REM  distributed under the License is distributed on an "AS IS" BASIS,
-@REM  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@REM  See the License for the specific language governing permissions and
-@REM  limitations under the License.
-
-@echo off
-
-set ERROR_CODE=0
-
-:init
-@REM Decide how to startup depending on the version of windows
-
-@REM -- Win98ME
-if NOT "%OS%"=="Windows_NT" goto Win9xArg
-
-@REM set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" @setlocal
-
-@REM -- 4NT shell
-if "%eval[2+2]" == "4" goto 4NTArgs
-
-@REM -- Regular WinNT shell
-set CMD_LINE_ARGS=%*
-goto WinNTGetScriptDir
-
-@REM The 4NT Shell from jp software
-:4NTArgs
-set CMD_LINE_ARGS=%$
-goto WinNTGetScriptDir
-
-:Win9xArg
-@REM Slurp the command line arguments.  This loop allows for an unlimited number
-@REM of agruments (up to the command line limit, anyway).
-set CMD_LINE_ARGS=
-:Win9xApp
-if %1a==a goto Win9xGetScriptDir
-set CMD_LINE_ARGS=%CMD_LINE_ARGS% %1
-shift
-goto Win9xApp
-
-:Win9xGetScriptDir
-set SAVEDIR=%CD%
-%0\
-cd %0\..\..
-set BASEDIR=%CD%
-cd %SAVEDIR%
-set SAVE_DIR=
-goto repoSetup
-
-:WinNTGetScriptDir
-set BASEDIR=%~dp0..
-
-:repoSetup
-
-IF NOT "%JAVA_HEAP%"=="" set JAVA_MEM=-Xms%JAVA_HEAP% -Xmx%JAVA_HEAP%
-IF "%JAVA_MEM%"=="" set JAVA_MEM=-Xms512m -Xmx512m
-IF "%GC_TUNE%"=="" set GC_TUNE=-XX:+UseG1GC
-
-if "%JAVACMD%"=="" set JAVACMD=java
-
-if "%REPO%"=="" set REPO=%BASEDIR%\lib
-
-set CLASSPATH=%REPO%\*;%BASEDIR%\conf;%BASEDIR%\..\server\lib\ext\*;%BASEDIR%\..\server\solr-webapp\webapp\WEB-INF\lib\*
-
-@REM Convert Environment Variables to Command Line Options
-set EXPORTER_ARGS=
-
-IF NOT "%CONFIG_FILE%"=="" set EXPORTER_ARGS=%EXPORTER_ARGS% --config-file %CONFIG_FILE%
-IF NOT "%PORT%"=="" set EXPORTER_ARGS=%EXPORTER_ARGS% -p %PORT%
-IF NOT "%SCRAPE_INTERVAL%"=="" set EXPORTER_ARGS=%EXPORTER_ARGS% --scrape-interval %SCRAPE_INTERVAL%
-IF NOT "%NUM_THREADS%"=="" set EXPORTER_ARGS=%EXPORTER_ARGS% --num-threads %NUM_THREADS%
-IF NOT "%ZK_HOST%"=="" set EXPORTER_ARGS=%EXPORTER_ARGS% --zk-host %ZK_HOST%
-IF NOT "%SOLR_URL%"=="" set EXPORTER_ARGS=%EXPORTER_ARGS% --solr-url %SOLR_URL%
-IF NOT "%CLUSTER_ID%"=="" set EXPORTER_ARGS=%EXPORTER_ARGS% --cluster-id "%CLUSTER_ID%"
-IF NOT "%CREDENTIALS%"=="" set EXPORTER_ARGS=%EXPORTER_ARGS% --credentials "%CREDENTIALS%"
-IF NOT "%SSL_ENABLED%"=="" set EXPORTER_ARGS=%EXPORTER_ARGS% --ssl-enabled "%SSL_ENABLED%"
-goto endInit
-
-@REM Reaching here means variables are defined and arguments have been captured
-:endInit
-
-%JAVACMD% %JAVA_MEM% %GC_TUNE% %JAVA_OPTS% %ZK_CREDS_AND_ACLS% -classpath "%CLASSPATH_PREFIX%;%CLASSPATH%" -Dapp.name="solr-exporter" -Dapp.repo="%REPO%" -Dbasedir="%BASEDIR%" org.apache.solr.prometheus.exporter.SolrExporter %EXPORTER_ARGS% %CMD_LINE_ARGS%
-if ERRORLEVEL 1 goto error
-goto end
-
-:error
-if "%OS%"=="Windows_NT" @endlocal
-set ERROR_CODE=1
-
-:end
-@REM set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" goto endNT
-
-@REM For old DOS remove the set variables from ENV - we assume they were not set
-@REM before we started - at least we don't leave any baggage around
-set CMD_LINE_ARGS=
-goto postExec
-
-:endNT
-@endlocal
-
-:postExec
-
-if "%FORCE_EXIT_ON_ERROR%" == "on" (
-  if %ERROR_CODE% NEQ 0 exit %ERROR_CODE%
-)
-
-exit /B %ERROR_CODE%
diff --git a/solr/prometheus-exporter/conf/grafana-solr-dashboard.json b/solr/prometheus-exporter/conf/grafana-solr-dashboard.json
deleted file mode 100644
index 5a4db0cf377..00000000000
--- a/solr/prometheus-exporter/conf/grafana-solr-dashboard.json
+++ /dev/null
@@ -1,7864 +0,0 @@
-{
-  "__inputs": [
-    {
-      "name": "DS_PROMETHEUS",
-      "label": "Prometheus",
-      "description": "",
-      "type": "datasource",
-      "pluginId": "prometheus",
-      "pluginName": "Prometheus"
-    }
-  ],
-  "__requires": [
-    {
-      "type": "panel",
-      "id": "gauge",
-      "name": "Gauge",
-      "version": ""
-    },
-    {
-      "type": "grafana",
-      "id": "grafana",
-      "name": "Grafana",
-      "version": "7.3.0"
-    },
-    {
-      "type": "panel",
-      "id": "graph",
-      "name": "Graph",
-      "version": ""
-    },
-    {
-      "type": "datasource",
-      "id": "prometheus",
-      "name": "Prometheus",
-      "version": "1.0.0"
-    },
-    {
-      "type": "panel",
-      "id": "stat",
-      "name": "Stat",
-      "version": ""
-    }
-  ],
-  "annotations": {
-    "list": [
-      {
-        "builtIn": 1,
-        "datasource": "-- Grafana --",
-        "enable": true,
-        "hide": true,
-        "iconColor": "rgba(0, 211, 255, 1)",
-        "name": "Annotations & Alerts",
-        "type": "dashboard"
-      }
-    ]
-  },
-  "description": "Default dashboard for Solr Exporter",
-  "editable": true,
-  "gnetId": 12456,
-  "graphTooltip": 0,
-  "id": null,
-  "iteration": 1619091548109,
-  "links": [
-    {
-      "icon": "external link",
-      "tags": [],
-      "title": "Solr project",
-      "type": "link",
-      "url": "https://solr.apache.org/"
-    },
-    {
-      "icon": "external link",
-      "tags": [],
-      "title": "Solr Prometheus documentation",
-      "type": "link",
-      "url": "https://solr.apache.org/guide/solr/latest/deployment-guide/monitoring-with-prometheus-and-grafana.html"
-    }
-  ],
-  "panels": [
-    {
-      "collapsed": false,
-      "datasource": "${DS_PROMETHEUS}",
-      "gridPos": {
-        "h": 1,
-        "w": 24,
-        "x": 0,
-        "y": 0
-      },
-      "id": 64,
-      "panels": [],
-      "title": "Solr Cluster",
-      "type": "row"
-    },
-    {
-      "aliasColors": {},
-      "bars": false,
-      "dashLength": 10,
-      "dashes": false,
-      "datasource": "${DS_PROMETHEUS}",
-      "fieldConfig": {
-        "defaults": {
-          "custom": {
-            "align": null,
-            "filterable": false
-          },
-          "mappings": [],
-          "thresholds": {
-            "mode": "absolute",
-            "steps": [
-              {
-                "color": "green",
-                "value": null
-              },
-              {
-                "color": "red",
-                "value": 80
-              }
-            ]
-          }
-        },
-        "overrides": []
-      },
-      "fill": 0,
-      "fillGradient": 0,
-      "gridPos": {
-        "h": 8,
-        "w": 6,
-        "x": 0,
-        "y": 1
-      },
-      "hiddenSeries": false,
-      "id": 62,
-      "legend": {
-        "alignAsTable": false,
-        "avg": false,
-        "current": false,
-        "max": false,
-        "min": false,
-        "show": true,
-        "total": false,
-        "values": false
-      },
-      "lines": true,
-      "linewidth": 2,
-      "nullPointMode": "null",
-      "options": {
-        "alertThreshold": true
-      },
-      "percentage": false,
-      "pluginVersion": "7.3.0",
-      "pointradius": 2,
-      "points": false,
-      "renderer": "flot",
-      "seriesOverrides": [
-        {
-          "alias": "Total nodes",
-          "lines": true
-        },
-        {
-          "alias": "Live nodes",
-          "bars": true,
-          "color": "#37872D",
-          "lines": false
-        }
-      ],
-      "spaceLength": 10,
-      "stack": false,
-      "steppedLine": false,
-      "targets": [
-        {
-          "expr": "solr_collections_live_nodes{cluster_id=~\"$cluster_id\"}",
-          "format": "time_series",
-          "instant": false,
-          "interval": "",
-          "legendFormat": "Live nodes ({{cluster_id}})",
-          "refId": "A"
-        },
-        {
-          "expr": "count(count by (node_name) (solr_collections_replica_state{cluster_id=~\"$cluster_id\"})) < solr_collections_live_nodes{cluster_id=~\"$cluster_id\"} or solr_collections_live_nodes{cluster_id=~\"$cluster_id\"}",
-          "hide": false,
-          "interval": "",
-          "intervalFactor": 1,
-          "legendFormat": "Total nodes ({{cluster_id}})",
-          "refId": "B"
-        }
-      ],
-      "thresholds": [],
-      "timeFrom": null,
-      "timeRegions": [],
-      "timeShift": null,
-      "title": "Number of live nodes",
-      "tooltip": {
-        "shared": true,
-        "sort": 0,
-        "value_type": "individual"
-      },
-      "type": "graph",
-      "xaxis": {
-        "buckets": null,
-        "mode": "time",
-        "name": null,
-        "show": true,
-        "values": []
-      },
-      "yaxes": [
-        {
-          "decimals": 0,
-          "format": "short",
-          "label": "",
-          "logBase": 1,
-          "max": null,
-          "min": "0",
-          "show": true
-        },
-        {
-          "format": "short",
-          "label": null,
-          "logBase": 1,
-          "max": null,
-          "min": null,
-          "show": true
-        }
-      ],
-      "yaxis": {
-        "align": false,
-        "alignLevel": null
-      }
-    },
-    {
-      "aliasColors": {},
-      "bars": false,
-      "dashLength": 10,
-      "dashes": false,
-      "datasource": "Prometheus",
-      "description": "",
-      "fieldConfig": {
-        "defaults": {
-          "unit": "short"
-        },
-        "overrides": []
-      },
-      "fill": 0,
-      "fillGradient": 0,
-      "gridPos": {
-        "h": 8,
-        "w": 6,
-        "x": 6,
-        "y": 1
-      },
-      "hiddenSeries": false,
-      "id": 221,
-      "legend": {
-        "alignAsTable": false,
-        "avg": false,
-        "current": false,
-        "max": false,
-        "min": false,
-        "show": true,
-        "total": false,
-        "values": false
-      },
-      "lines": true,
-      "linewidth": 2,
-      "nullPointMode": "null",
-      "options": {
-        "alertThreshold": true
-      },
-      "percentage": false,
-      "pluginVersion": "7.5.4",
-      "pointradius": 2,
-      "points": false,
-      "renderer": "flot",
-      "seriesOverrides": [
-        {
-          "$$hashKey": "object:395",
-          "alias": "Ensemble size",
-          "lines": true
-        },
-        {
-          "$$hashKey": "object:396",
-          "alias": "Healthy nodes",
-          "bars": true,
-          "color": "#5794F2",
-          "lines": false
-        }
-      ],
-      "spaceLength": 10,
-      "stack": false,
-      "steppedLine": false,
-      "targets": [
-        {
-          "expr": "count(solr_zookeeper_nodestatus{cluster_id=~\"$cluster_id\"} == 1)",
-          "format": "time_series",
-          "instant": false,
-          "interval": "",
-          "legendFormat": "Healthy nodes ({{cluster_id}})",
-          "refId": "A"
-        },
-        {
-          "expr": "solr_zookeeper_ensemble_size{cluster_id=~\"$cluster_id\"}",
-          "hide": false,
-          "interval": "",
-          "intervalFactor": 1,
-          "legendFormat": "Ensemble size ({{cluster_id}})",
-          "refId": "B"
-        }
-      ],
-      "thresholds": [],
-      "timeFrom": null,
-      "timeRegions": [],
-      "timeShift": null,
-      "title": "Zookeepers",
-      "tooltip": {
-        "shared": true,
-        "sort": 0,
-        "value_type": "individual"
-      },
-      "type": "graph",
-      "xaxis": {
-        "buckets": null,
-        "mode": "time",
-        "name": null,
-        "show": true,
-        "values": []
-      },
-      "yaxes": [
-        {
-          "$$hashKey": "object:413",
-          "decimals": 0,
-          "format": "short",
-          "label": "",
-          "logBase": 1,
-          "max": null,
-          "min": "0",
-          "show": true
-        },
-        {
-          "$$hashKey": "object:414",
-          "format": "short",
-          "label": null,
-          "logBase": 1,
-          "max": null,
-          "min": null,
-          "show": true
-        }
-      ],
-      "yaxis": {
-        "align": false,
-        "alignLevel": null
-      }
-    },
-    {
-      "aliasColors": {},
-      "bars": true,
-      "dashLength": 10,
-      "dashes": false,
-      "datasource": "${DS_PROMETHEUS}",
-      "fieldConfig": {
-        "defaults": {
-          "custom": {}
-        },
-        "overrides": []
-      },
-      "fill": 0,
-      "fillGradient": 0,
-      "gridPos": {
-        "h": 8,
-        "w": 12,
-        "x": 12,
-        "y": 1
-      },
-      "hiddenSeries": false,
-      "id": 68,
-      "legend": {
-        "avg": false,
-        "current": false,
-        "hideEmpty": true,
-        "hideZero": true,
-        "max": false,
-        "min": false,
-        "show": true,
-        "total": false,
-        "values": false
-      },
-      "lines": false,
-      "linewidth": 1,
-      "nullPointMode": "null",
-      "options": {
-        "alertThreshold": true
-      },
-      "percentage": false,
-      "pluginVersion": "7.3.0",
-      "pointradius": 2,
-      "points": false,
-      "renderer": "flot",
-      "seriesOverrides": [],
-      "spaceLength": 10,
-      "stack": true,
-      "steppedLine": false,
-      "targets": [
-        {
-          "expr": "solr_collections_shard_leader{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}",
-          "interval": "",
-          "legendFormat": "{{cluster_id}} {{collection}} {{shard}} {{replica}} ({{node_name}})",
-          "refId": "A"
-        }
-      ],
-      "thresholds": [],
-      "timeFrom": null,
-      "timeRegions": [],
-      "timeShift": null,
-      "title": "Shard leaders",
-      "tooltip": {
-        "shared": true,
-        "sort": 0,
-        "value_type": "individual"
-      },
-      "transformations": [],
-      "type": "graph",
-      "xaxis": {
-        "buckets": null,
-        "mode": "time",
-        "name": null,
-        "show": true,
-        "values": []
-      },
-      "yaxes": [
-        {
-          "decimals": 0,
-          "format": "short",
-          "label": "Leaders",
-          "logBase": 1,
-          "max": null,
-          "min": "0",
-          "show": true
-        },
-        {
-          "format": "short",
-          "label": null,
-          "logBase": 1,
-          "max": null,
-          "min": null,
-          "show": true
-        }
-      ],
-      "yaxis": {
-        "align": false,
-        "alignLevel": null
-      }
-    },
-    {
-      "datasource": "${DS_PROMETHEUS}",
-      "description": "",
-      "fieldConfig": {
-        "defaults": {
-          "custom": {},
-          "mappings": [],
-          "thresholds": {
-            "mode": "absolute",
-            "steps": [
-              {
-                "color": "green",
-                "value": null
-              },
-              {
-                "color": "red",
-                "value": 80
-              }
-            ]
-          }
-        },
-        "overrides": []
-      },
-      "gridPos": {
-        "h": 8,
-        "w": 3,
-        "x": 0,
-        "y": 9
-      },
-      "id": 74,
-      "options": {
-        "colorMode": "value",
-        "graphMode": "none",
-        "justifyMode": "auto",
-        "orientation": "auto",
-        "reduceOptions": {
-          "calcs": [
-            "lastNotNull"
-          ],
-          "fields": "",
-          "values": false
-        },
-        "textMode": "auto"
-      },
-      "pluginVersion": "7.3.0",
-      "targets": [
-        {
-          "expr": "count(count by (collection) (solr_collections_replica_state{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}))",
-          "interval": "",
-          "legendFormat": "",
-          "refId": "A"
-        }
-      ],
-      "timeFrom": null,
-      "timeShift": null,
-      "title": "Number of collections",
-      "type": "stat"
-    },
-    {
-      "datasource": "${DS_PROMETHEUS}",
-      "description": "",
-      "fieldConfig": {
-        "defaults": {
-          "custom": {},
-          "mappings": [],
-          "thresholds": {
-            "mode": "absolute",
-            "steps": [
-              {
-                "color": "green",
-                "value": null
-              },
-              {
-                "color": "red",
-                "value": 80
-              }
-            ]
-          }
-        },
-        "overrides": []
-      },
-      "gridPos": {
-        "h": 8,
-        "w": 9,
-        "x": 3,
-        "y": 9
-      },
-      "id": 70,
-      "options": {
-        "orientation": "auto",
-        "reduceOptions": {
-          "calcs": [
-            "lastNotNull"
-          ],
-          "fields": "",
-          "values": false
-        },
-        "showThresholdLabels": false,
-        "showThresholdMarkers": true
-      },
-      "pluginVersion": "7.3.0",
-      "targets": [
-        {
-          "expr": "count(solr_collections_replica_state{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"})",
-          "interval": "",
-          "legendFormat": "Total replicas",
-          "refId": "B"
-        },
-        {
-          "expr": "count(solr_collections_replica_state{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"} == 1) OR on() vector(0)",
-          "interval": "",
-          "legendFormat": "Active",
-          "refId": "D"
-        },
-        {
-          "expr": "count(solr_collections_replica_state{state=\"down\",cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}) OR on() vector(0)",
-          "interval": "",
-          "legendFormat": "Down",
-          "refId": "A"
-        },
-        {
-          "expr": "count(solr_collections_replica_state{state=\"recovering\",cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}) OR on() vector(0)",
-          "interval": "",
-          "legendFormat": "Recovering",
-          "refId": "C"
-        },
-        {
-          "expr": "count(solr_collections_replica_state{state=\"recovery_failed\",cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}) OR on() vector(0)",
-          "interval": "",
-          "legendFormat": "Recovery failed",
-          "refId": "E"
-        }
-      ],
-      "timeFrom": null,
-      "timeShift": null,
-      "title": "Current replica state",
-      "type": "gauge"
-    },
-    {
-      "aliasColors": {},
-      "bars": false,
-      "dashLength": 10,
-      "dashes": false,
-      "datasource": "${DS_PROMETHEUS}",
-      "fieldConfig": {
-        "defaults": {
-          "custom": {
-            "align": null,
-            "filterable": false
-          },
-          "mappings": [],
-          "thresholds": {
-            "mode": "absolute",
-            "steps": [
-              {
-                "color": "green",
-                "value": null
-              },
-              {
-                "color": "red",
-                "value": 80
-              }
-            ]
-          }
-        },
-        "overrides": []
-      },
-      "fill": 1,
-      "fillGradient": 0,
-      "gridPos": {
-        "h": 8,
-        "w": 12,
-        "x": 12,
-        "y": 9
-      },
-      "hiddenSeries": false,
-      "id": 72,
-      "legend": {
-        "avg": false,
-        "current": false,
-        "max": false,
-        "min": false,
-        "show": true,
-        "total": false,
-        "values": false
-      },
-      "lines": true,
-      "linewidth": 1,
-      "nullPointMode": "null",
-      "options": {
-        "alertThreshold": true
-      },
-      "percentage": false,
-      "pluginVersion": "7.3.0",
-      "pointradius": 2,
-      "points": false,
-      "renderer": "flot",
-      "seriesOverrides": [],
-      "spaceLength": 10,
-      "stack": true,
-      "steppedLine": false,
-      "targets": [
-        {
-          "expr": "1 - solr_collections_replica_state{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}",
-          "interval": "",
-          "legendFormat": "{{cluster_id}} {{collection}} {{shard}} {{replica}} ({{node_name}})={{state}}",
-          "refId": "A"
-        }
-      ],
-      "thresholds": [],
-      "timeFrom": null,
-      "timeRegions": [],
-      "timeShift": null,
-      "title": "Replica state history",
-      "description": "Active replicas have value 0. Non-active replicas will show stacked in the graph.",
-      "tooltip": {
-        "shared": true,
-        "sort": 0,
-        "value_type": "individual"
-      },
-      "type": "graph",
-      "xaxis": {
-        "buckets": null,
-        "mode": "time",
-        "name": null,
-        "show": true,
-        "values": []
-      },
-      "yaxes": [
-        {
-          "decimals": 0,
-          "format": "short",
-          "label": "Non-active states",
-          "logBase": 1,
-          "max": null,
-          "min": 0,
-          "show": true
-        },
-        {
-          "format": "short",
-          "label": null,
-          "logBase": 1,
-          "max": null,
-          "min": null,
-          "show": true
-        }
-      ],
-      "yaxis": {
-        "align": false,
-        "alignLevel": null
-      }
-    },
-    {
-      "collapsed": true,
-      "datasource": "${DS_PROMETHEUS}",
-      "gridPos": {
-        "h": 1,
-        "w": 24,
-        "x": 0,
-        "y": 17
-      },
-      "id": 211,
-      "panels": [
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "description": "Distributed QPS 1-min rate  aggregated by collection, roughly the current QPS across all nodes per collection",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {},
-              "mappings": [],
-              "thresholds": {
-                "mode": "absolute",
-                "steps": [
-                  {
-                    "color": "green",
-                    "value": null
-                  },
-                  {
-                    "color": "red",
-                    "value": 80
-                  }
-                ]
-              }
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 8,
-            "x": 0,
-            "y": 18
-          },
-          "hiddenSeries": false,
-          "id": 201,
-          "legend": {
-            "avg": false,
-            "current": true,
-            "hideEmpty": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": true
-          },
-          "lines": true,
-          "linewidth": 1,
-          "nullPointMode": "connected",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 2,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "sum by (cluster_id, collection) (solr_metrics_core_query_1minRate{internal=\"false\",cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\",searchHandler=~\"$searchHandler\"})",
-              "interval": "",
-              "legendFormat": "{{collection}} ({{cluster_id}})",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Total QPS (by collection)",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": "qps",
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "description": "Distributed QPS 1-min rate aggregated by base_url (Solr node)",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {},
-              "mappings": [],
-              "thresholds": {
-                "mode": "absolute",
-                "steps": [
-                  {
-                    "color": "green",
-                    "value": null
-                  },
-                  {
-                    "color": "red",
-                    "value": 80
-                  }
-                ]
-              }
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 8,
-            "x": 8,
-            "y": 18
-          },
-          "hiddenSeries": false,
-          "id": 202,
-          "legend": {
-            "avg": false,
-            "current": true,
-            "hideEmpty": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": true
-          },
-          "lines": true,
-          "linewidth": 1,
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 2,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "sum by (base_url) (solr_metrics_core_query_1minRate{internal=\"false\",cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\",searchHandler=~\"$searchHandler\"})",
-              "interval": "",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [
-            {
-              "colorMode": "critical",
-              "fill": true,
-              "line": true,
-              "op": "gt",
-              "yaxis": "left"
-            }
-          ],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Total QPS (by node)",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 8,
-            "x": 16,
-            "y": 18
-          },
-          "hiddenSeries": false,
-          "id": 206,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": true,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": true
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "connected",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_core_query_p95_ms{internal=\"false\",cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\",searchHandler=~\"$searchHandler\"}",
-              "format": "time_series",
-              "interval": "",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}  {{core}}  {{searchHandler}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Distributed Query Time per Core (p95 ms)",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 8,
-            "x": 0,
-            "y": 25
-          },
-          "hiddenSeries": false,
-          "id": 204,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": true,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": true
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_core_query_5minRate{internal=\"false\",cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\",searchHandler=~\"$searchHandler\"}",
-              "format": "time_series",
-              "interval": "",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}  {{core}}  {{searchHandler}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Distributed QPS per Core (5-min rate)",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 8,
-            "x": 8,
-            "y": 25
-          },
-          "hiddenSeries": false,
-          "id": 205,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": true,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": true
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "connected",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_core_query_p99_ms{internal=\"false\",cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\",searchHandler=~\"$searchHandler\"}",
-              "format": "time_series",
-              "interval": "",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}  {{core}}  {{searchHandler}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Distributed Query Time per Core (p99 ms)",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": "ms",
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 8,
-            "x": 16,
-            "y": 25
-          },
-          "hiddenSeries": false,
-          "id": 203,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": true,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": true
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_core_query_1minRate{internal=\"false\",cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\",searchHandler=~\"$searchHandler\"}",
-              "format": "time_series",
-              "instant": false,
-              "interval": "",
-              "intervalFactor": 3,
-              "legendFormat": "{{base_url}}  {{core}}  {{searchHandler}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Distributed QPS per Core (1-min rate)",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": "qps",
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 8,
-            "x": 0,
-            "y": 32
-          },
-          "hiddenSeries": false,
-          "id": 207,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": true,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": true
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "connected",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_core_query_p75_ms{internal=\"false\",cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\",searchHandler=~\"$searchHandler\"}",
-              "format": "time_series",
-              "interval": "",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}  {{core}}  {{searchHandler}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Distributed Query Time per Core (p75 ms)",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "description": "Helps identify if a core is receiving more traffic than other cores, possibly indicating unbalanced load.",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 8,
-            "x": 8,
-            "y": 32
-          },
-          "hiddenSeries": false,
-          "id": 218,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": true,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": true
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "connected",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_core_query_requests_total{internal!=\"false\",cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\",searchHandler=~\"$searchHandler\"}[1m])",
-              "format": "time_series",
-              "instant": false,
-              "interval": "",
-              "intervalFactor": 3,
-              "legendFormat": "{{base_url}}/{{core}}{{searchHandler}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Local Query Count per Core",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": "qps",
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 8,
-            "x": 16,
-            "y": 32
-          },
-          "hiddenSeries": false,
-          "id": 219,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": true,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": true
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "connected",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_core_query_1minRate{internal!=\"false\",cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\",searchHandler=~\"$searchHandler\"}",
-              "format": "time_series",
-              "instant": false,
-              "interval": "",
-              "intervalFactor": 3,
-              "legendFormat": "{{base_url}}/{{core}}{{searchHandler}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Local QPS per Core (1-min rate)",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": "qps",
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 8,
-            "x": 0,
-            "y": 39
-          },
-          "hiddenSeries": false,
-          "id": 217,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": true,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": true
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "connected",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_core_query_p95_ms{internal!=\"false\",cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\",searchHandler=~\"$searchHandler\"}",
-              "format": "time_series",
-              "interval": "",
-              "intervalFactor": 3,
-              "legendFormat": "{{base_url}}/{{core}}{{searchHandler}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Local Query Time per Core (p95 ms)",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 8,
-            "x": 8,
-            "y": 39
-          },
-          "hiddenSeries": false,
-          "id": 210,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": true,
-            "hideEmpty": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": true
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_core_query_client_errors_1minRate{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\",searchHandler=~\"$searchHandler\"}",
-              "format": "time_series",
-              "interval": "",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}  {{core}}  {{searchHandler}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Query Client Errors per Core (1-min rate)",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 8,
-            "x": 16,
-            "y": 39
-          },
-          "hiddenSeries": false,
-          "id": 209,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": true,
-            "hideEmpty": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": true
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_core_query_errors_1minRate{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\",searchHandler=~\"$searchHandler\"}",
-              "format": "time_series",
-              "interval": "",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}  {{core}}  {{searchHandler}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Query Errors per Core (1-min rate)",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        }
-      ],
-      "repeat": null,
-      "title": "Query Metrics",
-      "type": "row"
-    },
-    {
-      "collapsed": true,
-      "datasource": "${DS_PROMETHEUS}",
-      "gridPos": {
-        "h": 1,
-        "w": 24,
-        "x": 0,
-        "y": 18
-      },
-      "id": 212,
-      "panels": [
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 0,
-            "y": 19
-          },
-          "hiddenSeries": false,
-          "id": 2,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_jetty_requests_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}[1m])",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}} {{method}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Requests",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "description": "",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 12,
-            "y": 19
-          },
-          "hiddenSeries": false,
-          "id": 1,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_jetty_response_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}[1m])",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}} {{status}}",
-              "refId": "A",
-              "step": 40
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Response",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 24,
-            "x": 0,
-            "y": 26
-          },
-          "hiddenSeries": false,
-          "id": 55,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "rightSide": true,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_jetty_dispatches_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}[1m])",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Dispatches",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        }
-      ],
-      "repeat": null,
-      "title": "Jetty Metrics",
-      "type": "row"
-    },
-    {
-      "collapsed": true,
-      "datasource": "${DS_PROMETHEUS}",
-      "gridPos": {
-        "h": 1,
-        "w": 24,
-        "x": 0,
-        "y": 19
-      },
-      "id": 213,
-      "panels": [
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 0,
-            "y": 20
-          },
-          "hiddenSeries": false,
-          "id": 3,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_jvm_buffers{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}} {{pool}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Buffers",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 12,
-            "y": 20
-          },
-          "hiddenSeries": false,
-          "id": 4,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_jvm_buffers_bytes{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}} {{pool}} {{item}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Buffer Size",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "decbytes",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 0,
-            "y": 27
-          },
-          "hiddenSeries": false,
-          "id": 5,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_jvm_gc_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}[1m])",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}} {{item}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "GC Count",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 12,
-            "y": 27
-          },
-          "hiddenSeries": false,
-          "id": 6,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_jvm_gc_seconds_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}[1m])",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}} {{item}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "GC Time",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "decimals": null,
-              "format": "s",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 0,
-            "y": 34
-          },
-          "hiddenSeries": false,
-          "id": 7,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_jvm_memory_heap_bytes{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}} {{item}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Heap Size",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "decbytes",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 12,
-            "y": 34
-          },
-          "hiddenSeries": false,
-          "id": 8,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_jvm_memory_non_heap_bytes{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}} {{item}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Non-Heap Size",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "decbytes",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 0,
-            "y": 41
-          },
-          "hiddenSeries": false,
-          "id": 9,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_jvm_memory_pools_bytes{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}} {{space}} {{item}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Pool Size",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "decbytes",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 12,
-            "y": 41
-          },
-          "hiddenSeries": false,
-          "id": 10,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_jvm_memory_bytes{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}} {{item}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Memory Size",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "decbytes",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 24,
-            "x": 0,
-            "y": 48
-          },
-          "hiddenSeries": false,
-          "id": 16,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "rightSide": true,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_jvm_threads{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}} {{item}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Threads",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        }
-      ],
-      "repeat": null,
-      "title": "JVM Metrics",
-      "type": "row"
-    },
-    {
-      "collapsed": true,
-      "datasource": "${DS_PROMETHEUS}",
-      "gridPos": {
-        "h": 1,
-        "w": 24,
-        "x": 0,
-        "y": 20
-      },
-      "id": 214,
-      "panels": [
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 0,
-            "y": 21
-          },
-          "hiddenSeries": false,
-          "id": 11,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_jvm_os_memory_bytes{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}} {{item}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Memory Size",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "decbytes",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 12,
-            "y": 21
-          },
-          "hiddenSeries": false,
-          "id": 12,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_jvm_os_file_descriptors{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}} {{item}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "File Descriptors",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 0,
-            "y": 28
-          },
-          "hiddenSeries": false,
-          "id": 13,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_jvm_os_cpu_load{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}} {{item}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "CPU Load",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "percentunit",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "unit": "s"
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 12,
-            "y": 28
-          },
-          "hiddenSeries": false,
-          "id": 14,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_jvm_os_cpu_time_seconds{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}[1m])",
-              "format": "time_series",
-              "interval": "",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}} {{item}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "CPU Time",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 24,
-            "x": 0,
-            "y": 35
-          },
-          "hiddenSeries": false,
-          "id": 15,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "rightSide": true,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_jvm_os_load_average{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}} {{item}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Load Average",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        }
-      ],
-      "repeat": null,
-      "title": "OS Metrics",
-      "type": "row"
-    },
-    {
-      "collapsed": true,
-      "datasource": "${DS_PROMETHEUS}",
-      "gridPos": {
-        "h": 1,
-        "w": 24,
-        "x": 0,
-        "y": 21
-      },
-      "id": 215,
-      "panels": [
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 0,
-            "y": 22
-          },
-          "hiddenSeries": false,
-          "id": 19,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_node_requests_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}",
-              "format": "time_series",
-              "interval": "",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}} {{handler}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Requests",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "unit": "s"
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 12,
-            "y": 22
-          },
-          "hiddenSeries": false,
-          "id": 22,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_node_time_seconds_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}[1m])",
-              "format": "time_series",
-              "interval": "",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}{{handler}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Request Time",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 0,
-            "y": 29
-          },
-          "hiddenSeries": false,
-          "id": 17,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_node_client_errors_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}[1m])",
-              "format": "time_series",
-              "interval": "",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}{{handler}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Client Errors",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 12,
-            "y": 29
-          },
-          "hiddenSeries": false,
-          "id": 20,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_node_server_errors_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}[1m])",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}{{handler}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Server Errors",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 0,
-            "y": 36
-          },
-          "hiddenSeries": false,
-          "id": 18,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_node_errors_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}[1m])",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}{{handler}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Errors",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 12,
-            "y": 36
-          },
-          "hiddenSeries": false,
-          "id": 21,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_node_timeouts_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}[1m])",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}{{handler}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Timeouts",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 0,
-            "y": 43
-          },
-          "hiddenSeries": false,
-          "id": 23,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_node_cores{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}} {{item}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Cores",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 12,
-            "y": 43
-          },
-          "hiddenSeries": false,
-          "id": 24,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_node_core_root_fs_bytes{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}} {{item}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Core Root File System",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "decbytes",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 0,
-            "y": 50
-          },
-          "hiddenSeries": false,
-          "id": 25,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_node_thread_pool_completed_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}",
-              "format": "time_series",
-              "interval": "",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}} {{handler}} {{executor}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Thread Pool Completed",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 12,
-            "y": 50
-          },
-          "hiddenSeries": false,
-          "id": 26,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_node_thread_pool_submitted_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}",
-              "format": "time_series",
-              "interval": "",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}} {{handler}} {{executor}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Thread Pool Submitted",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 24,
-            "x": 0,
-            "y": 57
-          },
-          "hiddenSeries": false,
-          "id": 27,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "rightSide": true,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_node_thread_pool_running{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}",
-              "format": "time_series",
-              "interval": "",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}} {{handler}} {{executor}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Thread Pool Running",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 16,
-            "x": 0,
-            "y": 64
-          },
-          "hiddenSeries": false,
-          "id": 28,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "rightSide": true,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_node_connections{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\"}",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}} {{handler}} {{item}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Connections",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "description": "Number of leaders on each node",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {},
-              "mappings": [],
-              "thresholds": {
-                "mode": "absolute",
-                "steps": [
-                  {
-                    "color": "green",
-                    "value": null
-                  },
-                  {
-                    "color": "red",
-                    "value": 80
-                  }
-                ]
-              }
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 8,
-            "x": 16,
-            "y": 64
-          },
-          "hiddenSeries": false,
-          "id": 220,
-          "legend": {
-            "avg": false,
-            "current": true,
-            "hideEmpty": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": true
-          },
-          "lines": true,
-          "linewidth": 1,
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 2,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "sum by (base_url) (solr_collections_shard_leader{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"})",
-              "interval": "",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [
-            {
-              "colorMode": "critical",
-              "fill": true,
-              "line": true,
-              "op": "gt",
-              "yaxis": "left"
-            }
-          ],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Leader Count (by node)",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        }
-      ],
-      "repeat": null,
-      "title": "Node Metrics",
-      "type": "row"
-    },
-    {
-      "collapsed": true,
-      "datasource": "${DS_PROMETHEUS}",
-      "gridPos": {
-        "h": 1,
-        "w": 24,
-        "x": 0,
-        "y": 22
-      },
-      "id": 216,
-      "panels": [
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 0,
-            "y": 23
-          },
-          "hiddenSeries": false,
-          "id": 31,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_core_requests_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}[1m])",
-              "format": "time_series",
-              "interval": "",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}}{{handler}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Requests",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "unit": "s"
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 12,
-            "y": 23
-          },
-          "hiddenSeries": false,
-          "id": 34,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_core_time_seconds_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}[1m])",
-              "format": "time_series",
-              "interval": "",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}}{{handler}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Request Time",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 0,
-            "y": 30
-          },
-          "hiddenSeries": false,
-          "id": 29,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_core_client_errors_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}[1m])",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}}{{handler}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Client Errors",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 12,
-            "y": 30
-          },
-          "hiddenSeries": false,
-          "id": 32,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_core_server_errors_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}[1m])",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}}{{handler}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Server Errors",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 0,
-            "y": 37
-          },
-          "hiddenSeries": false,
-          "id": 30,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_core_errors_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}[1m])",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}}{{handler}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Errors",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 12,
-            "y": 37
-          },
-          "hiddenSeries": false,
-          "id": 33,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_core_timeouts_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}[1m])",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}}{{handler}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Timeouts",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 0,
-            "y": 44
-          },
-          "hiddenSeries": false,
-          "id": 35,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_core_searcher_cache_ratio{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}",
-              "format": "time_series",
-              "interval": "",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}} {{type}} {{item}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Searcher Cache Hit Ratio",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 12,
-            "y": 44
-          },
-          "hiddenSeries": false,
-          "id": 36,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_core_searcher_cache{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}",
-              "format": "time_series",
-              "interval": "",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}} {{type}} {{item}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Searcher Cache",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 0,
-            "y": 51
-          },
-          "hiddenSeries": false,
-          "id": 37,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_core_searcher_warmup_time_seconds{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}} {{type}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Searcher Warm Up Time",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 12,
-            "y": 51
-          },
-          "hiddenSeries": false,
-          "id": 38,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_core_searcher_cumulative_cache_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}[1m])",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}} {{type}} {{item}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Searcher Cumulative Cache",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 0,
-            "y": 58
-          },
-          "hiddenSeries": false,
-          "id": 39,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_core_fs_bytes{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "File System",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "decbytes",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 12,
-            "y": 58
-          },
-          "hiddenSeries": false,
-          "id": 40,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_core_index_size_bytes{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Index Size",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "decbytes",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 24,
-            "x": 0,
-            "y": 65
-          },
-          "hiddenSeries": false,
-          "id": 41,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "rightSide": true,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_core_searcher_documents{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}} {{item}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Searcher Documents",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 0,
-            "y": 72
-          },
-          "hiddenSeries": false,
-          "id": 42,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "rightSide": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_core_update_handler_adds_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}[1m])",
-              "format": "time_series",
-              "interval": "",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Update Handler Adds (cumulative)",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 12,
-            "y": 72
-          },
-          "hiddenSeries": false,
-          "id": 46,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "solr_metrics_core_update_handler_pending_docs{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Pending Docs",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 8,
-            "x": 0,
-            "y": 79
-          },
-          "hiddenSeries": false,
-          "id": 43,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_core_update_handler_deletes_by_id_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}[1m])",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Update Handler Deletes By ID",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 8,
-            "x": 8,
-            "y": 79
-          },
-          "hiddenSeries": false,
-          "id": 44,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_core_update_handler_deletes_by_query_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}[1m])",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Update Handler Deletes By Query",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 8,
-            "x": 16,
-            "y": 79
-          },
-          "hiddenSeries": false,
-          "id": 47,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_core_update_handler_expunge_deletes_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}[1m])",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Update Handler Expunge Deletes",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 0,
-            "y": 86
-          },
-          "hiddenSeries": false,
-          "id": 48,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_core_update_handler_merges_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}[1m])",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Update Handler Merges",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 12,
-            "y": 86
-          },
-          "hiddenSeries": false,
-          "id": 52,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_core_update_handler_splits_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}[1m])",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Update Handler Splits",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 0,
-            "y": 93
-          },
-          "hiddenSeries": false,
-          "id": 49,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_core_update_handler_optimizes_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}[1m])",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Update Handler Optimizes",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 12,
-            "x": 12,
-            "y": 93
-          },
-          "hiddenSeries": false,
-          "id": 50,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_core_update_handler_rollbacks_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}[1m])",
-              "format": "time_series",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Update Handler Rollbacks",
-          "tooltip": {
-            "shared": true,
-            "sort": 0,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 8,
-            "x": 0,
-            "y": 100
-          },
-          "hiddenSeries": false,
-          "id": 51,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_core_update_handler_soft_auto_commits_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}[1m])",
-              "format": "time_series",
-              "interval": "",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Update Handler Soft Auto Commits",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 8,
-            "x": 8,
-            "y": 100
-          },
-          "hiddenSeries": false,
-          "id": 53,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_core_update_handler_auto_commits_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}[1m])",
-              "format": "time_series",
-              "interval": "",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Update Handler Auto Commits",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 8,
-            "x": 16,
-            "y": 100
-          },
-          "hiddenSeries": false,
-          "id": 54,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_core_update_handler_commits_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}[1m])",
-              "format": "time_series",
-              "interval": "",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Update Handler Commits",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        },
-        {
-          "aliasColors": {},
-          "bars": false,
-          "dashLength": 10,
-          "dashes": false,
-          "datasource": "${DS_PROMETHEUS}",
-          "fieldConfig": {
-            "defaults": {
-              "custom": {}
-            },
-            "overrides": []
-          },
-          "fill": 1,
-          "fillGradient": 0,
-          "gridPos": {
-            "h": 7,
-            "w": 24,
-            "x": 0,
-            "y": 107
-          },
-          "hiddenSeries": false,
-          "id": 45,
-          "legend": {
-            "alignAsTable": true,
-            "avg": false,
-            "current": false,
-            "hideEmpty": true,
-            "hideZero": true,
-            "max": false,
-            "min": false,
-            "rightSide": true,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 1,
-          "links": [],
-          "nullPointMode": "null",
-          "options": {
-            "alertThreshold": true
-          },
-          "percentage": false,
-          "pluginVersion": "7.3.0",
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "spaceLength": 10,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "expr": "increase(solr_metrics_core_update_handler_errors_total{cluster_id=~\"$cluster_id\",base_url=~\"$base_url\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\",core=~\"$core\"}[1m])",
-              "format": "time_series",
-              "interval": "",
-              "intervalFactor": 2,
-              "legendFormat": "{{base_url}}/{{core}}",
-              "refId": "A"
-            }
-          ],
-          "thresholds": [],
-          "timeFrom": null,
-          "timeRegions": [],
-          "timeShift": null,
-          "title": "Update Handler Errors",
-          "tooltip": {
-            "shared": true,
-            "sort": 2,
-            "value_type": "individual"
-          },
-          "type": "graph",
-          "xaxis": {
-            "buckets": null,
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
-            },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ],
-          "yaxis": {
-            "align": false,
-            "alignLevel": null
-          }
-        }
-      ],
-      "repeat": null,
-      "title": "Core Metrics",
-      "type": "row"
-    }
-  ],
-  "refresh": "30s",
-  "schemaVersion": 26,
-  "style": "dark",
-  "tags": [],
-  "templating": {
-    "list": [
-      {
-        "allValue": ".*",
-        "current": {},
-        "datasource": "${DS_PROMETHEUS}",
-        "definition": "",
-        "error": null,
-        "hide": 0,
-        "includeAll": true,
-        "label": null,
-        "multi": true,
-        "name": "cluster_id",
-        "options": [],
-        "query": "label_values(solr_ping{cluster_id=~\".+\"},cluster_id)",
-        "refresh": 1,
-        "regex": "/(.+)/",
-        "skipUrlSync": false,
-        "sort": 1,
-        "tagValuesQuery": "",
-        "tags": [],
-        "tagsQuery": "",
-        "type": "query",
-        "useTags": false
-      },
-      {
-        "allValue": ".*",
-        "current": {},
-        "datasource": "${DS_PROMETHEUS}",
-        "definition": "",
-        "error": null,
-        "hide": 0,
-        "includeAll": true,
-        "label": null,
-        "multi": true,
-        "name": "zk_host",
-        "options": [],
-        "query": "label_values(solr_ping{cluster_id=~\"$cluster_id\",zk_host=~\".+\"},zk_host)",
-        "refresh": 1,
-        "regex": "/(.+)/",
-        "skipUrlSync": false,
-        "sort": 1,
-        "tagValuesQuery": "",
-        "tags": [],
-        "tagsQuery": "",
-        "type": "query",
-        "useTags": false
-      },
-      {
-        "allValue": ".*",
-        "current": {},
-        "datasource": "${DS_PROMETHEUS}",
-        "definition": "",
-        "error": null,
-        "hide": 0,
-        "includeAll": true,
-        "label": null,
-        "multi": true,
-        "name": "base_url",
-        "options": [],
-        "query": "label_values(solr_ping{cluster_id=~\"$cluster_id\",base_url=~\".+\"},base_url)",
-        "refresh": 1,
-        "regex": "/(.+)/",
-        "skipUrlSync": false,
-        "sort": 1,
-        "tagValuesQuery": "",
-        "tags": [],
-        "tagsQuery": "",
-        "type": "query",
-        "useTags": false
-      },
-      {
-        "allValue": ".*",
-        "current": {},
-        "datasource": "${DS_PROMETHEUS}",
-        "definition": "",
-        "error": null,
-        "hide": 0,
-        "includeAll": true,
-        "label": null,
-        "multi": true,
-        "name": "collection",
-        "options": [],
-        "query": "label_values(solr_collections_shard_state{cluster_id=~\"$cluster_id\",zk_host=~\"$zk_host\",collection=~\".+\"},collection)",
-        "refresh": 1,
-        "regex": "/(.+)/",
-        "skipUrlSync": false,
-        "sort": 1,
-        "tagValuesQuery": "",
-        "tags": [],
-        "tagsQuery": "",
-        "type": "query",
-        "useTags": false
-      },
-      {
-        "allValue": ".*",
-        "current": {},
-        "datasource": "${DS_PROMETHEUS}",
-        "definition": "",
-        "error": null,
-        "hide": 0,
-        "includeAll": true,
-        "label": null,
-        "multi": true,
-        "name": "shard",
-        "options": [],
-        "query": "label_values(solr_collections_shard_state{cluster_id=~\"$cluster_id\",zk_host=~\"$zk_host\",collection=~\"$collection\",shard=~\".+\"},shard)",
-        "refresh": 1,
-        "regex": "/(.+)/",
-        "skipUrlSync": false,
-        "sort": 1,
-        "tagValuesQuery": "",
-        "tags": [],
-        "tagsQuery": "",
-        "type": "query",
-        "useTags": false
-      },
-      {
-        "allValue": ".*",
-        "current": {},
-        "datasource": "${DS_PROMETHEUS}",
-        "definition": "",
-        "error": null,
-        "hide": 0,
-        "includeAll": true,
-        "label": null,
-        "multi": true,
-        "name": "replica",
-        "options": [],
-        "query": "label_values(solr_collections_replica_state{cluster_id=~\"$cluster_id\",zk_host=~\"$zk_host\",collection=~\"$collection\",shard=~\"$shard\",replica=~\".+\"},replica)",
-        "refresh": 1,
-        "regex": "/(.+)/",
-        "skipUrlSync": false,
-        "sort": 1,
-        "tagValuesQuery": "",
-        "tags": [],
-        "tagsQuery": "",
-        "type": "query",
-        "useTags": false
-      },
-      {
-        "allValue": ".*",
-        "current": {},
-        "datasource": "${DS_PROMETHEUS}",
-        "definition": "",
-        "error": null,
-        "hide": 0,
-        "includeAll": true,
-        "label": null,
-        "multi": true,
-        "name": "core",
-        "options": [],
-        "query": "label_values(solr_collections_replica_state{cluster_id=~\"$cluster_id\",zk_host=~\"$zk_host\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\"},core)",
-        "refresh": 1,
-        "regex": "/(.+)/",
-        "skipUrlSync": false,
-        "sort": 1,
-        "tagValuesQuery": "",
-        "tags": [],
-        "tagsQuery": "",
-        "type": "query",
-        "useTags": false
-      },
-      {
-        "allValue": ".*",
-        "current": {},
-        "datasource": "${DS_PROMETHEUS}",
-        "definition": "label_values(solr_metrics_core_query_errors_1minRate{cluster_id=~\"$cluster_id\",zk_host=~\"$zk_host\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\"},searchHandler)",
-        "error": null,
-        "hide": 0,
-        "includeAll": true,
-        "label": null,
-        "multi": true,
-        "name": "searchHandler",
-        "options": [],
-        "query": "label_values(solr_metrics_core_query_errors_1minRate{cluster_id=~\"$cluster_id\",zk_host=~\"$zk_host\",collection=~\"$collection\",shard=~\"$shard\",replica=~\"$replica\"},searchHandler)",
-        "refresh": 1,
-        "regex": "/(.+)/",
-        "skipUrlSync": false,
-        "sort": 1,
-        "tagValuesQuery": "",
-        "tags": [],
-        "tagsQuery": "",
-        "type": "query",
-        "useTags": false
-      }
-    ]
-  },
-  "time": {
-    "from": "now-15m",
-    "to": "now"
-  },
-  "timepicker": {
-    "refresh_intervals": [
-      "5s",
-      "10s",
-      "30s",
-      "1m",
-      "5m",
-      "15m",
-      "30m",
-      "1h",
-      "2h",
-      "1d"
-    ],
-    "time_options": [
-      "5m",
-      "15m",
-      "1h",
-      "6h",
-      "12h",
-      "24h",
-      "2d",
-      "7d",
-      "30d"
-    ]
-  },
-  "timezone": "browser",
-  "title": "Solr Dashboard",
-  "uid": "yt5eXHbMz5",
-  "version": 2
-}
diff --git a/solr/prometheus-exporter/conf/log4j2.xml b/solr/prometheus-exporter/conf/log4j2.xml
deleted file mode 100644
index 47f533f1054..00000000000
--- a/solr/prometheus-exporter/conf/log4j2.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one or more
-  contributor license agreements.  See the NOTICE file distributed with
-  this work for additional information regarding copyright ownership.
-  The ASF licenses this file to You under the Apache License, Version 2.0
-  (the "License"); you may not use this file except in compliance with
-  the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-
-<!-- Use this file for logging exlusively to the console, useful for
-     some development tasks. Should not be used for production -->
-<!-- Default production configuration is asnychronous logging -->
-<Configuration>
-  <Appenders>
-    <Console name="STDERR" target="SYSTEM_ERR">
-      <PatternLayout>
-        <Pattern>
-          %maxLen{%-5p - %d{yyyy-MM-dd HH:mm:ss.SSS}; %c; %m%notEmpty{ =>%ex{short}}}{10240}%n
-        </Pattern>
-      </PatternLayout>
-    </Console>
-  </Appenders>
-  <Loggers>
-    <!-- Use <AsyncLogger/<AsyncRoot and <Logger/<Root for asynchronous logging or synchonous logging respectively -->
-    <AsyncRoot level="INFO">
-      <AppenderRef ref="STDERR"/>
-    </AsyncRoot>
-  </Loggers>
-</Configuration>
-
-
diff --git a/solr/prometheus-exporter/conf/solr-exporter-config.xml b/solr/prometheus-exporter/conf/solr-exporter-config.xml
deleted file mode 100644
index 275ab46f4f1..00000000000
--- a/solr/prometheus-exporter/conf/solr-exporter-config.xml
+++ /dev/null
@@ -1,1134 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<!--
- Licensed to the Apache Software Foundation (ASF) under one or more
- contributor license agreements.  See the NOTICE file distributed with
- this work for additional information regarding copyright ownership.
- The ASF licenses this file to You under the Apache License, Version 2.0
- (the "License"); you may not use this file except in compliance with
- the License.  You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<config>
-
-  <!--
-  Templates to help reduce jq boilerplate used by many metrics in this config;
-  mainly intended for metrics that don't require a bunch of jq magic to work and are mostly boilerplate.
-
-  A regex with named groups is used to match template references to template + vars using the basic pattern:
-
-      $jq:<TEMPLATE>( <UNIQUE>, <KEYSELECTOR>, <METRIC>, <TYPE> )
-
-  For instance,
-
-      $jq:core(requests_total, endswith(".requestTimes"), count, COUNTER)
-
-  TEMPLATE = core
-  UNIQUE = requests_total (unique suffix for this metric, results in a metric named "solr_metrics_core_requests_total")
-  KEYSELECTOR = endswith(".requestTimes") (filter to select the specific key for this metric)
-  METRIC = count
-  TYPE = COUNTER
-
-  Some templates may have a default type, so you can omit that from your template reference, such as:
-
-      $jq:core(requests_total, endswith(".requestTimes"), count)
-
-  Uses the defaultType=COUNTER as many uses of the core template are counts.
-
-  If a template reference omits the metric, then the unique suffix is used, for instance:
-
-      $jq:core-query(1minRate, endswith(".requestTimes"))
-
-  Creates a GAUGE metric (default type) named "solr_metrics_core_query_1minRate" using the 1minRate value from the selected JSON object.
-
-  Add templates as needed, three metrics using the same structure feels about right as the threshold for creating a new template.
-  -->
-  <jq-templates>
-    <template name="core-query" defaultType="GAUGE">
-      .metrics | to_entries | .[] | select(.key | startswith("solr.core.")) as $parent |
-      $parent.key | split(".") as $parent_key_items |
-      $parent_key_items | length as $parent_key_item_len |
-      (if $parent_key_item_len == 3 then $parent_key_items[2] else "" end) as $core |
-      (if $parent_key_item_len == 5 then $parent_key_items[2] else "" end) as $collection |
-      (if $parent_key_item_len == 5 then $parent_key_items[3] else "" end) as $shard |
-      (if $parent_key_item_len == 5 then $parent_key_items[4] else "" end) as $replica |
-      (if $parent_key_item_len == 5 then ($collection + "_" + $shard + "_" + $replica) else $core end) as $core |
-      $parent.value | to_entries | .[] | {KEYSELECTOR} | select (.value | type == "object") as $object |
-      $object.key | split(".")[0] as $category |
-      $object.key | split(".")[1] | rtrimstr("]") | split("[") | .[0] as $handler | .[1] // "false" as $internal |
-      select($category | startswith("QUERY")) |
-      select($handler | startswith("/")) |
-      {METRIC} as $value |
-      if $parent_key_item_len == 3 then
-      {
-      name: "solr_metrics_core_query_{UNIQUE}",
-      type: "{TYPE}",
-      help: "See: https://solr.apache.org/guide/solr/latest/deployment-guide/performance-statistics-reference.html",
-      label_names: ["category", "searchHandler", "internal", "core"],
-      label_values: [$category, $handler, $internal, $core],
-      value: $value
-      }
-      else
-      {
-      name: "solr_metrics_core_query_{UNIQUE}",
-      type: "{TYPE}",
-      help: "See: https://solr.apache.org/guide/solr/latest/deployment-guide/performance-statistics-reference.html",
-      label_names: ["category", "searchHandler", "internal", "core", "collection", "shard", "replica"],
-      label_values: [$category, $handler, $internal, $core, $collection, $shard, $replica],
-      value: $value
-      }
-      end
-    </template>
-    <template name="core" defaultType="COUNTER">
-      .metrics | to_entries | .[] | select(.key | startswith("solr.core.")) as $parent |
-      $parent.key | split(".") as $parent_key_items |
-      $parent_key_items | length as $parent_key_item_len |
-      (if $parent_key_item_len == 3 then $parent_key_items[2] else "" end) as $core |
-      (if $parent_key_item_len == 5 then $parent_key_items[2] else "" end) as $collection |
-      (if $parent_key_item_len == 5 then $parent_key_items[3] else "" end) as $shard |
-      (if $parent_key_item_len == 5 then $parent_key_items[4] else "" end) as $replica |
-      (if $parent_key_item_len == 5 then ($collection + "_" + $shard + "_" + $replica) else $core end) as $core |
-      $parent.value | to_entries | .[] | {KEYSELECTOR} as $object |
-      $object.key | split(".")[0] as $category |
-      $object.key | split(".")[1] | rtrimstr("]") | split("[") | .[0] as $handler | .[1] // "false" as $internal |
-      select($handler | startswith("/")) |
-      {METRIC} as $value |
-      if $parent_key_item_len == 3 then
-      {
-      name: "solr_metrics_core_{UNIQUE}",
-      type: "{TYPE}",
-      help: "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-      label_names: ["category", "handler", "internal", "core"],
-      label_values: [$category, $handler, $internal, $core],
-      value: $value
-      }
-      else
-      {
-      name: "solr_metrics_core_{UNIQUE}",
-      type: "{TYPE}",
-      help: "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-      label_names: ["category", "handler", "internal", "core", "collection", "shard", "replica"],
-      label_values: [$category, $handler, $internal, $core, $collection, $shard, $replica],
-      value: $value
-      }
-      end
-    </template>
-    <template name="update-handler" defaultType="COUNTER">
-      .metrics | to_entries | .[] | select(.key | startswith("solr.core.")) as $parent |
-      $parent.key | split(".") as $parent_key_items |
-      $parent_key_items | length as $parent_key_item_len |
-      (if $parent_key_item_len == 3 then $parent_key_items[2] else "" end) as $core |
-      (if $parent_key_item_len == 5 then $parent_key_items[2] else "" end) as $collection |
-      (if $parent_key_item_len == 5 then $parent_key_items[3] else "" end) as $shard |
-      (if $parent_key_item_len == 5 then $parent_key_items[4] else "" end) as $replica |
-      (if $parent_key_item_len == 5 then ($collection + "_" + $shard + "_" + $replica) else $core end) as $core |
-      $parent.value | to_entries | .[] | {KEYSELECTOR} as $object |
-      $object.key | split(".")[0] as $category |
-      $object.key | split(".")[1] as $handler |
-      {METRIC} as $value |
-      if $parent_key_item_len == 3 then
-      {
-      name: "solr_metrics_core_{UNIQUE}",
-      type: "{TYPE}",
-      help: "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-      label_names: ["category", "handler", "core"],
-      label_values: [$category, $handler, $core],
-      value: $value
-      }
-      else
-      {
-      name: "solr_metrics_core_{UNIQUE}",
-      type: "{TYPE}",
-      help: "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-      label_names: ["category", "handler", "core", "collection", "shard", "replica"],
-      label_values: [$category, $handler, $core, $collection, $shard, $replica],
-      value: $value
-      }
-      end
-    </template>
-    <template name="node" defaultType="COUNTER">
-      .metrics["solr.node"] | to_entries | .[] | {KEYSELECTOR} as $object |
-      $object.key | split(".")[0] as $category |
-      $object.key | split(".")[1] as $handler |
-      {METRIC} as $value |
-      {
-      name         : "solr_metrics_node_{UNIQUE}",
-      type         : "{TYPE}",
-      help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-      label_names  : ["category", "handler"],
-      label_values : [$category, $handler],
-      value        : $value
-      }
-    </template>
-    <template name="cache-searcher" defaultType="GAUGE">
-      .metrics | to_entries | .[] | select(.key | startswith("solr.core.")) as $parent |
-      $parent.key | split(".") as $parent_key_items |
-      $parent_key_items | length as $parent_key_item_len |
-      (if $parent_key_item_len == 3 then $parent_key_items[2] else "" end) as $core |
-      (if $parent_key_item_len == 5 then $parent_key_items[2] else "" end) as $collection |
-      (if $parent_key_item_len == 5 then $parent_key_items[3] else "" end) as $shard |
-      (if $parent_key_item_len == 5 then $parent_key_items[4] else "" end) as $replica |
-      (if $parent_key_item_len == 5 then ($collection + "_" + $shard + "_" + $replica) else $core end) as $core |
-      $parent.value | to_entries | .[] | select(.key | startswith("CACHE.searcher.")) | select (.key | endswith("documentCache") or endswith("fieldValueCache") or endswith("filterCache") or endswith("perSegFilter") or endswith("queryResultCache")) as $object |
-      $object.key | split(".")[0] as $category |
-      $object.key | split(".")[2] as $type |
-      $object.value | to_entries | .[] | {KEYSELECTOR} as $target |
-      $target.key as $item |
-      {METRIC} as $value |
-      if $parent_key_item_len == 3 then
-      {
-      name: "solr_metrics_core_searcher_{UNIQUE}",
-      type: "{TYPE}",
-      help: "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-      label_names: ["category", "core", "type", "item"],
-      label_values: [$category, $core, $type, $item],
-      value: $value
-      }
-      else
-      {
-      name: "solr_metrics_core_searcher_{UNIQUE}",
-      type: "{TYPE}",
-      help: "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-      label_names: ["category", "core", "collection", "shard", "replica", "type", "item"],
-      label_values: [$category, $core, $collection, $shard, $replica, $type, $item],
-      value: $value
-      }
-      end      
-    </template>
-    <template name="node-thread-pool" defaultType="COUNTER">
-      .metrics["solr.node"] | to_entries | .[] | select(.key | contains(".threadPool.")) | {KEYSELECTOR} as $object |
-      $object.key | split(".") as $key_items |
-      $key_items | length as $label_len |
-      $key_items[0] as $category |
-      (if $label_len >= 5 then $key_items[1] else "" end) as $handler |
-      (if $label_len >= 5 then $key_items[3] else $key_items[2] end) as $executor |
-      {METRIC} as $value |
-      {
-      name         : "solr_metrics_node_thread_pool_{UNIQUE}",
-      type         : "{TYPE}",
-      help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-      label_names  : ["category", "handler", "executor"],
-      label_values : [$category, $handler, $executor],
-      value        : $value
-      }
-    </template>
-    <template name="jvm-item" defaultType="GAUGE">
-      .metrics["solr.jvm"] | to_entries | .[] | {KEYSELECTOR} as $object |
-      $object.key | split(".") | last as $item |
-      {METRIC} as $value |
-      {
-      name         : "solr_metrics_jvm_{UNIQUE}",
-      type         : "{TYPE}",
-      help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-      label_names  : ["item"],
-      label_values : [$item],
-      value        : $value
-      }
-    </template>
-  </jq-templates>
-
-  <rules>
-
-    <ping>
-      <lst name="request">
-        <lst name="query">
-          <str name="path">/admin/ping</str>
-        </lst>
-        <arr name="jsonQueries">
-          <str>
-            . as $object | $object |
-            (if $object.status == "OK" then 1.0 else 0.0 end) as $value |
-            {
-              name         : "solr_ping",
-              type         : "GAUGE",
-              help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/ping.html",
-              label_names  : [],
-              label_values : [],
-              value        : $value
-            }
-          </str>
-        </arr>
-      </lst>
-    </ping>
-
-    <metrics>
-      <lst name="request">
-        <lst name="query">
-          <str name="path">/admin/metrics</str>
-          <lst name="params">
-            <!--
-              trim some of these expressions as needed if you don't care about
-              a particular group of metrics.
-            -->
-            <str name="expr">solr\.jetty:.*DefaultHandler.*</str>
-            <str name="expr">solr\.jvm:.*</str>
-            <str name="expr">solr\.node:.*</str>
-            <str name="expr">solr\.overseer:.*</str>
-            <str name="expr">solr\.core\..*:QUERY\..*</str>
-            <str name="expr">solr\.core\..*:ADMIN\..*</str>
-            <str name="expr">solr\.core\..*:CACHE\..*</str>
-            <str name="expr">solr\.core\..*:UPDATE\.updateHandler\..*</str>
-            <str name="expr">solr\.core\..*:CORE\.fs\..*</str>
-            <str name="expr">solr\.core\..*:HIGHLIGHTER\..*</str>
-            <str name="expr">solr\.core\..*:INDEX\..*</str>
-            <str name="expr">solr\.core\..*:REPLICATION\./replication\..*</str>
-            <str name="expr">solr\.core\..*:SEARCHER\.searcher\..*</str>
-          </lst>
-        </lst>
-        <arr name="jsonQueries">
-          <!--
-            jetty metrics
-          -->
-          <str>
-            .metrics["solr.jetty"] | to_entries | .[] | select(.key | startswith("org.eclipse.jetty.server.handler.DefaultHandler")) | select(.key | endswith("xx-responses")) as $object |
-            $object.key | split(".") | last | split("-") | first as $status |
-            $object.value.count as $value |
-            {
-            name         : "solr_metrics_jetty_response_total",
-            type         : "COUNTER",
-            help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-            label_names  : ["status"],
-            label_values : [$status],
-            value        : $value
-            }
-          </str>
-          <str>
-            .metrics["solr.jetty"] | to_entries | .[] | select(.key | startswith("org.eclipse.jetty.server.handler.DefaultHandler.")) | select(.key | endswith("-requests")) | select (.value | type == "object") as $object |
-            $object.key | split(".") | last | split("-") | first as $method |
-            $object.value.count as $value |
-            {
-              name         : "solr_metrics_jetty_requests_total",
-              type         : "COUNTER",
-              help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names  : ["method"],
-              label_values : [$method],
-              value        : $value
-            }
-          </str>
-          <str>
-            .metrics["solr.jetty"] | to_entries | .[] | select(.key == "org.eclipse.jetty.server.handler.DefaultHandler.dispatches") as $object |
-            $object.value.count as $value |
-            {
-              name         : "solr_metrics_jetty_dispatches_total",
-              type         : "COUNTER",
-              help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names  : [],
-              label_values : [],
-              value        : $value
-            }
-          </str>
-          <!--
-            jvm metrics
-          -->
-          <str>
-            .metrics["solr.jvm"] | to_entries | .[] | select(.key | startswith("buffers.")) | select(.key | endswith(".Count")) as $object |
-            $object.key | split(".")[1] as $pool |
-            $object.value as $value |
-            {
-              name         : "solr_metrics_jvm_buffers",
-              type         : "GAUGE",
-              help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names  : ["pool"],
-              label_values : [$pool],
-              value        : $value
-            }
-          </str>
-          <str>
-            .metrics["solr.jvm"] | to_entries | .[] | select(.key | startswith("buffers.")) | select(.key | (endswith(".MemoryUsed") or endswith(".TotalCapacity"))) as $object |
-            $object.key | split(".")[1] as $pool |
-            $object.key | split(".") | last as $item |
-            $object.value as $value |
-            {
-              name         : "solr_metrics_jvm_buffers_bytes",
-              type         : "GAUGE",
-              help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names  : ["pool", "item"],
-              label_values : [$pool, $item],
-              value        : $value
-            }
-          </str>
-          <str>
-            .metrics["solr.jvm"] | to_entries | .[] | select(.key | startswith("gc.")) | select(.key | endswith(".count")) as $object |
-            $object.key | split(".")[1] as $item |
-            $object.value as $value |
-            {
-              name         : "solr_metrics_jvm_gc_total",
-              type         : "COUNTER",
-              help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names  : ["item"],
-              label_values : [$item],
-              value        : $value
-            }
-          </str>
-          <str>
-            .metrics["solr.jvm"] | to_entries | .[] | select(.key | startswith("gc.")) | select(.key | endswith(".time")) as $object |
-            $object.key | split(".")[1] as $item |
-            ($object.value / 1000) as $value |
-            {
-              name         : "solr_metrics_jvm_gc_seconds_total",
-              type         : "COUNTER",
-              help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names  : ["item"],
-              label_values : [$item],
-              value        : $value
-            }
-          </str>
-          <str>
-            $jq:jvm-item(memory_heap_bytes,
-                         select(.key | startswith("memory.heap.")) | select(.key | endswith(".usage") | not),
-                         object.value)
-          </str>
-          <str>
-            $jq:jvm-item(memory_non_heap_bytes,
-                         select(.key | startswith("memory.non-heap.")) | select(.key | endswith(".usage") | not),
-                         object.value)
-          </str>
-          <str>
-            .metrics["solr.jvm"] | to_entries | .[] | select(.key | startswith("memory.pools.")) | select(.key | endswith(".usage") | not) as $object |
-            $object.key | split(".")[2] as $space |
-            $object.key | split(".") | last as $item |
-            $object.value as $value |
-            {
-              name         : "solr_metrics_jvm_memory_pools_bytes",
-              type         : "GAUGE",
-              help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names  : ["space", "item"],
-              label_values : [$space, $item],
-              value        : $value
-            }
-          </str>
-          <str>
-            $jq:jvm-item(memory_bytes, select(.key | startswith("memory.total.")), object.value)
-          </str>
-          <str>
-            $jq:jvm-item(os_memory_bytes,
-                         select(.key == "os.committedVirtualMemorySize" or .key == "os.freePhysicalMemorySize" or .key == "os.freeSwapSpaceSize" or .key =="os.totalPhysicalMemorySize" or .key == "os.totalSwapSpaceSize"),
-                         object.value)
-          </str>
-          <str>
-            $jq:jvm-item(os_file_descriptors, select(.key == "os.maxFileDescriptorCount" or .key == "os.openFileDescriptorCount"), object.value)
-          </str>
-          <str>
-            $jq:jvm-item(os_cpu_load, select(.key == "os.processCpuLoad" or .key == "os.systemCpuLoad"), object.value)
-          </str>
-          <str>
-            .metrics["solr.jvm"] | to_entries | .[] | select(.key == "os.processCpuTime") as $object |
-            ($object.value / 1000000000.0 ) as $value |
-            {
-              name         : "solr_metrics_jvm_os_cpu_time_seconds",
-              type         : "COUNTER",
-              help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names  : ["item"],
-              label_values : ["processCpuTime"],
-              value        : $value
-            }
-          </str>
-          <str>
-            .metrics["solr.jvm"] | to_entries | .[] | select(.key == "os.systemLoadAverage") as $object |
-            $object.value as $value |
-            {
-              name         : "solr_metrics_jvm_os_load_average",
-              type         : "GAUGE",
-              help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names  : ["item"],
-              label_values : ["systemLoadAverage"],
-              value        : $value
-            }
-          </str>
-          <str>
-            .metrics["solr.jvm"] | to_entries | .[] | select(.key | startswith("threads.")) | select(.key | endswith(".count")) as $object |
-            $object.key | split(".")[1] as $item |
-            $object.value as $value |
-            {
-              name         : "solr_metrics_jvm_threads",
-              type         : "GAUGE",
-              help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names  : ["item"],
-              label_values : [$item],
-              value        : $value
-            }
-          </str>
-          <!--
-            overseer metrics
-          -->
-          <str>
-            .metrics | to_entries | .[] | select(.key | startswith("solr.overseer")) as $object |
-            $object.value as $value | $value | to_entries | .[]  |
-            select(.key | startswith("queue.") and endswith("collectionWorkQueueSize")) as $object |
-            $object.value as $value |
-            {
-            name         : "solr_metrics_overseer_collectionWorkQueueSize",
-            type         : "GAUGE",
-            help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-            label_names  : [],
-            label_values : [],
-            value        : $value
-            }
-          </str>
-          <str>
-            .metrics | to_entries | .[] | select(.key | startswith("solr.overseer")) as $object |
-            $object.value as $value | $value | to_entries | .[]  |
-            select(.key | startswith("queue.") and endswith("stateUpdateQueueSize")) as $object |
-            $object.value as $value |
-            {
-            name         : "solr_metrics_overseer_stateUpdateQueueSize",
-            type         : "GAUGE",
-            help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-            label_names  : [],
-            label_values : [],
-            value        : $value
-            }
-          </str>
-          <!--
-            node metrics
-          -->
-          <str>
-            $jq:node(client_errors_total, select(.key | endswith(".clientErrors")), count)
-          </str>
-          <str>
-            $jq:node(errors_total, select(.key | endswith(".errors")), count)
-          </str>
-          <str>
-            $jq:node(requests_total, select(.key | endswith(".requestTimes")), count)
-          </str>
-          <str>
-            $jq:node(server_errors_total, select(.key | endswith(".serverErrors")), count)
-          </str>
-          <str>
-            $jq:node(timeouts_total, select(.key | endswith(".timeouts")), count)
-          </str>
-          <str>
-            $jq:node(time_seconds_total, select(.key | endswith(".totalTime")), ($object.value / 1000000000.0))
-          </str>
-          <str>
-            .metrics["solr.node"] | to_entries | .[] | select(.key | startswith("CONTAINER.cores.")) as $object |
-            $object.key | split(".")[0] as $category |
-            $object.key | split(".")[2] as $item |
-            $object.value as $value |
-            {
-              name         : "solr_metrics_node_cores",
-              type         : "GAUGE",
-              help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names  : ["category", "item"],
-              label_values : [$category, $item],
-              value        : $value
-            }
-          </str>
-          <str>
-            .metrics["solr.node"] | to_entries | .[] | select(.key | startswith("CONTAINER.fs.coreRoot.")) | select(.key | endswith(".totalSpace") or endswith(".usableSpace")) as $object |
-            $object.key | split(".") as $key_items |
-            $key_items | length as $label_len |
-            $key_items[0] as $category |
-            $key_items[3] as $item |
-            $object.value as $value |
-            {
-              name         : "solr_metrics_node_core_root_fs_bytes",
-              type         : "GAUGE",
-              help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names  : ["category", "item"],
-              label_values : [$category, $item],
-              value        : $value
-            }
-          </str>
-          <str>
-            $jq:node-thread-pool(completed_total, select(.key | endswith(".completed")) | select(.value | type=="object"), count)
-          </str>
-          <str>
-            $jq:node-thread-pool(running, select(.key | endswith(".running")), object.value, GAUGE)
-          </str>
-          <str>
-            $jq:node-thread-pool(submitted_total, select(.key | endswith(".submitted")), count)
-          </str>
-          <str>
-            .metrics["solr.node"] | to_entries | .[] | select(.key | endswith("Connections")) as $object |
-            $object.key | split(".") as $key_items |
-            $key_items | length as $label_len |
-            $key_items[0] as $category |
-            $key_items[1] as $handler |
-            $key_items[2] as $item |
-            $object.value as $value |
-            {
-              name         : "solr_metrics_node_connections",
-              type         : "GAUGE",
-              help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names  : ["category", "handler", "item"],
-              label_values : [$category, $handler, $item],
-              value        : $value
-            }
-          </str>
-
-          <!--
-          Query related core metrics; see jq-templates for details on the core-query template used below
-          -->
-          <str>
-            $jq:core-query(requests_total, select(.key | endswith(".requestTimes")), count, COUNTER)
-          </str>
-          <str>
-            $jq:core-query(errors_1minRate, select(.key | endswith(".errors")), 1minRate)
-          </str>
-          <str>
-            $jq:core-query(client_errors_1minRate, select(.key | endswith(".clientErrors")), 1minRate)
-          </str>
-          <str>
-            $jq:core-query(1minRate, select(.key | endswith(".requestTimes")), 1minRate)
-          </str>
-          <str>
-            $jq:core-query(5minRate, select(.key | endswith(".requestTimes")), 5minRate)
-          </str>
-          <str>
-            $jq:core-query(median_ms, select(.key | endswith(".requestTimes")), median_ms)
-          </str>
-          <str>
-            $jq:core-query(p75_ms, select(.key | endswith(".requestTimes")), p75_ms)
-          </str>
-          <str>
-            $jq:core-query(p95_ms, select(.key | endswith(".requestTimes")), p95_ms)
-          </str>
-          <str>
-            $jq:core-query(p99_ms, select(.key | endswith(".requestTimes")), p99_ms)
-          </str>
-          <str>
-            $jq:core-query(mean_rate, select(.key | endswith(".requestTimes")), meanRate)
-          </str>
-
-          <!-- core metrics other than query -->
-          <str>
-            $jq:core(client_errors_total, select(.key | endswith(".clientErrors")), count)
-          </str>
-          <str>
-            $jq:core(errors_total, select(.key | endswith(".errors")) | select (.value | type == "object"), count)
-          </str>
-          <str>
-            $jq:core(requests_total, select(.key | endswith(".requestTimes")) | select (.value | type == "object"), count)
-          </str>
-          <str>
-            $jq:core(server_errors_total, select(.key | endswith(".serverErrors")) | select (.value | type == "object"), count)
-          </str>
-          <str>
-            $jq:core(timeouts_total, select(.key | endswith(".timeouts")) | select (.value | type == "object"), count)
-          </str>
-          <str>
-            $jq:core(time_seconds_total, select(.key | endswith(".totalTime")), ($object.value / 1000000000.0))
-          </str>
-          <str>
-            .metrics | to_entries | .[] | select (.key | startswith("solr.core.")) as $parent |
-            $parent.key | split(".") as $parent_key_items |
-            $parent_key_items | length as $parent_key_item_len |
-            (if $parent_key_item_len == 3 then $parent_key_items[2] else "" end) as $core |
-            (if $parent_key_item_len == 5 then $parent_key_items[2] else "" end) as $collection |
-            (if $parent_key_item_len == 5 then $parent_key_items[3] else "" end) as $shard |
-            (if $parent_key_item_len == 5 then $parent_key_items[4] else "" end) as $replica |
-            (if $parent_key_item_len == 5 then ($collection + "_" + $shard + "_" + $replica) else $core end) as $core |
-            $parent.value | to_entries | .[] | select(.key == "CACHE.core.fieldCache") as $object |
-            $object.key | split(".")[0] as $category |
-            $object.value.entries_count as $value |
-            if $parent_key_item_len == 3 then
-            {
-            name: "solr_metrics_core_field_cache_total",
-            type: "COUNTER",
-            help: "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-            label_names: ["category", "core"],
-            label_values: [$category, $core],
-            value: $value
-            }
-            else
-            {
-            name: "solr_metrics_core_field_cache_total",
-            type: "COUNTER",
-            help: "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-            label_names: ["category", "core", "collection", "shard", "replica"],
-            label_values: [$category, $core, $collection, $shard, $replica],
-            value: $value
-            }
-            end
-          </str>
-          <str>
-            $jq:update-handler(update_handler_adds, select(.key == "UPDATE.updateHandler.adds"), object.value, GAUGE)
-          </str>
-          <str>
-            $jq:update-handler(update_handler_auto_commits_total, select(.key == "UPDATE.updateHandler.autoCommits"), object.value)
-          </str>
-          <str>
-            $jq:update-handler(update_handler_commits_total, select(.key == "UPDATE.updateHandler.commits"), count)
-          </str>
-          <str>
-            $jq:update-handler(update_handler_adds_total, select(.key == "UPDATE.updateHandler.cumulativeAdds"), count)
-          </str>
-          <str>
-            $jq:update-handler(update_handler_deletes_by_id_total, select(.key == "UPDATE.updateHandler.cumulativeDeletesById"), count)
-          </str>
-          <str>
-            $jq:update-handler(update_handler_deletes_by_query_total, select(.key == "UPDATE.updateHandler.cumulativeDeletesByQuery"), count)
-          </str>
-          <str>
-            $jq:update-handler(update_handler_errors_total, select(.key == "UPDATE.updateHandler.cumulativeErrors"), count)
-          </str>
-          <str>
-            $jq:update-handler(update_handler_deletes_by_id, select(.key == "UPDATE.updateHandler.deletesById"), object.value, GAUGE)
-          </str>
-          <str>
-            $jq:update-handler(update_handler_deletes_by_query, select(.key == "UPDATE.updateHandler.deletesByQuery"), object.value, GAUGE)
-          </str>
-          <str>
-            $jq:update-handler(update_handler_pending_docs, select(.key == "UPDATE.updateHandler.docsPending"), object.value, GAUGE)
-          </str>
-          <str>
-            $jq:update-handler(update_handler_errors, select(.key == "UPDATE.updateHandler.errors"), object.value, GAUGE)
-          </str>
-          <str>
-            $jq:update-handler(update_handler_expunge_deletes_total, select(.key == "UPDATE.updateHandler.expungeDeletes"), count)
-          </str>
-          <str>
-            $jq:update-handler(update_handler_merges_total, select(.key == "UPDATE.updateHandler.merges"), count)
-          </str>
-          <str>
-            $jq:update-handler(update_handler_optimizes_total, select(.key == "UPDATE.updateHandler.optimizes"), count)
-          </str>
-          <str>
-            $jq:update-handler(update_handler_rollbacks_total, select(.key == "UPDATE.updateHandler.rollbacks"), count)
-          </str>
-          <str>
-            $jq:update-handler(update_handler_soft_auto_commits_total, select(.key == "UPDATE.updateHandler.softAutoCommits"), object.value)
-          </str>
-          <str>
-            $jq:update-handler(update_handler_splits_total, select(.key == "UPDATE.updateHandler.splits"), count)
-          </str>
-
-          <str>
-            $jq:cache-searcher(cache, select(.key == "lookups" or .key == "hits" or .key == "size" or .key == "evictions" or .key == "inserts"), $target.value)
-          </str>
-          <str>
-            $jq:cache-searcher(cache_ratio, select(.key == "hitratio"), $target.value)
-          </str>
-          <str>
-            $jq:cache-searcher(warmup_time_seconds, select(.key == "warmupTime"), ($target.value / 1000))
-          </str>
-          <str>
-            $jq:cache-searcher(cumulative_cache_total,
-                               select(.key == "cumulative_lookups" or .key == "cumulative_hits" or .key == "cumulative_evictions" or .key == "cumulative_inserts"),
-                               $target.value,
-                               COUNTER)
-          </str>
-          <str>
-            $jq:cache-searcher(cumulative_cache_ratio, select(.key == "cumulative_hitratio"), $target.value)
-          </str>
-          <str>
-            .metrics | to_entries | .[] | select(.key | startswith("solr.core.")) as $parent |
-            $parent.key | split(".") as $parent_key_items |
-            $parent_key_items | length as $parent_key_item_len |
-            (if $parent_key_item_len == 3 then $parent_key_items[2] else "" end) as $core |
-            (if $parent_key_item_len == 5 then $parent_key_items[2] else "" end) as $collection |
-            (if $parent_key_item_len == 5 then $parent_key_items[3] else "" end) as $shard |
-            (if $parent_key_item_len == 5 then $parent_key_items[4] else "" end) as $replica |
-            (if $parent_key_item_len == 5 then ($collection + "_" + $shard + "_" + $replica) else $core end) as $core |
-            $parent.value | to_entries | .[] | select(.key | startswith("CORE.fs.")) | select (.key | endswith(".totalSpace") or endswith(".usableSpace")) as $object |
-            $object.key | split(".")[0] as $category |
-            $object.key | split(".")[2] as $item |
-            $object.value as $value |
-            if $parent_key_item_len == 3 then
-            {
-              name: "solr_metrics_core_fs_bytes",
-              type: "GAUGE",
-              help: "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names: ["category", "core", "item"],
-              label_values: [$category, $core, $item],
-              value: $value
-            }
-            else
-            {
-              name: "solr_metrics_core_fs_bytes",
-              type: "GAUGE",
-              help: "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names: ["category", "core", "collection", "shard", "replica", "item"],
-              label_values: [$category, $core, $collection, $shard, $replica, $item],
-              value: $value
-            }
-            end
-          </str>
-          <str>
-            .metrics | to_entries | .[] | select(.key | startswith("solr.core.")) as $parent |
-            $parent.key | split(".") as $parent_key_items |
-            $parent_key_items | length as $parent_key_item_len |
-            (if $parent_key_item_len == 3 then $parent_key_items[2] else "" end) as $core |
-            (if $parent_key_item_len == 5 then $parent_key_items[2] else "" end) as $collection |
-            (if $parent_key_item_len == 5 then $parent_key_items[3] else "" end) as $shard |
-            (if $parent_key_item_len == 5 then $parent_key_items[4] else "" end) as $replica |
-            (if $parent_key_item_len == 5 then ($collection + "_" + $shard + "_" + $replica) else $core end) as $core |
-            $parent.value | to_entries | .[] | select(.key | startswith("HIGHLIGHTER.")) | select (.key | endswith(".requests")) as $object |
-            $object.key | split(".")[0] as $category |
-            $object.key | split(".")[1] as $name |
-            $object.key | split(".")[2] as $item |
-            $object.value as $value |
-            if $parent_key_item_len == 3 then
-            {
-              name: "solr_metrics_core_highlighter_request_total",
-              type: "COUNTER",
-              help: "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names: ["category", "core", "name", "item"],
-              label_values: [$category, $core, $name, $item],
-              value: $value
-            }
-            else
-            {
-              name: "solr_metrics_core_highlighter_request_total",
-              type: "COUNTER",
-              help: "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names: ["category", "core", "collection", "shard", "replica", "name", "item"],
-              label_values: [$category, $core, $collection, $shard, $replica, $name, $item],
-              value: $value
-            }
-            end
-          </str>
-          <str>
-            .metrics | to_entries | .[] | select(.key | startswith("solr.core.")) as $parent |
-            $parent.key | split(".") as $parent_key_items |
-            $parent_key_items | length as $parent_key_item_len |
-            (if $parent_key_item_len == 3 then $parent_key_items[2] else "" end) as $core |
-            (if $parent_key_item_len == 5 then $parent_key_items[2] else "" end) as $collection |
-            (if $parent_key_item_len == 5 then $parent_key_items[3] else "" end) as $shard |
-            (if $parent_key_item_len == 5 then $parent_key_items[4] else "" end) as $replica |
-            (if $parent_key_item_len == 5 then ($collection + "_" + $shard + "_" + $replica) else $core end) as $core |
-            $parent.value | to_entries | .[] | select(.key == "INDEX.sizeInBytes") as $object |
-            $object.key | split(".")[0] as $category |
-            $object.value as $value |
-            if $parent_key_item_len == 3 then
-            {
-              name: "solr_metrics_core_index_size_bytes",
-              type: "GAUGE",
-              help: "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names: ["category", "core"],
-              label_values: [$category, $core],
-              value: $value
-            }
-            else
-            {
-              name: "solr_metrics_core_index_size_bytes",
-              type: "GAUGE",
-              help: "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names: ["category", "core", "collection", "shard", "replica"],
-              label_values: [$category, $core, $collection, $shard, $replica],
-              value: $value
-            }
-            end
-          </str>
-          <str>
-            .metrics | to_entries | .[] | select(.key | startswith("solr.core.")) as $parent |
-            $parent.key | split(".") as $parent_key_items |
-            $parent_key_items | length as $parent_key_item_len |
-            (if $parent_key_item_len == 3 then $parent_key_items[2] else "" end) as $core |
-            (if $parent_key_item_len == 5 then $parent_key_items[2] else "" end) as $collection |
-            (if $parent_key_item_len == 5 then $parent_key_items[3] else "" end) as $shard |
-            (if $parent_key_item_len == 5 then $parent_key_items[4] else "" end) as $replica |
-            (if $parent_key_item_len == 5 then ($collection + "_" + $shard + "_" + $replica) else $core end) as $core |
-            $parent.value | to_entries | .[] | select(.key == "REPLICATION./replication.isLeader") as $object |
-            $object.key | split(".")[0] as $category |
-            $object.key | split(".")[1] as $handler |
-            (if $object.value == true then 1.0 else 0.0 end) as $value |
-            if $parent_key_item_len == 3 then
-            {
-              name: "solr_metrics_core_replication_leader",
-              type: "GAUGE",
-              help: "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names: ["category", "handler", "core"],
-              label_values: [$category, $handler, $core],
-              value: $value
-            }
-            else
-            {
-              name: "solr_metrics_core_replication_leader",
-              type: "GAUGE",
-              help: "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names: ["category", "handler", "core", "collection", "shard", "replica"],
-              label_values: [$category, $handler, $core, $collection, $shard, $replica],
-              value: $value
-            }
-            end
-          </str>
-          <str>
-            .metrics | to_entries | .[] | select(.key | startswith("solr.core.")) as $parent |
-            $parent.key | split(".") as $parent_key_items |
-            $parent_key_items | length as $parent_key_item_len |
-            (if $parent_key_item_len == 3 then $parent_key_items[2] else "" end) as $core |
-            (if $parent_key_item_len == 5 then $parent_key_items[2] else "" end) as $collection |
-            (if $parent_key_item_len == 5 then $parent_key_items[3] else "" end) as $shard |
-            (if $parent_key_item_len == 5 then $parent_key_items[4] else "" end) as $replica |
-            (if $parent_key_item_len == 5 then ($collection + "_" + $shard + "_" + $replica) else $core end) as $core |
-            $parent.value | to_entries | .[] | select(.key == "REPLICATION./replication.isFollower") as $object |
-            $object.key | split(".")[0] as $category |
-            $object.key | split(".")[1] as $handler |
-            (if $object.value == true then 1.0 else 0.0 end) as $value |
-            if $parent_key_item_len == 3 then
-            {
-              name: "solr_metrics_core_replication_follower",
-              type: "GAUGE",
-              help: "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names: ["category", "handler", "core"],
-              label_values: [$category, $handler, $core],
-              value: $value
-            }
-            else
-            {
-              name: "solr_metrics_core_replication_follower",
-              type: "GAUGE",
-              help: "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names: ["category", "handler", "core", "collection", "shard", "replica"],
-              label_values: [$category, $handler, $core, $collection, $shard, $replica],
-              value: $value
-            }
-            end
-          </str>
-          <str>
-            .metrics | to_entries | .[] | select(.key | startswith("solr.core.")) as $parent |
-            $parent.key | split(".") as $parent_key_items |
-            $parent_key_items | length as $parent_key_item_len |
-            (if $parent_key_item_len == 3 then $parent_key_items[2] else "" end) as $core |
-            (if $parent_key_item_len == 5 then $parent_key_items[2] else "" end) as $collection |
-            (if $parent_key_item_len == 5 then $parent_key_items[3] else "" end) as $shard |
-            (if $parent_key_item_len == 5 then $parent_key_items[4] else "" end) as $replica |
-            (if $parent_key_item_len == 5 then ($collection + "_" + $shard + "_" + $replica) else $core end) as $core |
-            $parent.value | to_entries | .[] | select(.key == "SEARCHER.searcher.deletedDocs" or .key == "SEARCHER.searcher.maxDoc" or .key == "SEARCHER.searcher.numDocs") as $object |
-            $object.key | split(".")[0] as $category |
-            $object.key | split(".")[2] as $item |
-            $object.value as $value |
-            if $parent_key_item_len == 3 then
-            {
-              name: "solr_metrics_core_searcher_documents",
-              type: "GAUGE",
-              help: "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names: ["category", "core", "item"],
-              label_values: [$category, $core, $item],
-              value: $value
-            }
-            else
-            {
-              name: "solr_metrics_core_searcher_documents",
-              type: "GAUGE",
-              help: "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/metrics-reporting.html",
-              label_names: ["category", "core", "collection", "shard", "replica", "item"],
-              label_values: [$category, $core, $collection, $shard, $replica, $item],
-              value: $value
-            }
-            end
-          </str>
-        </arr>
-      </lst>
-    </metrics>
-
-    <collections>
-      <lst name="request">
-        <lst name="query">
-          <str name="path">/admin/collections</str>
-          <lst name="params">
-            <str name="action">CLUSTERSTATUS</str>
-          </lst>
-        </lst>
-        <arr name="jsonQueries">
-          <str>
-            .cluster.live_nodes | length as $value|
-            {
-              name         : "solr_collections_live_nodes",
-              type         : "GAUGE",
-              help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/cluster-node-management.html#clusterstatus",
-              label_names  : [],
-              label_values : [],
-              value        : $value
-            }
-          </str>
-          <str>
-            .cluster.collections | to_entries | .[] | . as $object |
-            $object.key as $collection |
-            $object.value.pullReplicas | tonumber as $value |
-            {
-              name         : "solr_collections_pull_replicas",
-              type         : "GAUGE",
-              help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/cluster-node-management.html#clusterstatus",
-              label_names  : ["collection"],
-              label_values : [$collection],
-              value        : $value
-            }
-          </str>
-          <str>
-            .cluster.collections | to_entries | .[] | . as $object |
-            $object.key as $collection |
-            $object.value.nrtReplicas | tonumber as $value |
-            {
-              name         : "solr_collections_nrt_replicas",
-              type         : "GAUGE",
-              help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/cluster-node-management.html#clusterstatus",
-              label_names  : ["collection"],
-              label_values : [$collection],
-              value        : $value
-            }
-          </str>
-          <str>
-            .cluster.collections | to_entries | .[] | . as $object |
-            $object.key as $collection |
-            $object.value.tlogReplicas | tonumber as $value |
-            {
-              name         : "solr_collections_tlog_replicas",
-              type         : "GAUGE",
-              help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/cluster-node-management.html#clusterstatus",
-              label_names  : ["collection"],
-              label_values : [$collection],
-              value        : $value
-            }
-          </str>
-          <str>
-            .cluster.collections | to_entries | .[] | . as $object |
-            $object.key as $collection |
-            $object.value.shards | to_entries | .[] | . as $shard_obj |
-            $shard_obj.key as $shard |
-            (if $shard_obj.value.state == "active" then 1.0 else 0.0 end) as $value |
-            {
-              name         : "solr_collections_shard_state",
-              type         : "GAUGE",
-              help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/cluster-node-management.html#clusterstatus",
-              label_names  : ["collection","shard"],
-              label_values : [$collection,$shard],
-              value        : $value
-            }
-          </str>
-          <str>
-            .cluster.collections | to_entries | .[] | . as $object |
-            $object.key as $collection |
-            $object.value.shards | to_entries | .[] | . as $shard_obj |
-            $shard_obj.key as $shard |
-            $shard_obj.value.replicas | to_entries | .[] | . as $replica_obj |
-            $replica_obj.key as $replica_name |
-            $replica_obj.value.core as $core |
-            $core[$collection + "_" + $shard + "_" | length:] as $replica |
-            $replica_obj.value.base_url as $base_url |
-            $replica_obj.value.node_name as $node_name |
-            $replica_obj.value.type as $type |
-            $replica_obj.value.state as $state |
-            (if $state == "active" then 1.0 else 0.0 end) as $value |
-            {
-              name         : "solr_collections_replica_state",
-              type         : "GAUGE",
-              help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/cluster-node-management.html#clusterstatus",
-              label_names  : ["collection", "shard", "replica", "replica_name", "core", "base_url", "node_name", "type", "state"],
-              label_values : [$collection, $shard, $replica, $replica_name, $core, $base_url, $node_name, $type, $state],
-              value        : $value
-            }
-          </str>
-
-          <str>
-            .cluster.collections | to_entries | .[] | . as $object |
-            $object.key as $collection |
-            $object.value.shards | to_entries | .[] | . as $shard_obj |
-            $shard_obj.key as $shard |
-            $shard_obj.value.replicas | to_entries | .[] | . as $replica_obj |
-            $replica_obj.key as $replica_name |
-            $replica_obj.value.core as $core |
-            $core[$collection + "_" + $shard + "_" | length:] as $replica |
-            $replica_obj.value.base_url as $base_url |
-            $replica_obj.value.node_name as $node_name |
-            $replica_obj.value.type as $type |
-            (if $replica_obj.value.leader == "true" then 1.0 else 0.0 end) as $value |
-            {
-              name         : "solr_collections_shard_leader",
-              type         : "GAUGE",
-              help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/cluster-node-management.html#clusterstatus",
-              label_names  : ["collection", "shard", "replica", "replica_name", "core", "base_url", "node_name", "type"],
-              label_values : [$collection, $shard, $replica, $replica_name, $core, $base_url, $node_name, $type],
-              value        : $value
-            }
-          </str>
-        </arr>
-      </lst>
-      <lst name="request">
-        <lst name="query">
-          <str name="path">/admin/zookeeper/status</str>
-        </lst>
-        <arr name="jsonQueries">
-          <str>
-            .zkStatus.ensembleSize as $value |
-            .zkStatus.mode as $mode |
-            {
-            name         : "solr_zookeeper_ensemble_size",
-            type         : "GAUGE",
-            help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/cloud-screens.html#zk-status-view",
-            label_names  : [],
-            label_values : [],
-            value        : $value
-            }
-          </str>
-          <str>
-            .zkStatus.details[] as $object |
-            $object.host as $host |
-            $object.ok as $ok |
-            (if $object.clientPort != null and $ok then 1.0 else 0.0 end) as $value |
-            {
-            name         : "solr_zookeeper_nodestatus",
-            type         : "GAUGE",
-            help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/cloud-screens.html#zk-status-view",
-            label_names  : ["host"],
-            label_values : [$host],
-            value        : $value
-            }
-          </str>
-          <str>
-            .zkStatus.status as $statusText |
-            (if $statusText == "green" then 1.0 else 0.0 end) as $value |
-            {
-            name         : "solr_zookeeper_status",
-            type         : "GAUGE",
-            help         : "See following URL: https://solr.apache.org/guide/solr/latest/deployment-guide/cloud-screens.html#zk-status-view",
-            label_names  : ["status"],
-            label_values : [$statusText],
-            value        : $value
-            }
-          </str>
-        </arr>
-      </lst>
-    </collections>
-
-    <!--
-    <search>
-      <lst name="request">
-        <lst name="query">
-          <str name="collection">collection1</str>
-          <str name="path">/select</str>
-          <lst name="params">
-            <str name="q">*:*</str>
-            <str name="start">0</str>
-            <str name="rows">0</str>
-            <str name="json.facet">
-              {
-                category: {
-                  type: terms,
-                  field: cat
-                }
-              }
-            </str>
-          </lst>
-        </lst>
-        <arr name="jsonQueries">
-          <str>
-            .facets.category.buckets[] as $object |
-            $object.val as $term |
-            $object.count as $value |
-            {
-              name         : "solr_facets_category",
-              type         : "GAUGE",
-              help         : "Category facets",
-              label_names  : ["term"],
-              label_values : [$term],
-              value        : $value
-            }
-          </str>
-        </arr>
-      </lst>
-    </search>
-    -->
-
-  </rules>
-
-</config>
diff --git a/solr/prometheus-exporter/src/java/org/apache/solr/prometheus/collector/CollectionsCollector.java b/solr/prometheus-exporter/src/java/org/apache/solr/prometheus/collector/CollectionsCollector.java
deleted file mode 100644
index c6262b784c7..00000000000
--- a/solr/prometheus-exporter/src/java/org/apache/solr/prometheus/collector/CollectionsCollector.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.solr.prometheus.collector;
-
-import org.apache.solr.prometheus.exporter.MetricsQuery;
-import org.apache.solr.prometheus.scraper.SolrScraper;
-
-public class CollectionsCollector implements MetricCollector {
-
-  private final SolrScraper solrClient;
-  private final MetricsQuery metricsQuery;
-
-  public CollectionsCollector(SolrScraper solrClient, MetricsQuery metricsQuery) {
-    this.solrClient = solrClient;
-    this.metricsQuery = metricsQuery;
-  }
-
-  @Override
-  public MetricSamples collect() throws Exception {
-    return solrClient.collections(metricsQuery);
-  }
-}
diff --git a/solr/prometheus-exporter/src/java/org/apache/solr/prometheus/collector/MetricCollector.java b/solr/prometheus-exporter/src/java/org/apache/solr/prometheus/collector/MetricCollector.java
deleted file mode 100644
index 469f58837fd..00000000000
--- a/solr/prometheus-exporter/src/java/org/apache/solr/prometheus/collector/MetricCollector.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.solr.prometheus.collector;
-
-public interface MetricCollector {
-  MetricSamples collect() throws Exception;
-}
diff --git a/solr/prometheus-exporter/src/java/org/apache/solr/prometheus/collector/MetricSamples.java b/solr/prometheus-exporter/src/java/org/apache/solr/prometheus/collector/MetricSamples.java
deleted file mode 100644
index 328d76d054c..00000000000
--- a/solr/prometheus-exporter/src/java/org/apache/solr/prometheus/collector/MetricSamples.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.solr.prometheus.collector;
-
-import io.prometheus.client.Collector;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-public class MetricSamples {
-
-  private final Map<String, Collector.MetricFamilySamples> samplesByMetricName;
-
-  private final Set<Collector.MetricFamilySamples.Sample> seenSamples;
-
-  public MetricSamples(Map<String, Collector.MetricFamilySamples> input) {
-    samplesByMetricName = input;
-    seenSamples = new HashSet<>();
-    for (Collector.MetricFamilySamples metricFamilySamples : input.values()) {
-      seenSamples.addAll(metricFamilySamples.samples);
-    }
-  }
-
-  public MetricSamples() {
-    this(new HashMap<>());
-  }
-
-  public void addSamplesIfNotPresent(
-      String metricName, Collector.MetricFamilySamples metricFamilySamples) {
-    if (samplesByMetricName.putIfAbsent(metricName, metricFamilySamples) == null) {
-      seenSamples.addAll(metricFamilySamples.samples);
-    }
-  }
-
-  public void addSampleIfMetricExists(
-      String metricName, Collector.MetricFamilySamples.Sample sample) {
-    Collector.MetricFamilySamples sampleFamily = samplesByMetricName.get(metricName);
-
-    if (sampleFamily == null) {
-      return;
-    }
-
-    if (seenSamples.add(sample)) {
-      sampleFamily.samples.add(sample);
-    }
-  }
-
-  public void addAll(MetricSamples other) {
-    for (Map.Entry<String, Collector.MetricFamilySamples> entry :
-        other.samplesByMetricName.entrySet()) {
-      String key = entry.getKey();
-      if (this.samplesByMetricName.containsKey(key)) {
-        for (Collector.MetricFamilySamples.Sample sample : entry.getValue().samples) {
-          addSampleIfMetricExists(key, sample);
-        }
-      } else {
-        this.samplesByMetricName.put(key, entry.getValue());
-        seenSamples.addAll(entry.getValue().samples);
-      }
-    }
-  }
-
-  public List<Collector.MetricFamilySamples> asList() {
-    return samplesByMetricName.values().stream()
-        .filter(value -> !value.samples.isEmpty())
-        .collect(Collectors.toList());
-  }
-}
diff --git a/solr/prometheus-exporter/src/java/org/apache/solr/prometheus/collector/MetricsCollector.java b/solr/prometheus-exporter/src/java/org/apache/solr/prometheus/collector/MetricsCollector.java
deleted file mode 100644
index c96b70c9b73..00000000000
--- a/solr/prometheus-exporter/src/java/org/apache/solr/prometheus/collector/MetricsCollector.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.solr.prometheus.collector;
-
-import org.apache.solr.prometheus.exporter.MetricsQuery;
-import org.apache.solr.prometheus.scraper.SolrScraper;
-
-public class MetricsCollector implements MetricCollector {
-
-  private final MetricsQuery metricsQuery;
-  private final SolrScraper solrClient;
-
-  public MetricsCollector(SolrScraper solrClient, MetricsQuery metricsQuery) {
-    this.solrClient = solrClient;
-    this.metricsQuery = metricsQuery;
-  }
-
-  @Override
-  public MetricSamples collect() throws Exception {
-    MetricSamples results = new MetricSamples();
-
-    solrClient.metricsForAllHosts(metricsQuery).forEach((host, samples) -> results.addAll(samples));
-
-    return results;
-  }
-}
diff --git a/solr/prometheus-exporter/src/java/org/apache/solr/prometheus/collector/MetricsCollectorFactory.java b/solr/prometheus-exporter/src/java/org/apache/solr/prometheus/collector/MetricsCollectorFactory.java
deleted file mode 100644
index bbb2dd7ca8c..00000000000
--- a/solr/prometheus-exporter/src/java/org/apache/solr/prometheus/collector/MetricsCollectorFactory.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.solr.prometheus.collector;
-
-import java.util.List;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import org.apache.solr.prometheus.exporter.MetricsConfiguration;
-import org.apache.solr.prometheus.scraper.SolrScraper;
-
-public class MetricsCollectorFactory {
-
-  private final MetricsConfiguration metricsConfiguration;
-  private final ExecutorService executor;
-  private final int refreshInSeconds;
-  private final SolrScraper solrScraper;
-
-  public MetricsCollectorFactory(
... 6136 lines suppressed ...


(solr) 20/33: SOLR-17854: Support filters to /admin/metrics endpoint for Prometheus metrics (#3499)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 046b206de34417bb672b335fe607d90dda586e39
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Fri Sep 5 12:57:31 2025 -0400

    SOLR-17854: Support filters to /admin/metrics endpoint for Prometheus metrics (#3499)
    
    * Add fitlering for /admin/metrics
    
    * Rename vars
    
    * Use a generic method for handling the 3 types metric types
    
    * Optimize requiredLabelsFilter
    
    * Add tests for MetricsHandlerTest
    
    * Add tests around FilterablePrometheusMetricReader
    
    * static member
    
    * Fix tests
    
    ---------
    
    Co-authored-by: David Smiley <ds...@apache.org>
    Co-authored-by: Matthew Biscocho <mb...@bloomberg.net>
---
 .../src/java/org/apache/solr/core/SolrCore.java    |  15 +-
 .../apache/solr/handler/admin/MetricsHandler.java  | 159 ++++-
 .../org/apache/solr/metrics/SolrMetricManager.java |  12 +-
 .../otel/FilterablePrometheusMetricReader.java     | 185 +++++
 .../solr/response/PrometheusResponseWriter.java    | 113 +---
 .../solr/handler/admin/MetricsHandlerTest.java     | 751 ++++-----------------
 .../otel/FilterablePrometheusMetricReaderTest.java |  95 +++
 .../apache/solr/update/SolrIndexMetricsTest.java   |   2 +-
 8 files changed, 586 insertions(+), 746 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/core/SolrCore.java b/solr/core/src/java/org/apache/solr/core/SolrCore.java
index ab4db7d04f2..a8cb4800fde 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrCore.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java
@@ -1422,14 +1422,13 @@ public class SolrCore implements SolrInfoBean, Closeable {
             }),
             OtelUnit.BYTES));
 
-    observables.add(
-        parentContext.observableLongGauge(
-            "solr_core_segment_count",
-            "Number of segments in a Solr core",
-            (observableLongMeasurement -> {
-              if (isReady())
-                observableLongMeasurement.record(getSegmentCount(), baseGaugeCoreAttributes);
-            })));
+    parentContext.observableLongGauge(
+        "solr_core_segments",
+        "Number of segments in a Solr core",
+        (observableLongMeasurement -> {
+          if (isReady())
+            observableLongMeasurement.record(getSegmentCount(), baseGaugeCoreAttributes);
+        }));
 
     // NOCOMMIT: Do we need these start_time metrics? I think at minimum it should be optional
     // otherwise we fall into metric bloat for something people may not care about.
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java
index 633ccc7bb00..143cf453f41 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java
@@ -25,14 +25,23 @@ import com.codahale.metrics.Metric;
 import com.codahale.metrics.MetricFilter;
 import com.codahale.metrics.MetricRegistry;
 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.HistogramSnapshot;
+import io.prometheus.metrics.model.snapshots.InfoSnapshot;
+import io.prometheus.metrics.model.snapshots.MetricSnapshot;
+import io.prometheus.metrics.model.snapshots.MetricSnapshots;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumSet;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
 import java.util.TreeSet;
 import java.util.function.BiConsumer;
 import java.util.function.Predicate;
@@ -49,6 +58,7 @@ import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.metrics.otel.FilterablePrometheusMetricReader;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestInfo;
 import org.apache.solr.response.SolrQueryResponse;
@@ -69,6 +79,16 @@ public class MetricsHandler extends RequestHandlerBase implements PermissionName
   public static final String KEY_PARAM = "key";
   public static final String EXPR_PARAM = "expr";
   public static final String TYPE_PARAM = "type";
+  // Prometheus filtering parameters
+  public static final String CATEGORY_PARAM = "category";
+  public static final String CORE_PARAM = "core";
+  public static final String COLLECTION_PARAM = "collection";
+  public static final String SHARD_PARAM = "shard";
+  public static final String REPLICA_PARAM = "replica";
+  public static final String METRIC_NAME_PARAM = "name";
+  private static final Set<String> labelFilterKeys =
+      Set.of(CATEGORY_PARAM, CORE_PARAM, COLLECTION_PARAM, SHARD_PARAM, REPLICA_PARAM);
+
   // NOCOMMIT: This wt=prometheus will be removed as it will become the default for /admin/metrics
   public static final String PROMETHEUS_METRICS_WT = "prometheus";
   public static final String OPEN_METRICS_WT = "openmetrics";
@@ -131,7 +151,7 @@ public class MetricsHandler extends RequestHandlerBase implements PermissionName
     // NOCOMMIT SOLR-17458: Make this the default option after dropwizard removal
     if (PROMETHEUS_METRICS_WT.equals(params.get(CommonParams.WT))
         || OPEN_METRICS_WT.equals(params.get(CommonParams.WT))) {
-      consumer.accept("metrics", metricManager.getPrometheusMetricReaders());
+      handlePrometheusRequest(params, consumer);
       return;
     }
 
@@ -151,6 +171,58 @@ public class MetricsHandler extends RequestHandlerBase implements PermissionName
     consumer.accept("metrics", response);
   }
 
+  private void handlePrometheusRequest(SolrParams params, BiConsumer<String, Object> consumer) {
+    Set<String> metricNames = readParamsAsSet(params, METRIC_NAME_PARAM);
+    SortedMap<String, Set<String>> labelFilters = labelFilters(params);
+
+    if (metricNames.isEmpty() && labelFilters.isEmpty()) {
+      consumer.accept(
+          "metrics",
+          mergeSnapshots(
+              metricManager.getPrometheusMetricReaders().values().stream()
+                  .flatMap(r -> r.collect().stream())
+                  .toList()));
+      return;
+    }
+
+    List<MetricSnapshot> allSnapshots = new ArrayList<>();
+    for (FilterablePrometheusMetricReader reader :
+        metricManager.getPrometheusMetricReaders().values()) {
+      MetricSnapshots filteredSnapshots = reader.collect(metricNames, labelFilters);
+      filteredSnapshots.forEach(allSnapshots::add);
+    }
+
+    // Merge all filtered snapshots and return the merged result
+    MetricSnapshots mergedSnapshots = mergeSnapshots(allSnapshots);
+    consumer.accept("metrics", mergedSnapshots);
+  }
+
+  private SortedMap<String, Set<String>> labelFilters(SolrParams params) {
+    SortedMap<String, Set<String>> labelFilters = new TreeMap<>();
+    labelFilterKeys.forEach(
+        (paramName) -> {
+          Set<String> filterValues = readParamsAsSet(params, paramName);
+          if (!filterValues.isEmpty()) {
+            labelFilters.put(paramName, filterValues);
+          }
+        });
+
+    return labelFilters;
+  }
+
+  private Set<String> readParamsAsSet(SolrParams params, String paramName) {
+    String[] paramValues = params.getParams(paramName);
+    if (paramValues == null || paramValues.length == 0) {
+      return Set.of();
+    }
+
+    List<String> paramSet = new ArrayList<>();
+    for (String param : paramValues) {
+      paramSet.addAll(StrUtils.splitSmart(param, ','));
+    }
+    return Set.copyOf(paramSet);
+  }
+
   private NamedList<Object> handleDropwizardRegistry(SolrParams params) {
     boolean compact = params.getBool(COMPACT_PARAM, true);
     MetricFilter mustMatchFilter = parseMustMatchFilter(params);
@@ -182,6 +254,7 @@ public class MetricsHandler extends RequestHandlerBase implements PermissionName
     return response;
   }
 
+  // NOCOMMIT: Remove this filtering logic
   private static class MetricsExpr {
     Pattern registryRegex;
     MetricFilter metricFilter;
@@ -499,9 +572,87 @@ public class MetricsHandler extends RequestHandlerBase implements PermissionName
     return metricTypes;
   }
 
-  private String getCoreNameFromRegistry(String registryName) {
-    String coreName = registryName.substring(registryName.indexOf('.') + 1);
-    return coreName.replace(".", "_");
+  /**
+   * Merge a collection of individual {@link MetricSnapshot} instances into one {@link
+   * MetricSnapshots}. This is necessary because we create a {@link
+   * io.opentelemetry.sdk.metrics.SdkMeterProvider} per Solr core resulting in duplicate metric
+   * names across cores which is an illegal format if under the same prometheus grouping.
+   */
+  private MetricSnapshots mergeSnapshots(List<MetricSnapshot> snapshots) {
+    Map<String, CounterSnapshot.Builder> counterSnapshotMap = new HashMap<>();
+    Map<String, GaugeSnapshot.Builder> gaugeSnapshotMap = new HashMap<>();
+    Map<String, HistogramSnapshot.Builder> histogramSnapshotMap = new HashMap<>();
+    InfoSnapshot otelInfoSnapshots = null;
+
+    for (MetricSnapshot snapshot : snapshots) {
+      String metricName = snapshot.getMetadata().getPrometheusName();
+
+      switch (snapshot) {
+        case CounterSnapshot counterSnapshot -> {
+          CounterSnapshot.Builder builder =
+              counterSnapshotMap.computeIfAbsent(
+                  metricName,
+                  k -> {
+                    var base =
+                        CounterSnapshot.builder()
+                            .name(counterSnapshot.getMetadata().getName())
+                            .help(counterSnapshot.getMetadata().getHelp());
+                    return counterSnapshot.getMetadata().hasUnit()
+                        ? base.unit(counterSnapshot.getMetadata().getUnit())
+                        : base;
+                  });
+          counterSnapshot.getDataPoints().forEach(builder::dataPoint);
+        }
+        case GaugeSnapshot gaugeSnapshot -> {
+          GaugeSnapshot.Builder builder =
+              gaugeSnapshotMap.computeIfAbsent(
+                  metricName,
+                  k -> {
+                    var base =
+                        GaugeSnapshot.builder()
+                            .name(gaugeSnapshot.getMetadata().getName())
+                            .help(gaugeSnapshot.getMetadata().getHelp());
+                    return gaugeSnapshot.getMetadata().hasUnit()
+                        ? base.unit(gaugeSnapshot.getMetadata().getUnit())
+                        : base;
+                  });
+          gaugeSnapshot.getDataPoints().forEach(builder::dataPoint);
+        }
+        case HistogramSnapshot histogramSnapshot -> {
+          HistogramSnapshot.Builder builder =
+              histogramSnapshotMap.computeIfAbsent(
+                  metricName,
+                  k -> {
+                    var base =
+                        HistogramSnapshot.builder()
+                            .name(histogramSnapshot.getMetadata().getName())
+                            .help(histogramSnapshot.getMetadata().getHelp());
+                    return histogramSnapshot.getMetadata().hasUnit()
+                        ? base.unit(histogramSnapshot.getMetadata().getUnit())
+                        : base;
+                  });
+          histogramSnapshot.getDataPoints().forEach(builder::dataPoint);
+        }
+        case InfoSnapshot infoSnapshot -> {
+          // InfoSnapshot is a special case in that each SdkMeterProvider will create a duplicate
+          // metric called target_info containing OTEL SDK metadata. Only one of these need to be
+          // kept
+          if (otelInfoSnapshots == null)
+            otelInfoSnapshots =
+                new InfoSnapshot(infoSnapshot.getMetadata(), infoSnapshot.getDataPoints());
+        }
+        default -> {
+          // Handle unexpected snapshot types gracefully
+        }
+      }
+    }
+
+    MetricSnapshots.Builder snapshotsBuilder = MetricSnapshots.builder();
+    counterSnapshotMap.values().forEach(b -> snapshotsBuilder.metricSnapshot(b.build()));
+    gaugeSnapshotMap.values().forEach(b -> snapshotsBuilder.metricSnapshot(b.build()));
+    histogramSnapshotMap.values().forEach(b -> snapshotsBuilder.metricSnapshot(b.build()));
+    if (otelInfoSnapshots != null) snapshotsBuilder.metricSnapshot(otelInfoSnapshots);
+    return snapshotsBuilder.build();
   }
 
   @Override
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
index bda5fa4af39..4ddc6443f80 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
@@ -52,7 +52,6 @@ import io.opentelemetry.api.metrics.ObservableLongGauge;
 import io.opentelemetry.api.metrics.ObservableLongMeasurement;
 import io.opentelemetry.api.metrics.ObservableLongUpDownCounter;
 import io.opentelemetry.api.metrics.ObservableMeasurement;
-import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
 import io.opentelemetry.sdk.metrics.SdkMeterProvider;
 import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil;
 import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter;
@@ -86,6 +85,7 @@ import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.core.SolrResourceLoader;
 import org.apache.solr.logging.MDCLoggingContext;
+import org.apache.solr.metrics.otel.FilterablePrometheusMetricReader;
 import org.apache.solr.metrics.otel.OtelUnit;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -753,7 +753,7 @@ public class SolrMetricManager {
         .computeIfAbsent(
             providerName,
             key -> {
-              var reader = new PrometheusMetricReader(true, null);
+              var reader = new FilterablePrometheusMetricReader(true, null);
               // NOCOMMIT: We need to add a Periodic Metric Reader here if we want to push with OTLP
               // with an exporter
               var provider = SdkMeterProvider.builder().registerMetricReader(reader);
@@ -1640,17 +1640,17 @@ public class SolrMetricManager {
     return metricsConfig;
   }
 
-  /** Get a shallow copied map of {@link PrometheusMetricReader}. */
-  public Map<String, PrometheusMetricReader> getPrometheusMetricReaders() {
+  /** Get a shallow copied map of {@link FilterablePrometheusMetricReader}. */
+  public Map<String, FilterablePrometheusMetricReader> getPrometheusMetricReaders() {
     return meterProviderAndReaders.entrySet().stream()
         .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().prometheusMetricReader()));
   }
 
-  public PrometheusMetricReader getPrometheusMetricReader(String providerName) {
+  public FilterablePrometheusMetricReader getPrometheusMetricReader(String providerName) {
     MeterProviderAndReaders mpr = meterProviderAndReaders.get(enforcePrefix(providerName));
     return (mpr != null) ? mpr.prometheusMetricReader() : null;
   }
 
   private record MeterProviderAndReaders(
-      SdkMeterProvider sdkMeterProvider, PrometheusMetricReader prometheusMetricReader) {}
+      SdkMeterProvider sdkMeterProvider, FilterablePrometheusMetricReader prometheusMetricReader) {}
 }
diff --git a/solr/core/src/java/org/apache/solr/metrics/otel/FilterablePrometheusMetricReader.java b/solr/core/src/java/org/apache/solr/metrics/otel/FilterablePrometheusMetricReader.java
new file mode 100644
index 00000000000..4cd321c112f
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/metrics/otel/FilterablePrometheusMetricReader.java
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.metrics.otel;
+
+import static java.util.stream.Collectors.toList;
+
+import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
+import io.prometheus.metrics.model.snapshots.CounterSnapshot;
+import io.prometheus.metrics.model.snapshots.DataPointSnapshot;
+import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
+import io.prometheus.metrics.model.snapshots.HistogramSnapshot;
+import io.prometheus.metrics.model.snapshots.InfoSnapshot;
+import io.prometheus.metrics.model.snapshots.Labels;
+import io.prometheus.metrics.model.snapshots.MetricSnapshot;
+import io.prometheus.metrics.model.snapshots.MetricSnapshots;
+import java.lang.invoke.MethodHandles;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FilterablePrometheusMetricReader extends PrometheusMetricReader {
+
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  private static final Set<String> PROM_SUFFIXES =
+      Set.of("_total", "_sum", "_count", "_bucket", "_gcount", "_gsum", "_created", "_info");
+
+  public FilterablePrometheusMetricReader(
+      boolean otelScopeEnabled, Predicate<String> allowedResourceAttributesFilter) {
+    super(otelScopeEnabled, allowedResourceAttributesFilter);
+  }
+
+  /**
+   * Collect metrics with filtering support for metric names and labels.
+   *
+   * @param includedNames Set of metric names to include. If empty, all metric names are included.
+   * @param requiredLabels Map of label names to their allowed values. If empty, no label filtering
+   *     is applied.
+   * @return Filtered MetricSnapshots
+   */
+  public MetricSnapshots collect(
+      Set<String> includedNames, SortedMap<String, Set<String>> requiredLabels) {
+
+    // If no filtering is requested then return all metrics
+    if (includedNames.isEmpty() && requiredLabels.isEmpty()) {
+      return super.collect();
+    }
+
+    // Prometheus appends a suffix to the metrics depending on the metric type. We need to sanitize
+    // the suffix off if they filter by Prometheus name instead of OTEL name.
+    Set<String> sanitizedNames =
+        includedNames.stream()
+            .map(
+                (name) -> {
+                  for (String suffix : PROM_SUFFIXES) {
+                    if (name.endsWith(suffix)) {
+                      return name.substring(0, name.lastIndexOf(suffix));
+                    }
+                  }
+                  return name;
+                })
+            .collect(Collectors.toSet());
+
+    MetricSnapshots snapshotsToFilter;
+    if (sanitizedNames.isEmpty()) {
+      snapshotsToFilter = super.collect();
+    } else {
+      snapshotsToFilter = super.collect(sanitizedNames::contains);
+    }
+
+    // Return named filtered snapshots if not label filters provided
+    if (requiredLabels.isEmpty()) {
+      return snapshotsToFilter;
+    }
+
+    MetricSnapshots.Builder filteredSnapshots = MetricSnapshots.builder();
+    for (MetricSnapshot metricSnapshot : snapshotsToFilter) {
+      switch (metricSnapshot) {
+        case CounterSnapshot snapshot -> {
+          List<CounterSnapshot.CounterDataPointSnapshot> filtered =
+              filterDatapoint(
+                  snapshot, requiredLabels, CounterSnapshot.CounterDataPointSnapshot.class);
+          if (!filtered.isEmpty()) {
+            filteredSnapshots.metricSnapshot(new CounterSnapshot(snapshot.getMetadata(), filtered));
+          }
+        }
+        case HistogramSnapshot snapshot -> {
+          List<HistogramSnapshot.HistogramDataPointSnapshot> filtered =
+              filterDatapoint(
+                  snapshot, requiredLabels, HistogramSnapshot.HistogramDataPointSnapshot.class);
+          if (!filtered.isEmpty()) {
+            filteredSnapshots.metricSnapshot(
+                new HistogramSnapshot(snapshot.getMetadata(), filtered));
+          }
+        }
+        case GaugeSnapshot snapshot -> {
+          List<GaugeSnapshot.GaugeDataPointSnapshot> filtered =
+              filterDatapoint(snapshot, requiredLabels, GaugeSnapshot.GaugeDataPointSnapshot.class);
+          if (!filtered.isEmpty()) {
+            filteredSnapshots.metricSnapshot(new GaugeSnapshot(snapshot.getMetadata(), filtered));
+          }
+        }
+        case InfoSnapshot ignored -> {
+          // Do nothing for InfoSnapshots. Always filter it out
+        }
+        default -> {
+          log.error("Unknown metric snapshot type {}", metricSnapshot.getClass());
+        }
+      }
+    }
+    return filteredSnapshots.build();
+  }
+
+  private <D extends DataPointSnapshot> List<D> filterDatapoint(
+      MetricSnapshot snapshot,
+      SortedMap<String, Set<String>> requiredLabels,
+      Class<D> dataPointClass) {
+    return snapshot.getDataPoints().stream()
+        .filter(dp -> requiredLabelsFilter(dp.getLabels(), requiredLabels))
+        .map(dataPointClass::cast)
+        .collect(toList());
+  }
+
+  static boolean requiredLabelsFilter(
+      Labels labels, SortedMap<String, Set<String>> requiredLabels) {
+    // Both Labels and requiredLabels are name-sorted with unique names.
+    // For each required label, scan forward through labels until we find it.
+    int labelIdx = 0;
+    requireLoop:
+    for (Map.Entry<String, Set<String>> entry : requiredLabels.entrySet()) {
+      String requiredLabelName = entry.getKey();
+      Set<String> allowedValues = entry.getValue();
+
+      // Labels are sorted, so we can continue from the last position
+      while (labelIdx < labels.size()) {
+        String labelName = labels.getName(labelIdx);
+
+        int comparison = labelName.compareTo(requiredLabelName);
+
+        if (comparison < 0) {
+          // a label before requiredLabelName; we don't care about this one
+          assert !requiredLabels.containsKey(labelName);
+          labelIdx++;
+          continue; // inspect the next input label
+        }
+        if (comparison > 0) {
+          // We've passed where requiredLabelName should be, so it doesn't exist
+          assert !labels.contains(requiredLabelName);
+          return false;
+        }
+
+        assert labels.contains(requiredLabelName);
+        if (!allowedValues.contains(labels.getValue(labelIdx))) {
+          return false;
+        }
+        // we satisfied requiredLabelName with its allowedValues.  Move onto next
+        labelIdx++;
+        continue requireLoop;
+      } // end while loop on input Labels
+      // didn't find requiredLabelName; ran out of names
+      assert !labels.contains(requiredLabelName);
+      return false;
+    }
+    return true; // found all requiredLabels in Labels
+  }
+}
diff --git a/solr/core/src/java/org/apache/solr/response/PrometheusResponseWriter.java b/solr/core/src/java/org/apache/solr/response/PrometheusResponseWriter.java
index 070ed662dc0..a5f69e1632b 100644
--- a/solr/core/src/java/org/apache/solr/response/PrometheusResponseWriter.java
+++ b/solr/core/src/java/org/apache/solr/response/PrometheusResponseWriter.java
@@ -18,27 +18,14 @@ package org.apache.solr.response;
 
 import static org.apache.solr.handler.admin.MetricsHandler.OPEN_METRICS_WT;
 
-import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
-import io.opentelemetry.sdk.metrics.SdkMeterProvider;
 import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter;
 import io.prometheus.metrics.expositionformats.PrometheusTextFormatWriter;
-import io.prometheus.metrics.model.snapshots.CounterSnapshot;
-import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
-import io.prometheus.metrics.model.snapshots.HistogramSnapshot;
-import io.prometheus.metrics.model.snapshots.InfoSnapshot;
-import io.prometheus.metrics.model.snapshots.MetricSnapshot;
 import io.prometheus.metrics.model.snapshots.MetricSnapshots;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.lang.invoke.MethodHandles;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.handler.admin.MetricsHandler;
 import org.apache.solr.request.SolrQueryRequest;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /** Response writer for Prometheus metrics. This is used only by the {@link MetricsHandler} */
 @SuppressWarnings(value = "unchecked")
@@ -49,23 +36,17 @@ public class PrometheusResponseWriter implements QueryResponseWriter {
   private static final String CONTENT_TYPE_OPEN_METRICS =
       "application/openmetrics-text; version=1.0.0; charset=utf-8";
 
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
   @Override
   public void write(
       OutputStream out, SolrQueryRequest request, SolrQueryResponse response, String contentType)
       throws IOException {
 
-    Map<String, PrometheusMetricReader> readers =
-        (Map<String, PrometheusMetricReader>) response.getValues().get("metrics");
-
-    List<MetricSnapshot> snapshots =
-        readers.values().stream().flatMap(r -> r.collect().stream()).toList();
-
+    var metrics = response.getValues().get("metrics");
+    MetricSnapshots snapshots = (MetricSnapshots) metrics;
     if (writeOpenMetricsFormat(request)) {
-      new OpenMetricsTextFormatWriter(false, true).write(out, mergeSnapshots(snapshots));
+      new OpenMetricsTextFormatWriter(false, true).write(out, snapshots);
     } else {
-      new PrometheusTextFormatWriter(false).write(out, mergeSnapshots(snapshots));
+      new PrometheusTextFormatWriter(false).write(out, snapshots);
     }
   }
 
@@ -92,90 +73,4 @@ public class PrometheusResponseWriter implements QueryResponseWriter {
     return acceptHeader.contains("application/openmetrics-text")
         && (acceptHeader.contains("version=1.0.0"));
   }
-
-  /**
-   * Merge a collection of individual {@link MetricSnapshot} instances into one {@link
-   * MetricSnapshots}. This is necessary because we create a {@link SdkMeterProvider} per Solr core
-   * resulting in duplicate metric names across cores which is an illegal format if not under the
-   * same prometheus grouping.
-   */
-  private MetricSnapshots mergeSnapshots(List<MetricSnapshot> snapshots) {
-    Map<String, CounterSnapshot.Builder> counterSnapshotMap = new HashMap<>();
-    Map<String, GaugeSnapshot.Builder> gaugeSnapshotMap = new HashMap<>();
-    Map<String, HistogramSnapshot.Builder> histogramSnapshotMap = new HashMap<>();
-    InfoSnapshot otelInfoSnapshots = null;
-
-    for (MetricSnapshot snapshot : snapshots) {
-      String metricName = snapshot.getMetadata().getPrometheusName();
-
-      switch (snapshot) {
-        case CounterSnapshot counterSnapshot -> {
-          CounterSnapshot.Builder builder =
-              counterSnapshotMap.computeIfAbsent(
-                  metricName,
-                  k -> {
-                    var base =
-                        CounterSnapshot.builder()
-                            .name(counterSnapshot.getMetadata().getName())
-                            .help(counterSnapshot.getMetadata().getHelp());
-                    return counterSnapshot.getMetadata().hasUnit()
-                        ? base.unit(counterSnapshot.getMetadata().getUnit())
-                        : base;
-                  });
-          counterSnapshot.getDataPoints().forEach(builder::dataPoint);
-        }
-        case GaugeSnapshot gaugeSnapshot -> {
-          GaugeSnapshot.Builder builder =
-              gaugeSnapshotMap.computeIfAbsent(
-                  metricName,
-                  k -> {
-                    var base =
-                        GaugeSnapshot.builder()
-                            .name(gaugeSnapshot.getMetadata().getName())
-                            .help(gaugeSnapshot.getMetadata().getHelp());
-                    return gaugeSnapshot.getMetadata().hasUnit()
-                        ? base.unit(gaugeSnapshot.getMetadata().getUnit())
-                        : base;
-                  });
-          gaugeSnapshot.getDataPoints().forEach(builder::dataPoint);
-        }
-        case HistogramSnapshot histogramSnapshot -> {
-          HistogramSnapshot.Builder builder =
-              histogramSnapshotMap.computeIfAbsent(
-                  metricName,
-                  k -> {
-                    var base =
-                        HistogramSnapshot.builder()
-                            .name(histogramSnapshot.getMetadata().getName())
-                            .help(histogramSnapshot.getMetadata().getHelp());
-                    return histogramSnapshot.getMetadata().hasUnit()
-                        ? base.unit(histogramSnapshot.getMetadata().getUnit())
-                        : base;
-                  });
-          histogramSnapshot.getDataPoints().forEach(builder::dataPoint);
-        }
-        case InfoSnapshot infoSnapshot -> {
-          // InfoSnapshot is a special case in that each SdkMeterProvider will create a duplicate
-          // metric called target_info containing OTEL SDK metadata. Only one of these need to be
-          // kept
-          if (otelInfoSnapshots == null)
-            otelInfoSnapshots =
-                new InfoSnapshot(infoSnapshot.getMetadata(), infoSnapshot.getDataPoints());
-        }
-        default -> {
-          log.warn(
-              "Unexpected snapshot type: {} for metric {}",
-              snapshot.getClass().getName(),
-              snapshot.getMetadata().getName());
-        }
-      }
-    }
-
-    MetricSnapshots.Builder snapshotsBuilder = MetricSnapshots.builder();
-    counterSnapshotMap.values().forEach(b -> snapshotsBuilder.metricSnapshot(b.build()));
-    gaugeSnapshotMap.values().forEach(b -> snapshotsBuilder.metricSnapshot(b.build()));
-    histogramSnapshotMap.values().forEach(b -> snapshotsBuilder.metricSnapshot(b.build()));
-    if (otelInfoSnapshots != null) snapshotsBuilder.metricSnapshot(otelInfoSnapshots);
-    return snapshotsBuilder.build();
-  }
 }
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 0878bf2c8cd..6cb09d3d95d 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
@@ -17,33 +17,19 @@
 
 package org.apache.solr.handler.admin;
 
-import com.codahale.metrics.Counter;
 import io.opentelemetry.api.common.Attributes;
-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.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;
 import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
 import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.common.MapWriter;
 import org.apache.solr.common.params.CommonParams;
-import org.apache.solr.common.util.NamedList;
-import org.apache.solr.common.util.SimpleOrderedMap;
-import org.apache.solr.core.PluginBag;
-import org.apache.solr.core.PluginInfo;
 import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.metrics.MetricsMap;
 import org.apache.solr.metrics.SolrMetricsContext;
 import org.apache.solr.request.SolrQueryRequest;
-import org.apache.solr.request.SolrRequestHandler;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.security.AuthorizationContext;
-import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -51,699 +37,228 @@ import org.junit.Test;
 public class MetricsHandlerTest extends SolrTestCaseJ4 {
   @BeforeClass
   public static void beforeClass() throws Exception {
-
     initCore("solrconfig-minimal.xml", "schema.xml");
     h.getCoreContainer().waitForLoadingCoresToFinish(30000);
-
-    // manually register & seed some metrics in solr.jvm and solr.jetty for testing via handler
-    // (use "solrtest_" prefix just in case the jvm or jetty adds a "foo" metric at some point)
-    Counter c = h.getCoreContainer().getMetricManager().counter(null, "solr.jvm", "solrtest_foo");
-    c.inc();
-    c = h.getCoreContainer().getMetricManager().counter(null, "solr.jetty", "solrtest_foo");
-    c.inc(2);
-    // test escapes
-    c = h.getCoreContainer().getMetricManager().counter(null, "solr.jetty", "solrtest_foo:bar");
-    c.inc(3);
-
-    h.getCoreContainer()
-        .getMetricManager()
-        .meter(null, "solr.jetty", "org.eclipse.jetty.server.handler.DefaultHandler.2xx-responses");
-    h.getCoreContainer()
-        .getMetricManager()
-        .counter(
-            null, "solr.jetty", "org.eclipse.jetty.server.handler.DefaultHandler.active-requests");
-    h.getCoreContainer()
-        .getMetricManager()
-        .timer(null, "solr.jetty", "org.eclipse.jetty.server.handler.DefaultHandler.dispatches");
-  }
-
-  @AfterClass
-  public static void cleanupMetrics() {
-    if (null != h) {
-      h.getCoreContainer().getMetricManager().registry("solr.jvm").remove("solrtest_foo");
-      h.getCoreContainer().getMetricManager().registry("solr.jetty").remove("solrtest_foo");
-      h.getCoreContainer().getMetricManager().registry("solr.jetty").remove("solrtest_foo:bar");
-    }
   }
 
-  // NOCOMMIT: This test does a bunch of /admin/metrics calls with various params, with various
-  // filters and parameters. We have not migrated all the metrics to otel yet or even created any
-  // filters. Once that is done, we should revisit this test and assert the prometheus response.
   @Test
-  @BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17458")
-  public void test() throws Exception {
+  public void testMetricNamesFiltering() throws Exception {
+    String expectedRequestsMetricName = "solr_core_requests";
     MetricsHandler handler = new MetricsHandler(h.getCoreContainer());
 
+    assertQ(req("*:*"), "//result[@numFound='0']");
+
     SolrQueryResponse resp = new SolrQueryResponse();
     handler.handleRequestBody(
         req(
             CommonParams.QT,
             "/admin/metrics",
-            MetricsHandler.COMPACT_PARAM,
-            "false",
             CommonParams.WT,
-            "json"),
+            MetricsHandler.PROMETHEUS_METRICS_WT,
+            MetricsHandler.METRIC_NAME_PARAM,
+            expectedRequestsMetricName),
         resp);
-    NamedList<?> values = resp.getValues();
-    assertNotNull(values.get("metrics"));
-    values = (NamedList<?>) values.get("metrics");
-    NamedList<?> nl = (NamedList<?>) values.get("solr.core.collection1");
-    assertNotNull(nl);
-    Object o = nl.get("SEARCHER.new.errors");
-    assertNotNull(o); // counter type
-    assertTrue(o instanceof MapWriter);
-    // response wasn't serialized, so we get here whatever MetricUtils produced instead of NamedList
-    assertNotNull(((MapWriter) o)._get("count"));
-    assertEquals(0L, ((MapWriter) nl.get("SEARCHER.new.errors"))._get("count"));
-    assertNotNull(nl.get("INDEX.segments")); // int gauge
-    assertTrue((int) ((MapWriter) nl.get("INDEX.segments"))._get("value") >= 0);
-    assertNotNull(nl.get("INDEX.sizeInBytes")); // long gauge
-    assertTrue((long) ((MapWriter) nl.get("INDEX.sizeInBytes"))._get("value") >= 0);
-    nl = (NamedList<?>) values.get("solr.node");
-    assertNotNull(nl.get("CONTAINER.cores.loaded")); // int gauge
-    assertEquals(1, ((MapWriter) nl.get("CONTAINER.cores.loaded"))._get("value"));
-    assertNotNull(nl.get("ADMIN./admin/authorization.clientErrors")); // timer type
-    Map<String, Object> map = new HashMap<>();
-    ((MapWriter) nl.get("ADMIN./admin/authorization.clientErrors")).toMap(map);
-    assertEquals(5, map.size());
-
-    resp = new SolrQueryResponse();
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            MetricsHandler.COMPACT_PARAM,
-            "false",
-            CommonParams.WT,
-            "json",
-            "group",
-            "jvm,jetty"),
-        resp);
-    values = resp.getValues();
-    assertNotNull(values.get("metrics"));
-    values = (NamedList<?>) values.get("metrics");
-    assertEquals(2, values.size());
-    assertNotNull(values.get("solr.jetty"));
-    assertNotNull(values.get("solr.jvm"));
-
-    resp = new SolrQueryResponse();
-    // "collection" works too, because it's a prefix for "collection1"
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            MetricsHandler.COMPACT_PARAM,
-            "false",
-            CommonParams.WT,
-            "json",
-            "registry",
-            "solr.core.collection,solr.jvm"),
-        resp);
-    values = resp.getValues();
-    assertNotNull(values.get("metrics"));
-    values = (NamedList<?>) values.get("metrics");
-    assertEquals(2, values.size());
-    assertNotNull(values.get("solr.core.collection1"));
-    assertNotNull(values.get("solr.jvm"));
-
-    resp = new SolrQueryResponse();
-    // "collection" works too, because it's a prefix for "collection1"
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            MetricsHandler.COMPACT_PARAM,
-            "false",
-            CommonParams.WT,
-            "json",
-            "registry",
-            "solr.core.collection",
-            "registry",
-            "solr.jvm"),
-        resp);
-    values = resp.getValues();
-    assertNotNull(values.get("metrics"));
-    values = (NamedList<?>) values.get("metrics");
-    assertEquals(2, values.size());
-    assertNotNull(values.get("solr.core.collection1"));
-    assertNotNull(values.get("solr.jvm"));
-
-    resp = new SolrQueryResponse();
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            MetricsHandler.COMPACT_PARAM,
-            "false",
-            CommonParams.WT,
-            "json",
-            "group",
-            "jvm,jetty"),
-        resp);
-    values = resp.getValues();
-    assertNotNull(values.get("metrics"));
-    values = (NamedList<?>) values.get("metrics");
-    assertEquals(2, values.size());
-    assertNotNull(values.get("solr.jetty"));
-    assertNotNull(values.get("solr.jvm"));
-
-    resp = new SolrQueryResponse();
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            MetricsHandler.COMPACT_PARAM,
-            "false",
-            CommonParams.WT,
-            "json",
-            "group",
-            "jvm",
-            "group",
-            "jetty"),
-        resp);
-    values = resp.getValues();
-    assertNotNull(values.get("metrics"));
-    values = (NamedList<?>) values.get("metrics");
-    assertEquals(2, values.size());
-    assertNotNull(values.get("solr.jetty"));
-    assertNotNull(values.get("solr.jvm"));
-
-    resp = new SolrQueryResponse();
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            MetricsHandler.COMPACT_PARAM,
-            "false",
-            CommonParams.WT,
-            "json",
-            "group",
-            "node",
-            "type",
-            "counter"),
-        resp);
-    values = resp.getValues();
-    assertNotNull(values.get("metrics"));
-    values = (NamedList<?>) values.get("metrics");
-    assertEquals(1, values.size());
-    values = (NamedList<?>) values.get("solr.node");
-    assertNotNull(values);
-    assertNull(values.get("ADMIN./admin/authorization.errors")); // this is a timer node
-
-    resp = new SolrQueryResponse();
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            MetricsHandler.COMPACT_PARAM,
-            "false",
-            CommonParams.WT,
-            "json",
-            "prefix",
-            "CONTAINER.cores,CONTAINER.threadPool"),
-        resp);
-    values = resp.getValues();
-    assertNotNull(values.get("metrics"));
-    values = (NamedList<?>) values.get("metrics");
-    assertEquals(1, values.size());
-    assertNotNull(values.get("solr.node"));
-    values = (NamedList<?>) values.get("solr.node");
-    assertEquals(15, values.size());
-    assertNotNull(values.get("CONTAINER.cores.lazy")); // this is a gauge node
-    assertNotNull(values.get("CONTAINER.threadPool.coreLoadExecutor.completed"));
-
-    resp = new SolrQueryResponse();
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            MetricsHandler.COMPACT_PARAM,
-            "false",
-            CommonParams.WT,
-            "json",
-            "prefix",
-            "CONTAINER.cores",
-            "regex",
-            "C.*thread.*completed"),
-        resp);
-    values = resp.getValues();
-    assertNotNull(values.get("metrics"));
-    values = (NamedList<?>) values.get("metrics");
-    assertNotNull(values.get("solr.node"));
-    values = (NamedList<?>) values.get("solr.node");
-    assertEquals(5, values.size());
-    assertNotNull(values.get("CONTAINER.threadPool.coreLoadExecutor.completed"));
-
-    resp = new SolrQueryResponse();
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            CommonParams.WT,
-            "json",
-            "prefix",
-            "CACHE.core.fieldCache",
-            "property",
-            "entries_count",
-            MetricsHandler.COMPACT_PARAM,
-            "true"),
-        resp);
-    values = resp.getValues();
-    assertNotNull(values.get("metrics"));
-    values = (NamedList<?>) values.get("metrics");
-    assertNotNull(values.get("solr.core.collection1"));
-    values = (NamedList<?>) values.get("solr.core.collection1");
-    assertEquals(1, values.size());
-    MapWriter writer = (MapWriter) values.get("CACHE.core.fieldCache");
-    assertNotNull(writer);
-    assertNotNull(writer._get("entries_count"));
-
-    resp = new SolrQueryResponse();
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            MetricsHandler.COMPACT_PARAM,
-            "false",
-            CommonParams.WT,
-            "json",
-            "group",
-            "jvm",
-            "prefix",
-            "CONTAINER.cores"),
-        resp);
-    values = resp.getValues();
-    assertNotNull(values.get("metrics"));
-    values = (NamedList<?>) values.get("metrics");
-    assertEquals(0, values.size());
+    var metrics = resp.getValues().get("metrics");
+    MetricSnapshots snapshots = (MetricSnapshots) metrics;
+    assertEquals(1, snapshots.size());
+    assertEquals(expectedRequestsMetricName, snapshots.get(0).getMetadata().getPrometheusName());
 
-    resp = new SolrQueryResponse();
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            MetricsHandler.COMPACT_PARAM,
-            "false",
-            CommonParams.WT,
-            "json",
-            "group",
-            "node",
-            "type",
-            "timer",
-            "prefix",
-            "CONTAINER.cores"),
-        resp);
-    values = resp.getValues();
-    assertNotNull(values.get("metrics"));
-    SimpleOrderedMap<?> map1 = (SimpleOrderedMap<?>) values.get("metrics");
-    assertEquals(0, map1.size());
     handler.close();
   }
 
   @Test
-  public void testPropertyFilter() throws Exception {
-    assertQ(req("*:*"), "//result[@numFound='0']");
-
+  public void testMultipleMetricNamesFiltering() throws Exception {
+    String expectedRequestsMetricName = "solr_core_requests";
+    String expectedSearcherMetricName = "solr_core_searcher_new";
+    var expected = Set.of(expectedRequestsMetricName, expectedSearcherMetricName);
     MetricsHandler handler = new MetricsHandler(h.getCoreContainer());
 
+    assertQ(req("*:*"), "//result[@numFound='0']");
+
     SolrQueryResponse resp = new SolrQueryResponse();
     handler.handleRequestBody(
         req(
             CommonParams.QT,
             "/admin/metrics",
             CommonParams.WT,
-            "json",
-            MetricsHandler.COMPACT_PARAM,
-            "true",
-            "group",
-            "core",
-            "prefix",
-            "CACHE.searcher"),
+            MetricsHandler.PROMETHEUS_METRICS_WT,
+            MetricsHandler.METRIC_NAME_PARAM,
+            expectedRequestsMetricName + "," + expectedSearcherMetricName),
         resp);
-    NamedList<?> values = resp.getValues();
-    assertNotNull(values.get("metrics"));
-    values = (NamedList<?>) values.get("metrics");
-    NamedList<?> nl = (NamedList<?>) values.get("solr.core.collection1");
-    assertNotNull(nl);
-    assertTrue(nl.size() > 0);
-    nl.forEach(
-        (k, v) -> {
-          assertTrue(v instanceof MapWriter);
-          Map<String, Object> map = new HashMap<>();
-          ((MapWriter) v).toMap(map);
-          assertTrue(map.size() > 2);
-        });
 
-    resp = new SolrQueryResponse();
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            CommonParams.WT,
-            "json",
-            MetricsHandler.COMPACT_PARAM,
-            "true",
-            "group",
-            "core",
-            "prefix",
-            "CACHE.searcher",
-            "property",
-            "inserts",
-            "property",
-            "size"),
-        resp);
-    values = resp.getValues();
-    values = (NamedList<?>) values.get("metrics");
-    nl = (NamedList<?>) values.get("solr.core.collection1");
-    assertNotNull(nl);
-    assertTrue(nl.size() > 0);
-    nl.forEach(
-        (k, v) -> {
-          assertTrue(v instanceof MapWriter);
-          Map<String, Object> map = new HashMap<>();
-          ((MapWriter) v).toMap(map);
-          assertEquals("k=" + k + ", v=" + map, 2, map.size());
-          assertNotNull(map.get("inserts"));
-          assertNotNull(map.get("size"));
-        });
+    var metrics = (MetricSnapshots) resp.getValues().get("metrics");
+    assertEquals(2, metrics.size());
+    Set<String> actual =
+        metrics.stream().map(m -> m.getMetadata().getPrometheusName()).collect(Collectors.toSet());
+    assertEquals(expected, actual);
+
     handler.close();
   }
 
-  // NOCOMMIT: Have not implemented any kind of filtering for OTEL yet
   @Test
-  @BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17458")
-  public void testKeyMetrics() throws Exception {
+  public void testNonExistentMetricNameFiltering() throws Exception {
+    String nonexistentMetricName = "nonexistent_metric_name";
     MetricsHandler handler = new MetricsHandler(h.getCoreContainer());
 
-    String key1 = "solr.core.collection1:CACHE.core.fieldCache";
     SolrQueryResponse resp = new SolrQueryResponse();
     handler.handleRequestBody(
         req(
             CommonParams.QT,
             "/admin/metrics",
             CommonParams.WT,
-            "json",
-            MetricsHandler.KEY_PARAM,
-            key1),
+            MetricsHandler.PROMETHEUS_METRICS_WT,
+            MetricsHandler.METRIC_NAME_PARAM,
+            nonexistentMetricName),
         resp);
-    NamedList<?> values = resp.getValues();
-    Object val = values._get(List.of("metrics", key1), null);
-    assertNotNull(val);
-    assertTrue(val instanceof MapWriter);
-    assertTrue(((MapWriter) val)._size() >= 2);
-
-    String key2 = "solr.core.collection1:CACHE.core.fieldCache:entries_count";
-    resp = new SolrQueryResponse();
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            CommonParams.WT,
-            "json",
-            MetricsHandler.KEY_PARAM,
-            key2),
-        resp);
-    val = resp.getValues()._get("metrics/" + key2);
-    assertNotNull(val);
-    assertTrue(val instanceof Number);
+    var metrics = (MetricSnapshots) resp.getValues().get("metrics");
+    assertEquals(0, metrics.size());
+    handler.close();
+  }
 
-    String key3 = "solr.jetty:solrtest_foo\\:bar";
-    resp = new SolrQueryResponse();
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            CommonParams.WT,
-            "json",
-            MetricsHandler.KEY_PARAM,
-            key3),
-        resp);
+  @Test
+  public void testLabelFiltering() throws Exception {
+    MetricsHandler handler = new MetricsHandler(h.getCoreContainer());
 
-    val = resp.getValues()._get("metrics/" + key3);
-    assertNotNull(val);
-    assertTrue(val instanceof Number);
-    assertEquals(3, ((Number) val).intValue());
+    assertQ(req("*:*"), "//result[@numFound='0']");
 
-    // test multiple keys
-    resp = new SolrQueryResponse();
+    SolrQueryResponse resp = new SolrQueryResponse();
     handler.handleRequestBody(
         req(
             CommonParams.QT,
             "/admin/metrics",
             CommonParams.WT,
-            "json",
-            MetricsHandler.KEY_PARAM,
-            key1,
-            MetricsHandler.KEY_PARAM,
-            key2,
-            MetricsHandler.KEY_PARAM,
-            key3),
+            MetricsHandler.PROMETHEUS_METRICS_WT,
+            MetricsHandler.CATEGORY_PARAM,
+            "QUERY"),
         resp);
+    var metrics = (MetricSnapshots) resp.getValues().get("metrics");
+
+    metrics.forEach(
+        (ms) -> {
+          ms.getDataPoints()
+              .forEach(
+                  (dp) -> {
+                    assertEquals("QUERY", dp.getLabels().get(MetricsHandler.CATEGORY_PARAM));
+                  });
+        });
 
-    val = resp.getValues()._get("metrics/" + key1);
-    assertNotNull(val);
-    val = resp.getValues()._get("metrics/" + key2);
-    assertNotNull(val);
-    val = resp.getValues()._get("metrics/" + key3);
-    assertNotNull(val);
+    handler.close();
+  }
 
-    String key4 = "solr.core.collection1:QUERY./select.requestTimes:1minRate";
-    resp = new SolrQueryResponse();
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            CommonParams.WT,
-            "json",
-            MetricsHandler.KEY_PARAM,
-            key4),
-        resp);
-    // the key contains a slash, need explicit list of path elements
-    val = resp.getValues()._get(Arrays.asList("metrics", key4), null);
-    assertNotNull(val);
-    assertTrue(val instanceof Number);
+  @Test
+  public void testMultipleLabelFiltering() throws Exception {
+    MetricsHandler handler = new MetricsHandler(h.getCoreContainer());
 
-    // test errors
+    assertQ(req("*:*"), "//result[@numFound='0']");
 
-    // invalid keys
-    resp = new SolrQueryResponse();
+    SolrQueryResponse resp = new SolrQueryResponse();
     handler.handleRequestBody(
         req(
             CommonParams.QT,
             "/admin/metrics",
             CommonParams.WT,
-            "json",
-            MetricsHandler.KEY_PARAM,
-            "foo",
-            MetricsHandler.KEY_PARAM,
-            "foo:bar:baz:xyz"),
+            MetricsHandler.PROMETHEUS_METRICS_WT,
+            MetricsHandler.CATEGORY_PARAM,
+            "QUERY" + "," + "SEARCHER"),
         resp);
-    values = resp.getValues();
-    NamedList<?> metrics = (NamedList<?>) values.get("metrics");
-    assertEquals(0, metrics.size());
-    assertNotNull(values._get(List.of("errors", "foo"), null));
-    assertNotNull(values._get(List.of("errors", "foo:bar:baz:xyz"), null));
 
-    // unknown registry
-    resp = new SolrQueryResponse();
+    var metrics = (MetricSnapshots) resp.getValues().get("metrics");
+    metrics.forEach(
+        (ms) -> {
+          ms.getDataPoints()
+              .forEach(
+                  (dp) -> {
+                    assertTrue(
+                        dp.getLabels().get(MetricsHandler.CATEGORY_PARAM).equals("QUERY")
+                            || dp.getLabels()
+                                .get(MetricsHandler.CATEGORY_PARAM)
+                                .equals("SEARCHER"));
+                  });
+        });
+
+    handler.close();
+  }
+
+  @Test
+  public void testNonExistentLabelFiltering() throws Exception {
+    MetricsHandler handler = new MetricsHandler(h.getCoreContainer());
+
+    SolrQueryResponse resp = new SolrQueryResponse();
     handler.handleRequestBody(
         req(
             CommonParams.QT,
             "/admin/metrics",
             CommonParams.WT,
-            "json",
-            MetricsHandler.KEY_PARAM,
-            "foo:bar:baz"),
+            MetricsHandler.PROMETHEUS_METRICS_WT,
+            MetricsHandler.CORE_PARAM,
+            "nonexistent_core_name"),
         resp);
-    values = resp.getValues();
-    metrics = (NamedList<?>) values.get("metrics");
+
+    var metrics = (MetricSnapshots) resp.getValues().get("metrics");
     assertEquals(0, metrics.size());
-    assertNotNull(values._get(List.of("errors", "foo:bar:baz"), null));
+    handler.close();
+  }
+
+  @Test
+  public void testMixedLabelFiltering() throws Exception {
+    MetricsHandler handler = new MetricsHandler(h.getCoreContainer());
+
+    assertQ(req("*:*"), "//result[@numFound='0']");
 
-    // unknown metric
-    resp = new SolrQueryResponse();
+    SolrQueryResponse resp = new SolrQueryResponse();
     handler.handleRequestBody(
         req(
             CommonParams.QT,
             "/admin/metrics",
             CommonParams.WT,
-            "json",
-            MetricsHandler.KEY_PARAM,
-            "solr.jetty:unknown:baz"),
+            MetricsHandler.PROMETHEUS_METRICS_WT,
+            MetricsHandler.CORE_PARAM,
+            "collection1",
+            MetricsHandler.CATEGORY_PARAM,
+            "SEARCHER"),
         resp);
-    values = resp.getValues();
-    metrics = (NamedList<?>) values.get("metrics");
-    assertEquals(0, metrics.size());
-    assertNotNull(values._get(List.of("errors", "solr.jetty:unknown:baz"), null));
+
+    var metrics = (MetricSnapshots) resp.getValues().get("metrics");
+    metrics.forEach(
+        (ms) -> {
+          ms.getDataPoints()
+              .forEach(
+                  (dp) -> {
+                    assertTrue(
+                        dp.getLabels().get(MetricsHandler.CATEGORY_PARAM).equals("SEARCHER")
+                            && dp.getLabels().get(MetricsHandler.CORE_PARAM).equals("collection1"));
+                  });
+        });
 
     handler.close();
   }
 
-  // NOCOMMIT: Have not implemented any kind of filtering for OTEL yet
   @Test
-  @BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17458")
-  @SuppressWarnings("unchecked")
-  public void testExprMetrics() throws Exception {
+  public void testMetricNamesAndLabelFiltering() throws Exception {
+    String expectedMetricName = "solr_core_segments";
     MetricsHandler handler = new MetricsHandler(h.getCoreContainer());
 
-    String key1 = "solr\\.core\\..*:.*/select\\.request.*:.*Rate";
+    assertQ(req("*:*"), "//result[@numFound='0']");
+
     SolrQueryResponse resp = new SolrQueryResponse();
     handler.handleRequestBody(
         req(
             CommonParams.QT,
             "/admin/metrics",
             CommonParams.WT,
-            "json",
-            MetricsHandler.EXPR_PARAM,
-            key1),
-        resp);
-    // response structure is like in the case of non-key params
-    Object val =
-        resp.getValues()
-            ._get(List.of("metrics", "solr.core.collection1", "QUERY./select.requestTimes"), null);
-    assertNotNull(val);
-    assertTrue(val instanceof MapWriter);
-    Map<String, Object> map = new HashMap<>();
-    ((MapWriter) val).toMap(map);
-    assertEquals(map.toString(), 4, map.size()); // mean, 1, 5, 15
-    assertNotNull(map.toString(), map.get("meanRate"));
-    assertNotNull(map.toString(), map.get("1minRate"));
-    assertNotNull(map.toString(), map.get("5minRate"));
-    assertNotNull(map.toString(), map.get("15minRate"));
-    assertEquals(map.toString(), ((Number) map.get("1minRate")).doubleValue(), 0.0, 0.0);
-    map.clear();
-
-    String key2 = "solr\\.core\\..*:.*/select\\.request.*";
-    resp = new SolrQueryResponse();
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            CommonParams.WT,
-            "json",
-            MetricsHandler.EXPR_PARAM,
-            key2),
+            MetricsHandler.PROMETHEUS_METRICS_WT,
+            MetricsHandler.CATEGORY_PARAM,
+            "CORE",
+            MetricsHandler.METRIC_NAME_PARAM,
+            expectedMetricName),
         resp);
-    // response structure is like in the case of non-key params
-    val = resp.getValues()._get(List.of("metrics", "solr.core.collection1"), null);
-    assertNotNull(val);
-    Object v = ((SimpleOrderedMap<Object>) val).get("QUERY./select.requestTimes");
-    assertNotNull(v);
-    assertTrue(v instanceof MapWriter);
-    ((MapWriter) v).toMap(map);
-    assertEquals(map.toString(), 14, map.size());
-    assertNotNull(map.toString(), map.get("1minRate"));
-    assertEquals(map.toString(), ((Number) map.get("1minRate")).doubleValue(), 0.0, 0.0);
-    map.clear();
-    // select requests counter
-    v = ((SimpleOrderedMap<Object>) val).get("QUERY./select.requests");
-    assertNotNull(v);
-    assertTrue(v instanceof Number);
-
-    // test multiple expressions producing overlapping metrics - should be no dupes
-
-    // this key matches also sub-metrics of /select, eg. /select[shard], ...
-    String key3 = "solr\\.core\\..*:.*/select.*\\.requestTimes:count";
-    resp = new SolrQueryResponse();
-    // ORDER OF PARAMS MATTERS HERE! see the refguide
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            CommonParams.WT,
-            "json",
-            MetricsHandler.EXPR_PARAM,
-            key2,
-            MetricsHandler.EXPR_PARAM,
-            key1,
-            MetricsHandler.EXPR_PARAM,
-            key3),
-        resp);
-    val = resp.getValues()._get(List.of("metrics", "solr.core.collection1"), null);
-    assertNotNull(val);
-    // for requestTimes only the full set of values from the first expr should be present
-    assertNotNull(val);
-    SimpleOrderedMap<Object> values = (SimpleOrderedMap<Object>) val;
-    assertEquals(values.jsonStr(), 3, values.size());
-    v = values.get("QUERY./select.requestTimes");
-    assertTrue(v instanceof MapWriter);
-    ((MapWriter) v).toMap(map);
-    assertTrue(map.toString(), map.containsKey("count"));
-    map.clear();
-    v = values.get("QUERY./select[shard].requestTimes");
-    assertTrue(v instanceof MapWriter);
-    ((MapWriter) v).toMap(map);
-    assertTrue(map.toString(), map.containsKey("count"));
-  }
 
-  private MetricSnapshot getMetricSnapshot(MetricSnapshots snapshots, String metricName) {
-    return snapshots.stream()
-        .filter(ss -> ss.getMetadata().getPrometheusName().equals(metricName))
-        .findAny()
-        .get();
-  }
-
-  private GaugeSnapshot.GaugeDataPointSnapshot getGaugeDatapointSnapshot(
-      MetricSnapshot snapshot, Labels labels) {
-    return (GaugeSnapshot.GaugeDataPointSnapshot)
-        snapshot.getDataPoints().stream()
-            .filter(ss -> ss.getLabels().hasSameValues(labels))
-            .findAny()
-            .get();
-  }
-
-  private CounterSnapshot.CounterDataPointSnapshot getCounterDatapointSnapshot(
-      MetricSnapshot snapshot, Labels labels) {
-    return (CounterSnapshot.CounterDataPointSnapshot)
-        snapshot.getDataPoints().stream()
-            .filter(ss -> ss.getLabels().hasSameValues(labels))
-            .findAny()
-            .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;
-    private SolrMetricsContext metricsInfo;
-
-    public RefreshablePluginHolder(PluginInfo info, DumpRequestHandler rh) {
-      super(info);
-      this.rh = rh;
-    }
-
-    @Override
-    public boolean isLoaded() {
-      return true;
-    }
-
-    void closeHandler() throws Exception {
-      this.metricsInfo = rh.getSolrMetricsContext();
-      //      if(metricsInfo.tag.contains(String.valueOf(rh.hashCode()))){
-      //        //this created a new child metrics
-      //        metricsInfo = metricsInfo.getParent();
-      //      }
-      this.rh.close();
-    }
-
-    void reset(DumpRequestHandler rh) {
-      this.rh = rh;
-      if (metricsInfo != null)
-        this.rh.initializeMetrics(metricsInfo, Attributes.empty(), "/dumphandler");
-    }
-
-    @Override
-    public SolrRequestHandler get() {
-      return rh;
-    }
+    var metrics = (MetricSnapshots) resp.getValues().get("metrics");
+    assertEquals(1, metrics.size());
+    var actualDatapoint = metrics.get(0).getDataPoints().getFirst();
+    assertEquals(expectedMetricName, metrics.get(0).getMetadata().getPrometheusName());
+    assertEquals("CORE", actualDatapoint.getLabels().get(MetricsHandler.CATEGORY_PARAM));
+    handler.close();
   }
 
   public static class DumpRequestHandler extends RequestHandlerBase {
diff --git a/solr/core/src/test/org/apache/solr/metrics/otel/FilterablePrometheusMetricReaderTest.java b/solr/core/src/test/org/apache/solr/metrics/otel/FilterablePrometheusMetricReaderTest.java
new file mode 100644
index 00000000000..ec91e149b4b
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/metrics/otel/FilterablePrometheusMetricReaderTest.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.metrics.otel;
+
+import io.prometheus.metrics.model.snapshots.Labels;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import org.apache.solr.SolrTestCaseJ4;
+import org.junit.Test;
+
+/**
+ * Test class for FilterablePrometheusMetricReader which focuses on requiredLabelsFilter method
+ * which filters metric data points based on label requirements.
+ */
+public class FilterablePrometheusMetricReaderTest extends SolrTestCaseJ4 {
+
+  Labels actualLabels = Labels.of("key1", "value1", "key2", "value2", "key3", "value3");
+
+  @Test
+  public void testFilterMatchingLabel() {
+    SortedMap<String, Set<String>> requiredLabels = new TreeMap<>(Map.of("key1", Set.of("value1")));
+    assertTrue(FilterablePrometheusMetricReader.requiredLabelsFilter(actualLabels, requiredLabels));
+  }
+
+  @Test
+  public void testFilterOneMatchingLabelValue() {
+    SortedMap<String, Set<String>> requiredLabels =
+        new TreeMap<>(Map.of("key1", Set.of("value1", "value123", "value456", "value789")));
+    assertTrue(FilterablePrometheusMetricReader.requiredLabelsFilter(actualLabels, requiredLabels));
+  }
+
+  @Test
+  public void testFilterNoMatchingLabel() {
+    SortedMap<String, Set<String>> requiredLabels =
+        new TreeMap<>(Map.of("dummyKey", Set.of("dummyValue")));
+    assertFalse(
+        FilterablePrometheusMetricReader.requiredLabelsFilter(actualLabels, requiredLabels));
+  }
+
+  @Test
+  public void testFilterAllMultipleMatchingLabels() {
+    SortedMap<String, Set<String>> requiredLabels =
+        new TreeMap<>(Map.of("key1", Set.of("value1"), "key2", Set.of("value2")));
+    assertTrue(FilterablePrometheusMetricReader.requiredLabelsFilter(actualLabels, requiredLabels));
+  }
+
+  @Test
+  public void testFilterMultipleWithOneLabelValueNotMatching() {
+    SortedMap<String, Set<String>> requiredLabels =
+        new TreeMap<>(Map.of("key1", Set.of("value1"), "key2", Set.of("value999")));
+    assertFalse(
+        FilterablePrometheusMetricReader.requiredLabelsFilter(actualLabels, requiredLabels));
+  }
+
+  @Test
+  public void testFilterMultipleWithOneLabelMissing() {
+    SortedMap<String, Set<String>> requiredLabels =
+        new TreeMap<>(Map.of("key1", Set.of("value1"), "key999", Set.of("value2")));
+    assertFalse(
+        FilterablePrometheusMetricReader.requiredLabelsFilter(actualLabels, requiredLabels));
+  }
+
+  @Test
+  public void testFilterLabelsWithMixedValues() {
+    // Scenario with multiple label values some matching and some not
+    SortedMap<String, Set<String>> requiredLabels =
+        new TreeMap<>(
+            Map.of("key1", Set.of("value1", "value123"), "key2", Set.of("value456", "value2")));
+    assertTrue(FilterablePrometheusMetricReader.requiredLabelsFilter(actualLabels, requiredLabels));
+  }
+
+  @Test
+  public void testFilterEmptyLabelValues() {
+    // Edge case where label has empty set of allowed values
+    SortedMap<String, Set<String>> requiredLabels = new TreeMap<>(Map.of("key1", Set.of()));
+    assertFalse(
+        FilterablePrometheusMetricReader.requiredLabelsFilter(actualLabels, requiredLabels));
+  }
+}
diff --git a/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java b/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java
index 09aa0489949..ecfd83c02d4 100644
--- a/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java
+++ b/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java
@@ -102,7 +102,7 @@ public class SolrIndexMetricsTest extends SolrTestCaseJ4 {
       var segmentSize =
           SolrMetricTestUtils.getGaugeDatapoint(
               core,
-              "solr_core_segment_count",
+              "solr_core_segments",
               SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
                   .label("category", "CORE")
                   .build());


(solr) 31/33: SOLR-17806: Migrate SolrIndexSearcher metrics to OTEL (#3482)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit c022d6bb42730a0dd412820d12e783be2478aa94
Author: Sanjay Dutt <sa...@apache.org>
AuthorDate: Fri Sep 26 20:51:15 2025 +0530

    SOLR-17806: Migrate SolrIndexSearcher metrics to OTEL (#3482)
    
    * Migrate SolrIndexSearcher metrics to OTEL
    
    * renaming metrics
    
    * metric renaming, adding descriptions
    
    * removing comment and renaming metric
    
    * switch to OTEL for statsCache
    
    * recording string metric is not required
    
    * Metrics cleanup
    
    * Fix broken searcher tests
    
    * Change solr_searcher_live_docs_cache to a counter
    
    * Feedback changes
    
    * Remove sort metric
    
    * Remove caching enabled metric
    
    ---------
    
    Co-authored-by: Matthew Biscocho <ml...@apache.org>
    Co-authored-by: Matthew Biscocho <mb...@bloomberg.net>
---
 .../src/java/org/apache/solr/core/SolrCore.java    |   1 +
 .../org/apache/solr/search/SolrIndexSearcher.java  | 203 ++++++++++++---------
 .../org/apache/solr/search/stats/StatsCache.java   |  97 +++++++++-
 .../apache/solr/cloud/TestBaseStatsCacheCloud.java | 134 +++++++-------
 .../solr/handler/admin/StatsReloadRaceTest.java    |  34 ++--
 .../org/apache/solr/search/TestIndexSearcher.java  |  11 --
 .../apache/solr/search/TestMainQueryCaching.java   |  53 ++----
 7 files changed, 311 insertions(+), 222 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/core/SolrCore.java b/solr/core/src/java/org/apache/solr/core/SolrCore.java
index d42096c6b6e..ea2b9a08e8f 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrCore.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java
@@ -1712,6 +1712,7 @@ public class SolrCore implements SolrInfoBean, Closeable {
       }
       cache = new LocalStatsCache();
     }
+    cache.initializeMetrics(solrMetricsContext, coreAttributes, null);
     return cache;
   }
 
diff --git a/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java b/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
index 235160ec28b..51dd2c83930 100644
--- a/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
+++ b/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
@@ -16,6 +16,10 @@
  */
 package org.apache.solr.search;
 
+import static org.apache.solr.metrics.SolrCoreMetricManager.COLLECTION_ATTR;
+import static org.apache.solr.metrics.SolrCoreMetricManager.CORE_ATTR;
+import static org.apache.solr.metrics.SolrCoreMetricManager.REPLICA_ATTR;
+import static org.apache.solr.metrics.SolrCoreMetricManager.SHARD_ATTR;
 import static org.apache.solr.search.CpuAllowedLimit.TIMING_CONTEXT;
 
 import com.codahale.metrics.Gauge;
@@ -25,7 +29,6 @@ import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
@@ -96,8 +99,10 @@ import org.apache.solr.common.util.CollectionUtil;
 import org.apache.solr.common.util.EnvUtils;
 import org.apache.solr.common.util.ExecutorUtil;
 import org.apache.solr.common.util.ExecutorUtil.MDCAwareThreadPoolExecutor;
+import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.common.util.ObjectReleaseTracker;
 import org.apache.solr.common.util.SolrNamedThreadFactory;
+import org.apache.solr.common.util.Utils;
 import org.apache.solr.core.DirectoryFactory;
 import org.apache.solr.core.DirectoryFactory.DirContext;
 import org.apache.solr.core.NodeConfig;
@@ -105,8 +110,10 @@ import org.apache.solr.core.SolrConfig;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.index.SlowCompositeReaderWrapper;
-import org.apache.solr.metrics.MetricsMap;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.OtelUnit;
+import org.apache.solr.metrics.otel.instruments.AttributedLongGauge;
+import org.apache.solr.metrics.otel.instruments.AttributedLongTimer;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestInfo;
 import org.apache.solr.response.SolrQueryResponse;
@@ -170,6 +177,13 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
   private final LongAdder liveDocsNaiveCacheHitCount = new LongAdder();
   private final LongAdder liveDocsInsertsCount = new LongAdder();
   private final LongAdder liveDocsHitCount = new LongAdder();
+  private final List<AutoCloseable> toClose = new ArrayList<>();
+
+  // Synchronous gauge for caching enabled status
+  private AttributedLongGauge cachingEnabledGauge;
+
+  // Timer for warmup time histogram
+  private AttributedLongTimer warmupTimer;
 
   // map of generic caches - not synchronized since it's read-only after the constructor.
   private final Map<String, SolrCache<?, ?>> cacheMap;
@@ -400,6 +414,7 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
     this.leafReader = SlowCompositeReaderWrapper.wrap(this.reader);
     this.core = core;
     this.statsCache = core.createStatsCache();
+    this.toClose.add(this.statsCache);
     this.schema = schema;
     this.name =
         "Searcher@"
@@ -606,8 +621,23 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
           core.getCoreAttributes().toBuilder().put(NAME_ATTR, cache.name()).build(),
           "solr_searcher_cache");
     }
-    // TODO SOLR-17458: Add Otel
-    initializeMetrics(solrMetricsContext, Attributes.empty(), STATISTICS_KEY);
+    Attributes baseAttributes;
+    if (core.getCoreContainer().isZooKeeperAware()) {
+      baseAttributes =
+          Attributes.builder()
+              .put(COLLECTION_ATTR, core.getCoreDescriptor().getCollectionName())
+              .put(CORE_ATTR, core.getCoreDescriptor().getName())
+              .put(SHARD_ATTR, core.getCoreDescriptor().getCloudDescriptor().getShardId())
+              .put(
+                  REPLICA_ATTR,
+                  Utils.parseMetricsReplicaName(
+                      core.getCoreDescriptor().getCollectionName(), core.getName()))
+              .build();
+    } else {
+      baseAttributes =
+          Attributes.builder().put(CORE_ATTR, core.getCoreDescriptor().getName()).build();
+    }
+    initializeMetrics(solrMetricsContext, baseAttributes, STATISTICS_KEY);
     registerTime = new Date();
   }
 
@@ -669,6 +699,7 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
 
     // do this at the end so it only gets done if there are no exceptions
     numCloses.incrementAndGet();
+    IOUtils.closeQuietly(toClose);
     assert ObjectReleaseTracker.release(this);
   }
 
@@ -2522,8 +2553,9 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
         log.debug("autowarming result for [{}]\n\t{}", this, cacheList[i]);
       }
     }
-    warmupTime =
+    this.warmupTime =
         TimeUnit.MILLISECONDS.convert(System.nanoTime() - warmingStartTime, TimeUnit.NANOSECONDS);
+    if (warmupTimer != null) warmupTimer.record(warmupTime);
   }
 
   /** return the named generic cache */
@@ -2604,95 +2636,84 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
     return solrMetricsContext;
   }
 
-  // TODO SOLR-17458: Migrate to Otel
   @Override
   public void initializeMetrics(
-      SolrMetricsContext parentContext, Attributes attributes, String scope) {
-    parentContext.gauge(() -> name, true, "searcherName", Category.SEARCHER.toString(), scope);
-    parentContext.gauge(() -> cachingEnabled, true, "caching", Category.SEARCHER.toString(), scope);
-    parentContext.gauge(() -> openTime, true, "openedAt", Category.SEARCHER.toString(), scope);
-    parentContext.gauge(() -> warmupTime, true, "warmupTime", Category.SEARCHER.toString(), scope);
-    parentContext.gauge(
-        () -> registerTime, true, "registeredAt", Category.SEARCHER.toString(), scope);
-    parentContext.gauge(
-        fullSortCount::sum, true, "fullSortCount", Category.SEARCHER.toString(), scope);
-    parentContext.gauge(
-        skipSortCount::sum, true, "skipSortCount", Category.SEARCHER.toString(), scope);
-    final MetricsMap liveDocsCacheMetrics =
-        new MetricsMap(
-            (map) -> {
-              map.put("inserts", liveDocsInsertsCount.sum());
-              map.put("hits", liveDocsHitCount.sum());
-              map.put("naiveHits", liveDocsNaiveCacheHitCount.sum());
-            });
-    parentContext.gauge(
-        liveDocsCacheMetrics, true, "liveDocsCache", Category.SEARCHER.toString(), scope);
-    // reader stats
-    parentContext.gauge(
-        rgauge(parentContext.nullNumber(), () -> reader.numDocs()),
-        true,
-        "numDocs",
-        Category.SEARCHER.toString(),
-        scope);
-    parentContext.gauge(
-        rgauge(parentContext.nullNumber(), () -> reader.maxDoc()),
-        true,
-        "maxDoc",
-        Category.SEARCHER.toString(),
-        scope);
-    parentContext.gauge(
-        rgauge(parentContext.nullNumber(), () -> reader.maxDoc() - reader.numDocs()),
-        true,
-        "deletedDocs",
-        Category.SEARCHER.toString(),
-        scope);
-    parentContext.gauge(
-        rgauge(parentContext.nullString(), () -> reader.toString()),
-        true,
-        "reader",
-        Category.SEARCHER.toString(),
-        scope);
-    parentContext.gauge(
-        rgauge(parentContext.nullString(), () -> reader.directory().toString()),
-        true,
-        "readerDir",
-        Category.SEARCHER.toString(),
-        scope);
-    parentContext.gauge(
-        rgauge(parentContext.nullNumber(), () -> reader.getVersion()),
-        true,
-        "indexVersion",
-        Category.SEARCHER.toString(),
-        scope);
+      SolrMetricsContext solrMetricsContext, Attributes attributes, String scope) {
+    var baseAttributes =
+        attributes.toBuilder().put(CATEGORY_ATTR, Category.SEARCHER.toString()).build();
+
+    // warmupTime (ms) - timer for histogram tracking
+    warmupTimer =
+        new AttributedLongTimer(
+            solrMetricsContext.longHistogram(
+                "solr_searcher_warmup_time", "Searcher warmup time (ms)", OtelUnit.MILLISECONDS),
+            baseAttributes);
+
+    toClose.add(
+        solrMetricsContext.observableLongCounter(
+            "solr_searcher_live_docs_cache",
+            "LiveDocs cache metrics",
+            obs -> {
+              obs.record(
+                  liveDocsInsertsCount.sum(),
+                  baseAttributes.toBuilder().put(TYPE_ATTR, "inserts").build());
+              obs.record(
+                  liveDocsHitCount.sum(),
+                  baseAttributes.toBuilder().put(TYPE_ATTR, "hits").build());
+              obs.record(
+                  liveDocsNaiveCacheHitCount.sum(),
+                  baseAttributes.toBuilder().put(TYPE_ATTR, "naive_hits").build());
+            }));
+    // reader stats (numeric)
+    toClose.add(
+        solrMetricsContext.observableLongGauge(
+            "solr_searcher_index_num_docs",
+            "Number of live docs in the index",
+            obs -> {
+              try {
+                obs.record(reader.numDocs(), baseAttributes);
+              } catch (Exception ignore) {
+                // replacement for nullNumber
+              }
+            }));
+
+    toClose.add(
+        solrMetricsContext.observableLongGauge(
+            "solr_searcher_index_docs",
+            "Total number of docs in the index (including deletions)",
+            obs -> {
+              try {
+                obs.record(reader.maxDoc(), baseAttributes);
+              } catch (Exception ignore) {
+              }
+            }));
+    // indexVersion (numeric)
+    toClose.add(
+        solrMetricsContext.observableLongGauge(
+            "solr_searcher_index_version",
+            "Lucene index version",
+            obs -> {
+              try {
+                obs.record(reader.getVersion(), baseAttributes);
+              } catch (Exception ignore) {
+              }
+            }));
     // size of the currently opened commit
-    parentContext.gauge(
-        () -> {
-          try {
-            Collection<String> files = reader.getIndexCommit().getFileNames();
-            long total = 0;
-            for (String file : files) {
-              total += DirectoryFactory.sizeOf(reader.directory(), file);
-            }
-            return total;
-          } catch (Exception e) {
-            return parentContext.nullNumber();
-          }
-        },
-        true,
-        "indexCommitSize",
-        Category.SEARCHER.toString(),
-        scope);
-    // statsCache metrics
-    parentContext.gauge(
-        new MetricsMap(
-            map -> {
-              statsCache.getCacheMetrics().getSnapshot(map::putNoEx);
-              map.put("statsCacheImpl", statsCache.getClass().getSimpleName());
-            }),
-        true,
-        "statsCache",
-        Category.CACHE.toString(),
-        scope);
+    toClose.add(
+        solrMetricsContext.observableLongGauge(
+            "solr_searcher_index_commit_size_bytes",
+            "Size of the current index commit (bytes)",
+            obs -> {
+              try {
+                long total = 0L;
+                for (String file : reader.getIndexCommit().getFileNames()) {
+                  total += DirectoryFactory.sizeOf(reader.directory(), file);
+                }
+                obs.record(total, baseAttributes);
+              } catch (Exception e) {
+                // skip recording if unavailable (no nullNumber in OTel)
+              }
+            }));
   }
 
   /**
diff --git a/solr/core/src/java/org/apache/solr/search/stats/StatsCache.java b/solr/core/src/java/org/apache/solr/search/stats/StatsCache.java
index 833c40e9076..44dc7aca40e 100644
--- a/solr/core/src/java/org/apache/solr/search/stats/StatsCache.java
+++ b/solr/core/src/java/org/apache/solr/search/stats/StatsCache.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr.search.stats;
 
+import io.opentelemetry.api.common.Attributes;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.List;
@@ -31,10 +32,14 @@ import org.apache.lucene.search.Query;
 import org.apache.lucene.search.ScoreMode;
 import org.apache.lucene.search.TermStatistics;
 import org.apache.lucene.search.Weight;
+import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.core.PluginInfo;
+import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.handler.component.ResponseBuilder;
 import org.apache.solr.handler.component.ShardRequest;
 import org.apache.solr.handler.component.ShardResponse;
+import org.apache.solr.metrics.SolrMetricProducer;
+import org.apache.solr.metrics.SolrMetricsContext;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.search.QueryCommand;
 import org.apache.solr.search.SolrCache;
@@ -51,7 +56,7 @@ import org.apache.solr.util.plugin.PluginInfoInitialized;
  * is aggregated), and on each core involved in a shard request (where this data is maintained and
  * updated from the aggregator's cache).
  */
-public abstract class StatsCache implements PluginInfoInitialized {
+public abstract class StatsCache implements PluginInfoInitialized, SolrInfoBean {
   // TODO: decouple use in response from use in request context for these keys
   /** Map of terms and {@link TermStats}. */
   public static final String TERM_STATS_KEY = "solr.stats.term";
@@ -65,6 +70,9 @@ public abstract class StatsCache implements PluginInfoInitialized {
   /** List of fields in the query. */
   public static final String FIELDS_KEY = "solr.stats.fields";
 
+  private SolrMetricsContext solrMetricsContext;
+  private AutoCloseable toClose;
+
   public static final class StatsCacheMetrics {
     public final LongAdder lookups = new LongAdder();
     public final LongAdder retrieveStats = new LongAdder();
@@ -298,4 +306,91 @@ public abstract class StatsCache implements PluginInfoInitialized {
       return super.collectionStatistics(field);
     }
   }
+
+  @Override
+  public String getName() {
+    return StatsCache.class.getName();
+  }
+
+  @Override
+  public String getDescription() {
+    return "A cache of global document frequency information for selected terms";
+  }
+
+  @Override
+  public Category getCategory() {
+    return Category.CACHE;
+  }
+
+  @Override
+  public SolrMetricsContext getSolrMetricsContext() {
+    return solrMetricsContext;
+  }
+
+  @Override
+  public void initializeMetrics(
+      SolrMetricsContext solrMetricsContext, Attributes attributes, String scope) {
+    this.solrMetricsContext = solrMetricsContext;
+    var cacheBaseAttribute =
+        attributes.toBuilder()
+            .put(SolrMetricProducer.CATEGORY_ATTR, Category.CACHE.toString())
+            .build();
+    this.toClose =
+        solrMetricsContext.observableLongGauge(
+            "solr_searcher_termstats_cache",
+            "Operation counts for the searcher term statistics cache, reported per operation type",
+            obs -> {
+              var cacheMetrics = getCacheMetrics();
+              obs.record(
+                  cacheMetrics.lookups.sum(),
+                  cacheBaseAttribute.toBuilder()
+                      .put(SolrMetricProducer.TYPE_ATTR, "lookups")
+                      .build());
+              obs.record(
+                  cacheMetrics.missingGlobalFieldStats.sum(),
+                  cacheBaseAttribute.toBuilder()
+                      .put(SolrMetricProducer.TYPE_ATTR, "missing_global_field")
+                      .build());
+              obs.record(
+                  cacheMetrics.missingGlobalTermStats.sum(),
+                  cacheBaseAttribute.toBuilder()
+                      .put(SolrMetricProducer.TYPE_ATTR, "missing_global_term")
+                      .build());
+              obs.record(
+                  cacheMetrics.mergeToGlobalStats.sum(),
+                  cacheBaseAttribute.toBuilder()
+                      .put(SolrMetricProducer.TYPE_ATTR, "merge_to_global")
+                      .build());
+              obs.record(
+                  cacheMetrics.retrieveStats.sum(),
+                  cacheBaseAttribute.toBuilder()
+                      .put(SolrMetricProducer.TYPE_ATTR, "retrieve")
+                      .build());
+              obs.record(
+                  cacheMetrics.returnLocalStats.sum(),
+                  cacheBaseAttribute.toBuilder()
+                      .put(SolrMetricProducer.TYPE_ATTR, "return_local")
+                      .build());
+              obs.record(
+                  cacheMetrics.sendGlobalStats.sum(),
+                  cacheBaseAttribute.toBuilder()
+                      .put(SolrMetricProducer.TYPE_ATTR, "send_global")
+                      .build());
+              obs.record(
+                  cacheMetrics.useCachedGlobalStats.sum(),
+                  cacheBaseAttribute.toBuilder()
+                      .put(SolrMetricProducer.TYPE_ATTR, "use_cached_global")
+                      .build());
+              obs.record(
+                  cacheMetrics.receiveGlobalStats.sum(),
+                  cacheBaseAttribute.toBuilder()
+                      .put(SolrMetricProducer.TYPE_ATTR, "receive_global")
+                      .build());
+            });
+  }
+
+  @Override
+  public void close() throws IOException {
+    IOUtils.closeQuietly(toClose);
+  }
 }
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestBaseStatsCacheCloud.java b/solr/core/src/test/org/apache/solr/cloud/TestBaseStatsCacheCloud.java
index 8f475e64541..607715f9aa4 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestBaseStatsCacheCloud.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestBaseStatsCacheCloud.java
@@ -16,19 +16,24 @@
  */
 package org.apache.solr.cloud;
 
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.function.Function;
 import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.SolrRequest.METHOD;
+import org.apache.solr.client.solrj.SolrRequest.SolrRequestType;
 import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
+import org.apache.solr.client.solrj.impl.InputStreamResponseParser;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.GenericSolrRequest;
 import org.apache.solr.client.solrj.request.UpdateRequest;
 import org.apache.solr.client.solrj.response.QueryResponse;
 import org.apache.solr.common.SolrDocument;
 import org.apache.solr.common.SolrInputDocument;
+import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.embedded.JettySolrRunner;
 import org.apache.solr.search.similarities.CustomSimilarityFactory;
@@ -135,66 +140,54 @@ public abstract class TestBaseStatsCacheCloud extends SolrCloudTestCase {
     StatsCache.StatsCacheMetrics statsCacheMetrics = new StatsCache.StatsCacheMetrics();
     for (JettySolrRunner jettySolrRunner : cluster.getJettySolrRunners()) {
       try (SolrClient client = getHttpSolrClient(jettySolrRunner.getBaseUrl().toString())) {
-        NamedList<Object> metricsRsp =
-            client.request(
-                new GenericSolrRequest(
-                    SolrRequest.METHOD.GET,
-                    "/admin/metrics",
-                    params("group", "solr.core", "prefix", "CACHE.searcher.statsCache")));
-        assertNotNull(metricsRsp);
-        NamedList<Object> metricsPerReplica = (NamedList<Object>) metricsRsp.get("metrics");
-        assertNotNull("no metrics perReplica", metricsPerReplica);
-        // log.info("======= Node: " + jettySolrRunner.getBaseUrl());
-        // log.info("======= Metrics:\n" + Utils.toJSONString(metricsPerReplica));
-        metricsPerReplica.forEach(
-            (replica, metrics) -> {
-              Map<String, Object> values =
-                  (Map<String, Object>)
-                      ((NamedList<Object>) metrics).get("CACHE.searcher.statsCache");
-              values.forEach(
-                  (name, value) -> {
-                    long val = value instanceof Number ? ((Number) value).longValue() : 0;
-                    switch (name) {
-                      case "lookups":
-                        statsCacheMetrics.lookups.add(val);
-                        break;
-                      case "returnLocalStats":
-                        statsCacheMetrics.returnLocalStats.add(val);
-                        break;
-                      case "mergeToGlobalStats":
-                        statsCacheMetrics.mergeToGlobalStats.add(val);
-                        break;
-                      case "missingGlobalFieldStats":
-                        statsCacheMetrics.missingGlobalFieldStats.add(val);
-                        break;
-                      case "missingGlobalTermStats":
-                        statsCacheMetrics.missingGlobalTermStats.add(val);
-                        break;
-                      case "receiveGlobalStats":
-                        statsCacheMetrics.receiveGlobalStats.add(val);
-                        break;
-                      case "retrieveStats":
-                        statsCacheMetrics.retrieveStats.add(val);
-                        break;
-                      case "sendGlobalStats":
-                        statsCacheMetrics.sendGlobalStats.add(val);
-                        break;
-                      case "useCachedGlobalStats":
-                        statsCacheMetrics.useCachedGlobalStats.add(val);
-                        break;
-                      case "statsCacheImpl":
-                        assertTrue(
-                            "incorrect cache impl, expected"
-                                + getImplementationName()
-                                + " but was "
-                                + value,
-                            getImplementationName().endsWith((String) value));
-                        break;
-                      default:
-                        fail("Unexpected cache metrics: key=" + name + ", value=" + value);
-                    }
-                  });
-            });
+        var req =
+            new GenericSolrRequest(
+                METHOD.GET,
+                "/admin/metrics",
+                SolrRequestType.ADMIN,
+                SolrParams.of("wt", "prometheus"));
+        req.setResponseParser(new InputStreamResponseParser("prometheus"));
+
+        NamedList<Object> resp = client.request(req);
+        try (InputStream in = (InputStream) resp.get("stream")) {
+          String output = new String(in.readAllBytes(), StandardCharsets.UTF_8);
+
+          for (String line : output.lines().toList()) {
+            if (line.startsWith("solr_searcher_termstats_cache")) {
+              String type = extractTypeAttribute(line);
+              long value = extractMetricValue(line);
+              switch (type) {
+                case "lookups":
+                  statsCacheMetrics.lookups.add(value);
+                  break;
+                case "return_local":
+                  statsCacheMetrics.returnLocalStats.add(value);
+                  break;
+                case "merge_to_global":
+                  statsCacheMetrics.mergeToGlobalStats.add(value);
+                  break;
+                case "missing_global_field":
+                  statsCacheMetrics.missingGlobalFieldStats.add(value);
+                  break;
+                case "missing_global_term":
+                  statsCacheMetrics.missingGlobalTermStats.add(value);
+                  break;
+                case "receive_global":
+                  statsCacheMetrics.receiveGlobalStats.add(value);
+                  break;
+                case "retrieve":
+                  statsCacheMetrics.retrieveStats.add(value);
+                  break;
+                case "send_global":
+                  statsCacheMetrics.sendGlobalStats.add(value);
+                  break;
+                case "use_cached_global":
+                  statsCacheMetrics.useCachedGlobalStats.add(value);
+                  break;
+              }
+            }
+          }
+        }
       }
     }
     checkStatsCacheMetrics(statsCacheMetrics);
@@ -246,4 +239,23 @@ public abstract class TestBaseStatsCacheCloud extends SolrCloudTestCase {
     ureq.process(client, collectionName);
     client.commit(collectionName);
   }
+
+  /**
+   * Extract type label value from Prometheus format line
+   * "solr_searcher_stats_cache{...type="lookups",...}" -> "lookups"
+   */
+  private String extractTypeAttribute(String line) {
+    int typeStart = line.indexOf("type=\"");
+    int typeEnd = line.indexOf('"', typeStart += 6);
+    return line.substring(typeStart, typeEnd);
+  }
+
+  /**
+   * Extract numeric value from Prometheus format line. "solr_searcher_stats_cache{...} 123.0" ->
+   * 123
+   */
+  private long extractMetricValue(String line) {
+    String valueStr = line.substring(line.lastIndexOf(' ') + 1);
+    return (long) Double.parseDouble(valueStr);
+  }
 }
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/StatsReloadRaceTest.java b/solr/core/src/test/org/apache/solr/handler/admin/StatsReloadRaceTest.java
index 7cdbf10f3e2..7f88b6c8d5c 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/StatsReloadRaceTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/StatsReloadRaceTest.java
@@ -23,8 +23,9 @@ import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.CoreAdminParams;
 import org.apache.solr.common.params.CoreAdminParams.CoreAdminAction;
-import org.apache.solr.common.util.NamedList;
+import org.apache.solr.core.SolrCore;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -126,34 +127,27 @@ public class StatsReloadRaceTest extends SolrTestCaseJ4 {
     boolean found = false;
     int count = 10;
     while (!found && count-- > 0) {
-      h.getCoreContainer()
-          .getRequestHandler("/admin/metrics")
-          .handleRequest(req("prefix", "SEARCHER", "registry", registry, "compact", "true"), rsp);
-
-      NamedList<?> values = rsp.getValues();
       // this is not guaranteed to exist right away after core reload - there's a
       // small window between core load and before searcher metrics are registered,
       // so we may have to check a few times, and then fail softly if reload is not complete yet
-      NamedList<?> metrics = (NamedList<?>) values.get("metrics");
-      if (metrics == null) {
-        if (softFail) {
-          return;
-        } else {
-          fail("missing 'metrics' element in handler's output: " + values.asMap(5).toString());
-        }
-      }
-      metrics = (NamedList<?>) metrics.get(registry);
-      if (metrics.get(key) != null) {
+      SolrCore core = h.getCore();
+      var indexVersion =
+          SolrMetricTestUtils.getGaugeDatapoint(
+              core,
+              "solr_searcher_index_version",
+              SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+                  .label("category", "SEARCHER")
+                  .build());
+      if (indexVersion == null) {
+        Thread.sleep(500);
+      } else {
         found = true;
-        assertTrue(metrics.get(key) instanceof Long);
         break;
-      } else {
-        Thread.sleep(500);
       }
     }
     if (softFail && !found) {
       return;
     }
-    assertTrue("Key " + key + " not found in registry " + registry, found);
+    assertTrue("solr_searcher_index_version metric not found", found);
   }
 }
diff --git a/solr/core/src/test/org/apache/solr/search/TestIndexSearcher.java b/solr/core/src/test/org/apache/solr/search/TestIndexSearcher.java
index 649d7bbd914..4f7e0eb0809 100644
--- a/solr/core/src/test/org/apache/solr/search/TestIndexSearcher.java
+++ b/solr/core/src/test/org/apache/solr/search/TestIndexSearcher.java
@@ -16,11 +16,8 @@
  */
 package org.apache.solr.search;
 
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Metric;
 import java.io.IOException;
 import java.lang.reflect.Array;
-import java.util.Date;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
@@ -137,18 +134,10 @@ public class TestIndexSearcher extends SolrTestCaseJ4 {
     int baseRefCount = r3.getRefCount();
     assertEquals(1, baseRefCount);
 
-    Map<String, Metric> metrics = h.getCore().getCoreMetricManager().getRegistry().getMetrics();
-    @SuppressWarnings({"unchecked"})
-    Gauge<Date> g = (Gauge<Date>) metrics.get("SEARCHER.searcher.registeredAt");
-    Date sr3SearcherRegAt = g.getValue();
     assertU(commit()); // nothing has changed
     SolrQueryRequest sr4 = req("q", "foo");
     assertSame(
         "nothing changed, searcher should be the same", sr3.getSearcher(), sr4.getSearcher());
-    assertEquals(
-        "nothing changed, searcher should not have been re-registered",
-        sr3SearcherRegAt.toInstant(),
-        g.getValue().toInstant());
     IndexReader r4 = sr4.getSearcher().getRawReader();
 
     // force an index change so the registered searcher won't be the one we are testing (and
diff --git a/solr/core/src/test/org/apache/solr/search/TestMainQueryCaching.java b/solr/core/src/test/org/apache/solr/search/TestMainQueryCaching.java
index 60cd7b9ab90..23349bf643b 100644
--- a/solr/core/src/test/org/apache/solr/search/TestMainQueryCaching.java
+++ b/solr/core/src/test/org/apache/solr/search/TestMainQueryCaching.java
@@ -28,8 +28,6 @@ import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.util.ExecutorUtil;
 import org.apache.solr.common.util.SolrNamedThreadFactory;
 import org.apache.solr.core.SolrCore;
-import org.apache.solr.metrics.MetricsMap;
-import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.util.SolrMetricTestUtils;
 import org.junit.AfterClass;
 import org.junit.Before;
@@ -100,33 +98,18 @@ public class TestMainQueryCaching extends SolrTestCaseJ4 {
     return (long) SolrMetricTestUtils.getCacheSearcherOpsInserts(core, cacheName).getValue();
   }
 
-  private static long coreToSortCount(SolrCore core, String skipOrFull) {
+  private static long coreToMatchAllDocsCount(SolrCore core, String type) {
     return (long)
-        ((SolrMetricManager.GaugeWrapper<?>)
-                core.getCoreMetricManager()
-                    .getRegistry()
-                    .getMetrics()
-                    .get("SEARCHER.searcher." + skipOrFull + "SortCount"))
-            .getGauge()
+        SolrMetricTestUtils.getCounterDatapoint(
+                core,
+                "solr_searcher_live_docs_cache",
+                SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+                    .label("category", "SEARCHER")
+                    .label("type", type)
+                    .build())
             .getValue();
   }
 
-  // NOCOMMIT: Fix this once SolrIndexSearcher is migrated for OTEL
-  private static long coreToMatchAllDocsInsertCount(SolrCore core) {
-    return (long) coreToLiveDocsCacheMetrics(core).get("inserts");
-  }
-
-  private static Map<String, Object> coreToLiveDocsCacheMetrics(SolrCore core) {
-    return ((MetricsMap)
-            ((SolrMetricManager.GaugeWrapper<?>)
-                    core.getCoreMetricManager()
-                        .getRegistry()
-                        .getMetrics()
-                        .get("SEARCHER.searcher.liveDocsCache"))
-                .getGauge())
-        .getValue();
-  }
-
   private static final String SCORING_QUERY = "str:d*";
 
   // wrapped as a ConstantScoreQuery
@@ -259,10 +242,8 @@ public class TestMainQueryCaching extends SolrTestCaseJ4 {
     Map<?, ?> res = (Map<?, ?>) fromJSONString(response);
     Map<?, ?> body = (Map<?, ?>) (res.get("response"));
     SolrCore core = h.getCore();
-    assertEquals("Bad matchAllDocs insert count", 1, coreToMatchAllDocsInsertCount(core));
-    assertEquals(
-        "Bad filterCache insert count", 0, coreToInserts(core, SolrMetricTestUtils.FILTER_CACHE));
-    assertEquals("Bad full sort count", 0, coreToSortCount(core, "full"));
+    assertEquals("Bad matchAllDocs insert count", 1, coreToMatchAllDocsCount(core, "inserts"));
+    assertEquals("Bad filterCache insert count", 0, coreToInserts(core, "filterCache"));
     assertEquals("Should have exactly " + ALL_DOCS, ALL_DOCS, (long) (body.get("numFound")));
     long queryCacheInsertCount = coreToInserts(core, SolrMetricTestUtils.QUERY_RESULT_CACHE);
     if (queryCacheInsertCount == expectCounters[0]) {
@@ -271,7 +252,6 @@ public class TestMainQueryCaching extends SolrTestCaseJ4 {
       assertEquals(++expectCounters[0], queryCacheInsertCount);
       expectCounters[1]++;
     }
-    assertEquals("Bad skip sort count", expectCounters[1], coreToSortCount(core, "skip"));
   }
 
   @Test
@@ -412,13 +392,11 @@ public class TestMainQueryCaching extends SolrTestCaseJ4 {
     assertEquals(
         "Bad matchAllDocs insert count",
         (matchAllDocs ? 1 : 0),
-        coreToMatchAllDocsInsertCount(core));
+        coreToMatchAllDocsCount(core, "inserts"));
     assertEquals(
         "Bad filterCache insert count",
         expectFilterCacheInsertCount,
-        coreToInserts(core, SolrMetricTestUtils.FILTER_CACHE));
-    assertEquals("Bad full sort count", expectFullSortCount, coreToSortCount(core, "full"));
-    assertEquals("Bad skip sort count", expectSkipSortCount, coreToSortCount(core, "skip"));
+        coreToInserts(core, "filterCache"));
     assertEquals(
         "Should have exactly " + expectNumFound, expectNumFound, (long) (body.get("numFound")));
   }
@@ -472,15 +450,14 @@ public class TestMainQueryCaching extends SolrTestCaseJ4 {
       assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS));
     }
     final SolrCore core = h.getCore();
-    Map<String, Object> liveDocsCacheMetrics = coreToLiveDocsCacheMetrics(core);
 
     // the one and only liveDocs computation
-    long inserts = (long) liveDocsCacheMetrics.get("inserts");
+    long inserts = coreToMatchAllDocsCount(core, "inserts");
 
     // hits during the initial phase
-    long hits = (long) liveDocsCacheMetrics.get("hits");
+    long hits = coreToMatchAllDocsCount(core, "hits");
 
-    long naiveHits = (long) liveDocsCacheMetrics.get("naiveHits");
+    long naiveHits = coreToMatchAllDocsCount(core, "naive_hits");
 
     assertEquals(1, inserts);
     assertEquals(nThreads - 1, hits + naiveHits);


(solr) 07/33: SOLR-17806: Migrate PeerSync and PeerSyncWithLeader metrics to OTEL (#3440)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit ed7f2d116ade9af13ec79ba2bea4de8cb610f225
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Tue Jul 29 16:05:05 2025 -0400

    SOLR-17806: Migrate PeerSync and PeerSyncWithLeader metrics to OTEL (#3440)
    
    * Migrate peersync metrics to OTEL
    
    * metric utils rename
---
 .../src/java/org/apache/solr/update/PeerSync.java  | 38 +++++++++++++-------
 .../org/apache/solr/update/PeerSyncWithLeader.java | 38 ++++++++++++++------
 .../apache/solr/cloud/PeerSyncReplicationTest.java | 40 +++++++++++++++-------
 .../org/apache/solr/cloud/TestCloudRecovery.java   | 35 ++++++++-----------
 .../apache/solr/metrics/SolrMetricTestUtils.java   |  9 +++--
 5 files changed, 102 insertions(+), 58 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/update/PeerSync.java b/solr/core/src/java/org/apache/solr/update/PeerSync.java
index 0036c856a6b..e4fa8e8bc39 100644
--- a/solr/core/src/java/org/apache/solr/update/PeerSync.java
+++ b/solr/core/src/java/org/apache/solr/update/PeerSync.java
@@ -21,8 +21,6 @@ import static org.apache.solr.common.params.CommonParams.ID;
 import static org.apache.solr.update.processor.DistributedUpdateProcessor.DistribPhase.FROMLEADER;
 import static org.apache.solr.update.processor.DistributingUpdateProcessorFactory.DISTRIB_UPDATE_PARAM;
 
-import com.codahale.metrics.Counter;
-import com.codahale.metrics.Timer;
 import com.google.common.annotations.VisibleForTesting;
 import io.opentelemetry.api.common.Attributes;
 import java.io.IOException;
@@ -51,6 +49,8 @@ import org.apache.solr.handler.component.ShardRequest;
 import org.apache.solr.handler.component.ShardResponse;
 import org.apache.solr.metrics.SolrMetricProducer;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.instruments.AttributedLongCounter;
+import org.apache.solr.metrics.otel.instruments.AttributedLongTimer;
 import org.apache.solr.request.LocalSolrQueryRequest;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
@@ -90,9 +90,9 @@ public class PeerSync implements SolrMetricProducer {
   private MissedUpdatesFinder missedUpdatesFinder;
 
   // metrics
-  private Timer syncTime;
-  private Counter syncErrors;
-  private Counter syncSkipped;
+  private AttributedLongTimer syncTime;
+  private AttributedLongCounter syncErrors;
+  private AttributedLongCounter syncSkipped;
   private SolrMetricsContext solrMetricsContext;
 
   // comparator that sorts by absolute value, putting highest first
@@ -144,14 +144,28 @@ public class PeerSync implements SolrMetricProducer {
     return solrMetricsContext;
   }
 
-  // TODO SOLR-17458: Migrate to Otel
   @Override
   public void initializeMetrics(
       SolrMetricsContext parentContext, Attributes attributes, String scope) {
     this.solrMetricsContext = parentContext.getChildContext(this);
-    syncTime = solrMetricsContext.timer("time", scope, METRIC_SCOPE);
-    syncErrors = solrMetricsContext.counter("errors", scope, METRIC_SCOPE);
-    syncSkipped = solrMetricsContext.counter("skipped", scope, METRIC_SCOPE);
+    var baseAttributes =
+        attributes.toBuilder()
+            .put("category", SolrInfoBean.Category.REPLICATION.toString())
+            .build();
+    syncErrors =
+        new AttributedLongCounter(
+            solrMetricsContext.longCounter(
+                "solr_core_peer_sync_errors", "Total number of sync errors with peer"),
+            baseAttributes);
+    syncSkipped =
+        new AttributedLongCounter(
+            solrMetricsContext.longCounter(
+                "solr_core_peer_sync_skipped", "Total number of skipped syncs with peer"),
+            baseAttributes);
+    syncTime =
+        new AttributedLongTimer(
+            solrMetricsContext.longHistogram("solr_core_peer_sync_time", "Peer sync times", "ms"),
+            baseAttributes);
   }
 
   public static long percentile(List<Long> arr, float frac) {
@@ -182,7 +196,7 @@ public class PeerSync implements SolrMetricProducer {
       syncErrors.inc();
       return PeerSyncResult.failure();
     }
-    Timer.Context timerContext = null;
+    AttributedLongTimer.MetricTimer timerContext = null;
     try {
       if (log.isInfoEnabled()) {
         log.info("{} START replicas={} nUpdates={}", msg(), replicas, nUpdates);
@@ -195,7 +209,7 @@ public class PeerSync implements SolrMetricProducer {
       }
 
       // measure only when actual sync is performed
-      timerContext = syncTime.time();
+      timerContext = syncTime.start();
 
       // Fire off the requests before getting our own recent updates (for better concurrency)
       // This also allows us to avoid getting updates we don't need... if we got our updates and
@@ -274,7 +288,7 @@ public class PeerSync implements SolrMetricProducer {
       return success ? PeerSyncResult.success() : PeerSyncResult.failure();
     } finally {
       if (timerContext != null) {
-        timerContext.close();
+        timerContext.stop();
       }
     }
   }
diff --git a/solr/core/src/java/org/apache/solr/update/PeerSyncWithLeader.java b/solr/core/src/java/org/apache/solr/update/PeerSyncWithLeader.java
index d6630dc46b5..94c6252dbe1 100644
--- a/solr/core/src/java/org/apache/solr/update/PeerSyncWithLeader.java
+++ b/solr/core/src/java/org/apache/solr/update/PeerSyncWithLeader.java
@@ -22,8 +22,6 @@ import static org.apache.solr.update.PeerSync.MissedUpdatesRequest;
 import static org.apache.solr.update.PeerSync.absComparator;
 import static org.apache.solr.update.PeerSync.percentile;
 
-import com.codahale.metrics.Counter;
-import com.codahale.metrics.Timer;
 import io.opentelemetry.api.common.Attributes;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
@@ -43,6 +41,8 @@ import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.metrics.SolrMetricProducer;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.instruments.AttributedLongCounter;
+import org.apache.solr.metrics.otel.instruments.AttributedLongTimer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -67,9 +67,9 @@ public class PeerSyncWithLeader implements SolrMetricProducer {
   private Set<Long> bufferedUpdates;
 
   // metrics
-  private Timer syncTime;
-  private Counter syncErrors;
-  private Counter syncSkipped;
+  private AttributedLongTimer syncTime;
+  private AttributedLongCounter syncErrors;
+  private AttributedLongCounter syncSkipped;
   private SolrMetricsContext solrMetricsContext;
 
   public PeerSyncWithLeader(SolrCore core, String leaderUrl, int nUpdates) {
@@ -104,9 +104,25 @@ public class PeerSyncWithLeader implements SolrMetricProducer {
   public void initializeMetrics(
       SolrMetricsContext parentContext, Attributes attributes, String scope) {
     this.solrMetricsContext = parentContext.getChildContext(this);
-    syncTime = solrMetricsContext.timer("time", scope, METRIC_SCOPE);
-    syncErrors = solrMetricsContext.counter("errors", scope, METRIC_SCOPE);
-    syncSkipped = solrMetricsContext.counter("skipped", scope, METRIC_SCOPE);
+    var baseAttributes =
+        attributes.toBuilder()
+            .put("category", SolrInfoBean.Category.REPLICATION.toString())
+            .build();
+    syncErrors =
+        new AttributedLongCounter(
+            solrMetricsContext.longCounter(
+                "solr_core_sync_with_leader_errors", "Total number of sync errors with leader"),
+            baseAttributes);
+    syncSkipped =
+        new AttributedLongCounter(
+            solrMetricsContext.longCounter(
+                "solr_core_sync_with_leader_skipped", "Total number of skipped syncs with leader"),
+            baseAttributes);
+    syncTime =
+        new AttributedLongTimer(
+            solrMetricsContext.longHistogram(
+                "solr_core_sync_with_leader_time", "leader sync times", "ms"),
+            baseAttributes);
   }
 
   // start of peersync related debug messages.  includes the core name for correlation.
@@ -138,7 +154,7 @@ public class PeerSyncWithLeader implements SolrMetricProducer {
       return PeerSync.PeerSyncResult.failure();
     }
 
-    Timer.Context timerContext = null;
+    AttributedLongTimer.MetricTimer timerContext = null;
     try {
       if (log.isInfoEnabled()) {
         log.info("{} START leader={} nUpdates={}", msg(), leaderUrl, nUpdates);
@@ -154,7 +170,7 @@ public class PeerSyncWithLeader implements SolrMetricProducer {
       }
 
       // measure only when actual sync is performed
-      timerContext = syncTime.time();
+      timerContext = syncTime.start();
 
       List<Long> ourUpdates;
       try (UpdateLog.RecentUpdates recentUpdates = ulog.getRecentUpdates()) {
@@ -198,7 +214,7 @@ public class PeerSyncWithLeader implements SolrMetricProducer {
       return success ? PeerSync.PeerSyncResult.success() : PeerSync.PeerSyncResult.failure();
     } finally {
       if (timerContext != null) {
-        timerContext.close();
+        timerContext.stop();
       }
     }
   }
diff --git a/solr/core/src/test/org/apache/solr/cloud/PeerSyncReplicationTest.java b/solr/core/src/test/org/apache/solr/cloud/PeerSyncReplicationTest.java
index 128423b736a..469f3f2cd31 100644
--- a/solr/core/src/test/org/apache/solr/cloud/PeerSyncReplicationTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/PeerSyncReplicationTest.java
@@ -20,8 +20,6 @@ package org.apache.solr.cloud;
 import static java.util.Collections.singletonList;
 
 import com.carrotsearch.randomizedtesting.generators.RandomStrings;
-import com.codahale.metrics.Counter;
-import com.codahale.metrics.Metric;
 import com.codahale.metrics.MetricRegistry;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
@@ -32,7 +30,6 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -44,7 +41,9 @@ import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.common.cloud.Replica;
 import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.SolrCore;
 import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.metrics.SolrMetricTestUtils;
 import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -186,15 +185,32 @@ public class PeerSyncReplicationTest extends AbstractFullDistribZkTestBase {
         }
       }
       assertNotNull(registry);
-      Map<String, Metric> metrics = registry.getMetrics();
-      assertTrue(
-          "REPLICATION.peerSync.time present", metrics.containsKey("REPLICATION.peerSync.time"));
-      assertTrue(
-          "REPLICATION.peerSync.errors present",
-          metrics.containsKey("REPLICATION.peerSync.errors"));
-
-      Counter counter = (Counter) metrics.get("REPLICATION.peerSync.errors");
-      assertEquals(0L, counter.getCount());
+      CoreContainer cc = nodePeerSynced.jetty.getCoreContainer();
+      String coreName =
+          cc.getAllCoreNames().stream()
+              .filter(n -> n.contains(DEFAULT_TEST_COLLECTION_NAME))
+              .findFirst()
+              .orElseThrow(
+                  () ->
+                      new IllegalStateException(
+                          "Couldn't find core for " + nodePeerSynced.coreNodeName));
+      try (SolrCore core = cc.getCore(coreName)) {
+        assertTrue(
+            SolrMetricTestUtils.getHistogramDatapoint(
+                    core,
+                    "solr_core_leader_sync_time_milliseconds",
+                    SolrMetricTestUtils.newCloudLabelsBuilder(core)
+                        .label("category", "REPLICATION")
+                        .build())
+                .hasCount());
+        assertNull(
+            SolrMetricTestUtils.getCounterDatapoint(
+                core,
+                "solr_core_leader_sync_errors",
+                SolrMetricTestUtils.newCloudLabelsBuilder(core)
+                    .label("category", "REPLICATION")
+                    .build()));
+      }
       success = true;
     } finally {
       System.clearProperty("solr.index.replication.fingerprint.enabled");
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestCloudRecovery.java b/solr/core/src/test/org/apache/solr/cloud/TestCloudRecovery.java
index d64c62b5ab5..3562b4bec46 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestCloudRecovery.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestCloudRecovery.java
@@ -17,17 +17,12 @@
 
 package org.apache.solr.cloud;
 
-import com.codahale.metrics.Counter;
-import com.codahale.metrics.Metric;
-import com.codahale.metrics.Timer;
 import java.io.FileOutputStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
@@ -35,9 +30,10 @@ import org.apache.solr.client.solrj.response.QueryResponse;
 import org.apache.solr.common.cloud.ClusterStateUtil;
 import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.embedded.JettySolrRunner;
-import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.metrics.SolrMetricTestUtils;
 import org.apache.solr.update.UpdateLog;
 import org.apache.solr.update.UpdateShardHandler;
 import org.apache.solr.util.TestInjection;
@@ -140,22 +136,19 @@ public class TestCloudRecovery extends SolrCloudTestCase {
 
     // check metrics
     long replicationCount = 0;
-    long errorsCount = 0;
-    long skippedCount = 0;
     for (JettySolrRunner jetty : cluster.getJettySolrRunners()) {
-      SolrMetricManager manager = jetty.getCoreContainer().getMetricManager();
-      List<String> registryNames =
-          manager.registryNames().stream()
-              .filter(s -> s.startsWith("solr.core."))
-              .collect(Collectors.toList());
-      for (String registry : registryNames) {
-        Map<String, Metric> metrics = manager.registry(registry).getMetrics();
-        Timer timer = (Timer) metrics.get("REPLICATION.peerSync.time");
-        Counter counter = (Counter) metrics.get("REPLICATION.peerSync.errors");
-        Counter skipped = (Counter) metrics.get("REPLICATION.peerSync.skipped");
-        replicationCount += timer.getCount();
-        errorsCount += counter.getCount();
-        skippedCount += skipped.getCount();
+      CoreContainer cc = jetty.getCoreContainer();
+      for (String coreName : cc.getAllCoreNames()) {
+        try (SolrCore core = cc.getCore(coreName)) {
+          var replicationDatapoint =
+              SolrMetricTestUtils.getHistogramDatapoint(
+                  core,
+                  "solr_core_peer_sync_time_milliseconds",
+                  SolrMetricTestUtils.newCloudLabelsBuilder(core)
+                      .label("category", "REPLICATION")
+                      .build());
+          replicationCount += (replicationDatapoint != null) ? replicationDatapoint.getCount() : 0;
+        }
       }
     }
     if (onlyLeaderIndexes) {
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java b/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java
index 479615ddf93..f7878f19c8e 100644
--- a/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java
@@ -22,6 +22,7 @@ import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
 import io.prometheus.metrics.model.snapshots.CounterSnapshot;
 import io.prometheus.metrics.model.snapshots.DataPointSnapshot;
 import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
+import io.prometheus.metrics.model.snapshots.HistogramSnapshot;
 import io.prometheus.metrics.model.snapshots.Labels;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -173,9 +174,7 @@ public final class SolrMetricTestUtils {
 
   private static <T> T getDatapoint(
       SolrCore core, String metricName, Labels labels, Class<T> snapshotType) {
-
     var reader = getPrometheusMetricReader(core);
-
     return snapshotType.cast(SolrMetricTestUtils.getDataPointSnapshot(reader, metricName, labels));
   }
 
@@ -188,4 +187,10 @@ public final class SolrMetricTestUtils {
       SolrCore core, String metricName, Labels labels) {
     return getDatapoint(core, metricName, labels, CounterSnapshot.CounterDataPointSnapshot.class);
   }
+
+  public static HistogramSnapshot.HistogramDataPointSnapshot getHistogramDatapoint(
+      SolrCore core, String metricName, Labels labels) {
+    return getDatapoint(
+        core, metricName, labels, HistogramSnapshot.HistogramDataPointSnapshot.class);
+  }
 }


(solr) 14/33: SOLR-14393: Remove HIGHLIGHTER, QUERYPARSER, SPELLCHECKER metrics (#3469)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 9561d06026f1d6ab20611bc74f1337741b19264c
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Tue Aug 19 17:04:53 2025 -0400

    SOLR-14393: Remove HIGHLIGHTER, QUERYPARSER, SPELLCHECKER metrics (#3469)
    
    * Remove HIGHLIGHTER, QUERYPARSER, and SPELLCHECK
    
    * Don't make certain classes AutoClosable
    
    * Delete comment
    
    * Bring back Override
---
 .../java/org/apache/solr/core/SolrInfoBean.java    |  3 --
 .../solr/handler/component/HighlightComponent.java |  9 ------
 .../handler/component/SpellCheckComponent.java     |  9 ------
 .../highlight/BreakIteratorBoundaryScanner.java    |  9 ------
 .../org/apache/solr/highlight/DefaultEncoder.java  |  9 ------
 .../org/apache/solr/highlight/GapFragmenter.java   | 10 ------
 .../solr/highlight/HighlightingPluginBase.java     | 37 +---------------------
 .../org/apache/solr/highlight/HtmlEncoder.java     |  9 ------
 .../org/apache/solr/highlight/HtmlFormatter.java   | 10 ------
 .../org/apache/solr/highlight/RegexFragmenter.java | 10 ------
 .../solr/highlight/ScoreOrderFragmentsBuilder.java |  9 ------
 .../solr/highlight/SimpleBoundaryScanner.java      |  9 ------
 .../solr/highlight/SimpleFragListBuilder.java      | 11 -------
 .../solr/highlight/SimpleFragmentsBuilder.java     |  9 ------
 .../solr/highlight/SingleFragListBuilder.java      | 11 -------
 .../apache/solr/highlight/SolrBoundaryScanner.java |  4 +--
 .../org/apache/solr/highlight/SolrEncoder.java     |  3 +-
 .../org/apache/solr/highlight/SolrFormatter.java   |  3 +-
 .../apache/solr/highlight/SolrFragListBuilder.java |  3 +-
 .../org/apache/solr/highlight/SolrFragmenter.java  |  3 +-
 .../solr/highlight/SolrFragmentsBuilder.java       |  4 +--
 .../solr/highlight/WeightedFragListBuilder.java    | 11 -------
 .../java/org/apache/solr/search/QParserPlugin.java | 34 +-------------------
 .../solr/search/join/HashRangeQParserPlugin.java   |  5 ---
 .../apache/solr/search/RankQParserPluginTest.java  | 17 ++++------
 .../org/apache/solr/search/join/BJQParserTest.java |  1 -
 26 files changed, 15 insertions(+), 237 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/core/SolrInfoBean.java b/solr/core/src/java/org/apache/solr/core/SolrInfoBean.java
index 6957bb76f1b..d62f51e439e 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrInfoBean.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrInfoBean.java
@@ -32,9 +32,6 @@ public interface SolrInfoBean extends SolrMetricProducer {
     QUERY,
     UPDATE,
     CACHE,
-    HIGHLIGHTER,
-    QUERYPARSER,
-    SPELLCHECKER,
     SEARCHER,
     REPLICATION,
     TLOG,
diff --git a/solr/core/src/java/org/apache/solr/handler/component/HighlightComponent.java b/solr/core/src/java/org/apache/solr/handler/component/HighlightComponent.java
index 37c87653cef..99f02dcf955 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/HighlightComponent.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/HighlightComponent.java
@@ -240,20 +240,11 @@ public class HighlightComponent extends SearchComponent
     }
   }
 
-  ////////////////////////////////////////////
-  ///  SolrInfoBean
-  ////////////////////////////////////////////
-
   @Override
   public String getDescription() {
     return "Highlighting";
   }
 
-  @Override
-  public Category getCategory() {
-    return Category.HIGHLIGHTER;
-  }
-
   ////////////////////////////////////////////
   ///  highlighting response collation
   ////////////////////////////////////////////
diff --git a/solr/core/src/java/org/apache/solr/handler/component/SpellCheckComponent.java b/solr/core/src/java/org/apache/solr/handler/component/SpellCheckComponent.java
index 687b3adf030..98af4ed46dc 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/SpellCheckComponent.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/SpellCheckComponent.java
@@ -875,17 +875,8 @@ public class SpellCheckComponent extends SearchComponent implements SolrCoreAwar
     return Collections.unmodifiableMap(spellCheckers);
   }
 
-  // ///////////////////////////////////////////
-  // / SolrInfoBean
-  // //////////////////////////////////////////
-
   @Override
   public String getDescription() {
     return "A Spell Checker component";
   }
-
-  @Override
-  public Category getCategory() {
-    return Category.SPELLCHECKER;
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/highlight/BreakIteratorBoundaryScanner.java b/solr/core/src/java/org/apache/solr/highlight/BreakIteratorBoundaryScanner.java
index 8efefef314b..af11bbbf6a6 100644
--- a/solr/core/src/java/org/apache/solr/highlight/BreakIteratorBoundaryScanner.java
+++ b/solr/core/src/java/org/apache/solr/highlight/BreakIteratorBoundaryScanner.java
@@ -63,13 +63,4 @@ public class BreakIteratorBoundaryScanner extends SolrBoundaryScanner {
 
     return new org.apache.lucene.search.vectorhighlight.BreakIteratorBoundaryScanner(bi);
   }
-
-  ///////////////////////////////////////////////////////////////////////
-  //////////////////////// SolrInfoMBeans methods ///////////////////////
-  ///////////////////////////////////////////////////////////////////////
-
-  @Override
-  public String getDescription() {
-    return "BreakIteratorBoundaryScanner";
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/highlight/DefaultEncoder.java b/solr/core/src/java/org/apache/solr/highlight/DefaultEncoder.java
index a648a1f8ab4..c049b5e313b 100644
--- a/solr/core/src/java/org/apache/solr/highlight/DefaultEncoder.java
+++ b/solr/core/src/java/org/apache/solr/highlight/DefaultEncoder.java
@@ -26,13 +26,4 @@ public class DefaultEncoder extends HighlightingPluginBase implements SolrEncode
   public Encoder getEncoder(String fieldName, SolrParams params) {
     return new org.apache.lucene.search.highlight.DefaultEncoder();
   }
-
-  ///////////////////////////////////////////////////////////////////////
-  //////////////////////// SolrInfoMBeans methods ///////////////////////
-  ///////////////////////////////////////////////////////////////////////
-
-  @Override
-  public String getDescription() {
-    return "DefaultEncoder";
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/highlight/GapFragmenter.java b/solr/core/src/java/org/apache/solr/highlight/GapFragmenter.java
index 4dc42ef27df..3a5f845b1d0 100644
--- a/solr/core/src/java/org/apache/solr/highlight/GapFragmenter.java
+++ b/solr/core/src/java/org/apache/solr/highlight/GapFragmenter.java
@@ -28,21 +28,11 @@ import org.apache.solr.common.params.SolrParams;
 public class GapFragmenter extends HighlightingPluginBase implements SolrFragmenter {
   @Override
   public Fragmenter getFragmenter(String fieldName, SolrParams params) {
-    numRequests.inc();
     params = SolrParams.wrapDefaults(params, defaults);
 
     int fragsize = params.getFieldInt(fieldName, HighlightParams.FRAGSIZE, 100);
     return (fragsize <= 0) ? new NullFragmenter() : new LuceneGapFragmenter(fragsize);
   }
-
-  ///////////////////////////////////////////////////////////////////////
-  //////////////////////// SolrInfoMBeans methods ///////////////////////
-  ///////////////////////////////////////////////////////////////////////
-
-  @Override
-  public String getDescription() {
-    return "GapFragmenter";
-  }
 }
 
 /**
diff --git a/solr/core/src/java/org/apache/solr/highlight/HighlightingPluginBase.java b/solr/core/src/java/org/apache/solr/highlight/HighlightingPluginBase.java
index c0e45ef582f..af38f8f0257 100644
--- a/solr/core/src/java/org/apache/solr/highlight/HighlightingPluginBase.java
+++ b/solr/core/src/java/org/apache/solr/highlight/HighlightingPluginBase.java
@@ -16,22 +16,15 @@
  */
 package org.apache.solr.highlight;
 
-import com.codahale.metrics.Counter;
-import io.opentelemetry.api.common.Attributes;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.NamedList;
-import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.metrics.SolrMetricsContext;
 
 /**
  * @since solr 1.3
  */
-public abstract class HighlightingPluginBase implements SolrInfoBean {
-  protected Counter numRequests;
+public abstract class HighlightingPluginBase {
   protected SolrParams defaults;
-  protected Set<String> metricNames = ConcurrentHashMap.newKeySet(1);
   protected SolrMetricsContext solrMetricsContext;
 
   public void init(NamedList<?> args) {
@@ -42,32 +35,4 @@ public abstract class HighlightingPluginBase implements SolrInfoBean {
       }
     }
   }
-
-  //////////////////////// SolrInfoMBeans methods //////////////////////
-
-  @Override
-  public String getName() {
-    return this.getClass().getName();
-  }
-
-  @Override
-  public abstract String getDescription();
-
-  @Override
-  public Category getCategory() {
-    return Category.HIGHLIGHTER;
-  }
-
-  @Override
-  public SolrMetricsContext getSolrMetricsContext() {
-    return solrMetricsContext;
-  }
-
-  // TODO SOLR-17458: Migrate to OTEL
-  @Override
-  public void initializeMetrics(
-      SolrMetricsContext parentContext, Attributes attributes, String scope) {
-    solrMetricsContext = parentContext.getChildContext(this);
-    numRequests = solrMetricsContext.counter("requests", getCategory().toString(), scope);
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/highlight/HtmlEncoder.java b/solr/core/src/java/org/apache/solr/highlight/HtmlEncoder.java
index 9a8190d5f4a..3344b454846 100644
--- a/solr/core/src/java/org/apache/solr/highlight/HtmlEncoder.java
+++ b/solr/core/src/java/org/apache/solr/highlight/HtmlEncoder.java
@@ -27,13 +27,4 @@ public class HtmlEncoder extends HighlightingPluginBase implements SolrEncoder {
   public Encoder getEncoder(String fieldName, SolrParams params) {
     return new SimpleHTMLEncoder();
   }
-
-  ///////////////////////////////////////////////////////////////////////
-  //////////////////////// SolrInfoMBeans methods ///////////////////////
-  ///////////////////////////////////////////////////////////////////////
-
-  @Override
-  public String getDescription() {
-    return "HtmlEncoder";
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/highlight/HtmlFormatter.java b/solr/core/src/java/org/apache/solr/highlight/HtmlFormatter.java
index bb82e866054..a89e792b063 100644
--- a/solr/core/src/java/org/apache/solr/highlight/HtmlFormatter.java
+++ b/solr/core/src/java/org/apache/solr/highlight/HtmlFormatter.java
@@ -25,20 +25,10 @@ import org.apache.solr.common.params.SolrParams;
 public class HtmlFormatter extends HighlightingPluginBase implements SolrFormatter {
   @Override
   public Formatter getFormatter(String fieldName, SolrParams params) {
-    numRequests.inc();
     params = SolrParams.wrapDefaults(params, defaults);
 
     return new SimpleHTMLFormatter(
         params.getFieldParam(fieldName, HighlightParams.SIMPLE_PRE, "<em>"),
         params.getFieldParam(fieldName, HighlightParams.SIMPLE_POST, "</em>"));
   }
-
-  ///////////////////////////////////////////////////////////////////////
-  //////////////////////// SolrInfoMBeans methods ///////////////////////
-  ///////////////////////////////////////////////////////////////////////
-
-  @Override
-  public String getDescription() {
-    return "HtmlFormatter";
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/highlight/RegexFragmenter.java b/solr/core/src/java/org/apache/solr/highlight/RegexFragmenter.java
index e55923ddb0c..6370d9a9fe3 100644
--- a/solr/core/src/java/org/apache/solr/highlight/RegexFragmenter.java
+++ b/solr/core/src/java/org/apache/solr/highlight/RegexFragmenter.java
@@ -56,7 +56,6 @@ public class RegexFragmenter extends HighlightingPluginBase implements SolrFragm
 
   @Override
   public Fragmenter getFragmenter(String fieldName, SolrParams params) {
-    numRequests.inc();
     params = SolrParams.wrapDefaults(params, defaults);
 
     int fragsize =
@@ -85,13 +84,4 @@ public class RegexFragmenter extends HighlightingPluginBase implements SolrFragm
 
     return new LuceneRegexFragmenter(fragsize, increment, slop, maxchars, p);
   }
-
-  ///////////////////////////////////////////////////////////////////////
-  //////////////////////// SolrInfoMBeans methods ///////////////////////
-  ///////////////////////////////////////////////////////////////////////
-
-  @Override
-  public String getDescription() {
-    return "RegexFragmenter (" + defaultPatternRaw + ")";
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/highlight/ScoreOrderFragmentsBuilder.java b/solr/core/src/java/org/apache/solr/highlight/ScoreOrderFragmentsBuilder.java
index fdd43efd0ff..fa8159cf0ab 100644
--- a/solr/core/src/java/org/apache/solr/highlight/ScoreOrderFragmentsBuilder.java
+++ b/solr/core/src/java/org/apache/solr/highlight/ScoreOrderFragmentsBuilder.java
@@ -31,13 +31,4 @@ public class ScoreOrderFragmentsBuilder extends SolrFragmentsBuilder {
     sofb.setMultiValuedSeparator(getMultiValuedSeparatorChar(params));
     return sofb;
   }
-
-  ///////////////////////////////////////////////////////////////////////
-  //////////////////////// SolrInfoMBeans methods ///////////////////////
-  ///////////////////////////////////////////////////////////////////////
-
-  @Override
-  public String getDescription() {
-    return "ScoreOrderFragmentsBuilder";
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/highlight/SimpleBoundaryScanner.java b/solr/core/src/java/org/apache/solr/highlight/SimpleBoundaryScanner.java
index 30fdcb0ad90..76d77a218ee 100644
--- a/solr/core/src/java/org/apache/solr/highlight/SimpleBoundaryScanner.java
+++ b/solr/core/src/java/org/apache/solr/highlight/SimpleBoundaryScanner.java
@@ -32,13 +32,4 @@ public class SimpleBoundaryScanner extends SolrBoundaryScanner {
     }
     return new org.apache.lucene.search.vectorhighlight.SimpleBoundaryScanner(maxScan, chars);
   }
-
-  ///////////////////////////////////////////////////////////////////////
-  //////////////////////// SolrInfoMBeans methods ///////////////////////
-  ///////////////////////////////////////////////////////////////////////
-
-  @Override
-  public String getDescription() {
-    return "SimpleBoundaryScanner";
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/highlight/SimpleFragListBuilder.java b/solr/core/src/java/org/apache/solr/highlight/SimpleFragListBuilder.java
index efac2c56a97..d0c7f7d2a22 100644
--- a/solr/core/src/java/org/apache/solr/highlight/SimpleFragListBuilder.java
+++ b/solr/core/src/java/org/apache/solr/highlight/SimpleFragListBuilder.java
@@ -27,17 +27,6 @@ public class SimpleFragListBuilder extends HighlightingPluginBase implements Sol
     // If that ever changes, it should wrap them with defaults...
     // params = SolrParams.wrapDefaults(params, defaults)
 
-    numRequests.inc();
-
     return new org.apache.lucene.search.vectorhighlight.SimpleFragListBuilder();
   }
-
-  ///////////////////////////////////////////////////////////////////////
-  //////////////////////// SolrInfoMBeans methods ///////////////////////
-  ///////////////////////////////////////////////////////////////////////
-
-  @Override
-  public String getDescription() {
-    return "SimpleFragListBuilder";
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/highlight/SimpleFragmentsBuilder.java b/solr/core/src/java/org/apache/solr/highlight/SimpleFragmentsBuilder.java
index 4ed6926a32a..c32191c9878 100644
--- a/solr/core/src/java/org/apache/solr/highlight/SimpleFragmentsBuilder.java
+++ b/solr/core/src/java/org/apache/solr/highlight/SimpleFragmentsBuilder.java
@@ -30,13 +30,4 @@ public class SimpleFragmentsBuilder extends SolrFragmentsBuilder {
     sfb.setMultiValuedSeparator(getMultiValuedSeparatorChar(params));
     return sfb;
   }
-
-  ///////////////////////////////////////////////////////////////////////
-  //////////////////////// SolrInfoMBeans methods ///////////////////////
-  ///////////////////////////////////////////////////////////////////////
-
-  @Override
-  public String getDescription() {
-    return "SimpleFragmentsBuilder";
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/highlight/SingleFragListBuilder.java b/solr/core/src/java/org/apache/solr/highlight/SingleFragListBuilder.java
index e1741583814..a53c29d9341 100644
--- a/solr/core/src/java/org/apache/solr/highlight/SingleFragListBuilder.java
+++ b/solr/core/src/java/org/apache/solr/highlight/SingleFragListBuilder.java
@@ -27,17 +27,6 @@ public class SingleFragListBuilder extends HighlightingPluginBase implements Sol
     // If that ever changes, it should wrap them with defaults...
     // params = SolrParams.wrapDefaults(params, defaults)
 
-    numRequests.inc();
-
     return new org.apache.lucene.search.vectorhighlight.SingleFragListBuilder();
   }
-
-  ///////////////////////////////////////////////////////////////////////
-  //////////////////////// SolrInfoMBeans methods ///////////////////////
-  ///////////////////////////////////////////////////////////////////////
-
-  @Override
-  public String getDescription() {
-    return "SingleFragListBuilder";
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/highlight/SolrBoundaryScanner.java b/solr/core/src/java/org/apache/solr/highlight/SolrBoundaryScanner.java
index 7981fa1051f..eb70190bbd2 100644
--- a/solr/core/src/java/org/apache/solr/highlight/SolrBoundaryScanner.java
+++ b/solr/core/src/java/org/apache/solr/highlight/SolrBoundaryScanner.java
@@ -18,14 +18,12 @@ package org.apache.solr.highlight;
 
 import org.apache.lucene.search.vectorhighlight.BoundaryScanner;
 import org.apache.solr.common.params.SolrParams;
-import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.util.plugin.NamedListInitializedPlugin;
 
 public abstract class SolrBoundaryScanner extends HighlightingPluginBase
-    implements SolrInfoBean, NamedListInitializedPlugin {
+    implements NamedListInitializedPlugin {
 
   public BoundaryScanner getBoundaryScanner(String fieldName, SolrParams params) {
-    numRequests.inc();
     params = SolrParams.wrapDefaults(params, defaults);
 
     return get(fieldName, params);
diff --git a/solr/core/src/java/org/apache/solr/highlight/SolrEncoder.java b/solr/core/src/java/org/apache/solr/highlight/SolrEncoder.java
index f47f3ab7849..4db06d01f3e 100644
--- a/solr/core/src/java/org/apache/solr/highlight/SolrEncoder.java
+++ b/solr/core/src/java/org/apache/solr/highlight/SolrEncoder.java
@@ -18,10 +18,9 @@ package org.apache.solr.highlight;
 
 import org.apache.lucene.search.highlight.Encoder;
 import org.apache.solr.common.params.SolrParams;
-import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.util.plugin.NamedListInitializedPlugin;
 
-public interface SolrEncoder extends SolrInfoBean, NamedListInitializedPlugin {
+public interface SolrEncoder extends NamedListInitializedPlugin {
   /**
    * Return an {@link org.apache.lucene.search.highlight.Encoder} appropriate for this field.
    *
diff --git a/solr/core/src/java/org/apache/solr/highlight/SolrFormatter.java b/solr/core/src/java/org/apache/solr/highlight/SolrFormatter.java
index e7558bab9bf..ec29701e943 100644
--- a/solr/core/src/java/org/apache/solr/highlight/SolrFormatter.java
+++ b/solr/core/src/java/org/apache/solr/highlight/SolrFormatter.java
@@ -18,10 +18,9 @@ package org.apache.solr.highlight;
 
 import org.apache.lucene.search.highlight.Formatter;
 import org.apache.solr.common.params.SolrParams;
-import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.util.plugin.NamedListInitializedPlugin;
 
-public interface SolrFormatter extends SolrInfoBean, NamedListInitializedPlugin {
+public interface SolrFormatter extends NamedListInitializedPlugin {
 
   /**
    * Return a {@link org.apache.lucene.search.highlight.Formatter} appropriate for this field.
diff --git a/solr/core/src/java/org/apache/solr/highlight/SolrFragListBuilder.java b/solr/core/src/java/org/apache/solr/highlight/SolrFragListBuilder.java
index a8ef452debd..13fa7748481 100644
--- a/solr/core/src/java/org/apache/solr/highlight/SolrFragListBuilder.java
+++ b/solr/core/src/java/org/apache/solr/highlight/SolrFragListBuilder.java
@@ -18,10 +18,9 @@ package org.apache.solr.highlight;
 
 import org.apache.lucene.search.vectorhighlight.FragListBuilder;
 import org.apache.solr.common.params.SolrParams;
-import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.util.plugin.NamedListInitializedPlugin;
 
-public interface SolrFragListBuilder extends SolrInfoBean, NamedListInitializedPlugin {
+public interface SolrFragListBuilder extends NamedListInitializedPlugin {
   /**
    * Return a FragListBuilder.
    *
diff --git a/solr/core/src/java/org/apache/solr/highlight/SolrFragmenter.java b/solr/core/src/java/org/apache/solr/highlight/SolrFragmenter.java
index e3ecd5d0b7d..1d89f4627a0 100644
--- a/solr/core/src/java/org/apache/solr/highlight/SolrFragmenter.java
+++ b/solr/core/src/java/org/apache/solr/highlight/SolrFragmenter.java
@@ -18,10 +18,9 @@ package org.apache.solr.highlight;
 
 import org.apache.lucene.search.highlight.Fragmenter;
 import org.apache.solr.common.params.SolrParams;
-import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.util.plugin.NamedListInitializedPlugin;
 
-public interface SolrFragmenter extends SolrInfoBean, NamedListInitializedPlugin {
+public interface SolrFragmenter extends NamedListInitializedPlugin {
   /**
    * Return a {@link org.apache.lucene.search.highlight.Fragmenter} appropriate for this field.
    *
diff --git a/solr/core/src/java/org/apache/solr/highlight/SolrFragmentsBuilder.java b/solr/core/src/java/org/apache/solr/highlight/SolrFragmentsBuilder.java
index 50c1dd6818d..22b22e89303 100644
--- a/solr/core/src/java/org/apache/solr/highlight/SolrFragmentsBuilder.java
+++ b/solr/core/src/java/org/apache/solr/highlight/SolrFragmentsBuilder.java
@@ -21,11 +21,10 @@ import org.apache.lucene.search.vectorhighlight.FragmentsBuilder;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.HighlightParams;
 import org.apache.solr.common.params.SolrParams;
-import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.util.plugin.NamedListInitializedPlugin;
 
 public abstract class SolrFragmentsBuilder extends HighlightingPluginBase
-    implements SolrInfoBean, NamedListInitializedPlugin {
+    implements NamedListInitializedPlugin {
 
   public static final String DEFAULT_PRE_TAGS = "<em>";
   public static final String DEFAULT_POST_TAGS = "</em>";
@@ -38,7 +37,6 @@ public abstract class SolrFragmentsBuilder extends HighlightingPluginBase
    * @return An appropriate {@link org.apache.lucene.search.vectorhighlight.FragmentsBuilder}.
    */
   public FragmentsBuilder getFragmentsBuilder(SolrParams params, BoundaryScanner bs) {
-    numRequests.inc();
     params = SolrParams.wrapDefaults(params, defaults);
 
     return getFragmentsBuilder(params, getPreTags(params, null), getPostTags(params, null), bs);
diff --git a/solr/core/src/java/org/apache/solr/highlight/WeightedFragListBuilder.java b/solr/core/src/java/org/apache/solr/highlight/WeightedFragListBuilder.java
index f2029fd1882..b0f2f9c1da5 100644
--- a/solr/core/src/java/org/apache/solr/highlight/WeightedFragListBuilder.java
+++ b/solr/core/src/java/org/apache/solr/highlight/WeightedFragListBuilder.java
@@ -27,17 +27,6 @@ public class WeightedFragListBuilder extends HighlightingPluginBase implements S
     // If that ever changes, it should wrap them with defaults...
     // params = SolrParams.wrapDefaults(params, defaults)
 
-    numRequests.inc();
-
     return new org.apache.lucene.search.vectorhighlight.WeightedFragListBuilder();
   }
-
-  ///////////////////////////////////////////////////////////////////////
-  //////////////////////// SolrInfoMBeans methods ///////////////////////
-  ///////////////////////////////////////////////////////////////////////
-
-  @Override
-  public String getDescription() {
-    return "WeightedFragListBuilder";
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/search/QParserPlugin.java b/solr/core/src/java/org/apache/solr/search/QParserPlugin.java
index 9dcca155e8d..80855aa0bdf 100644
--- a/solr/core/src/java/org/apache/solr/search/QParserPlugin.java
+++ b/solr/core/src/java/org/apache/solr/search/QParserPlugin.java
@@ -16,13 +16,10 @@
  */
 package org.apache.solr.search;
 
-import io.opentelemetry.api.common.Attributes;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import org.apache.solr.common.params.SolrParams;
-import org.apache.solr.core.SolrInfoBean;
-import org.apache.solr.metrics.SolrMetricsContext;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.search.join.BlockJoinChildQParserPlugin;
 import org.apache.solr.search.join.BlockJoinParentQParserPlugin;
@@ -35,7 +32,7 @@ import org.apache.solr.search.neural.KnnQParserPlugin;
 import org.apache.solr.search.neural.VectorSimilarityQParserPlugin;
 import org.apache.solr.util.plugin.NamedListInitializedPlugin;
 
-public abstract class QParserPlugin implements NamedListInitializedPlugin, SolrInfoBean {
+public abstract class QParserPlugin implements NamedListInitializedPlugin {
   /** internal use - name of the default parser */
   public static final String DEFAULT_QTYPE = LuceneQParserPlugin.NAME;
 
@@ -104,33 +101,4 @@ public abstract class QParserPlugin implements NamedListInitializedPlugin, SolrI
    */
   public abstract QParser createParser(
       String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req);
-
-  @Override
-  public String getName() {
-    // TODO: ideally use the NAME property that each qparser plugin has
-
-    return this.getClass().getName();
-  }
-
-  @Override
-  public String getDescription() {
-    return ""; // UI required non-null to work
-  }
-
-  @Override
-  public Category getCategory() {
-    return Category.QUERYPARSER;
-  }
-
-  @Override
-  public void initializeMetrics(
-      SolrMetricsContext parentContext, Attributes attributes, String scope) {
-    // by default do nothing
-  }
-
-  // by default no metrics
-  @Override
-  public SolrMetricsContext getSolrMetricsContext() {
-    return null;
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/search/join/HashRangeQParserPlugin.java b/solr/core/src/java/org/apache/solr/search/join/HashRangeQParserPlugin.java
index bdf237d4250..1cb408cf050 100644
--- a/solr/core/src/java/org/apache/solr/search/join/HashRangeQParserPlugin.java
+++ b/solr/core/src/java/org/apache/solr/search/join/HashRangeQParserPlugin.java
@@ -36,9 +36,4 @@ public class HashRangeQParserPlugin extends QParserPlugin {
       String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
     return new HashRangeQParser(qstr, localParams, params, req);
   }
-
-  @Override
-  public String getName() {
-    return NAME;
-  }
 }
diff --git a/solr/core/src/test/org/apache/solr/search/RankQParserPluginTest.java b/solr/core/src/test/org/apache/solr/search/RankQParserPluginTest.java
index 1cfc840e7ef..5d929230e46 100644
--- a/solr/core/src/test/org/apache/solr/search/RankQParserPluginTest.java
+++ b/solr/core/src/test/org/apache/solr/search/RankQParserPluginTest.java
@@ -56,11 +56,10 @@ public class RankQParserPluginTest extends SolrTestCaseJ4 {
   }
 
   public void testCreateParser() throws IOException {
-    try (RankQParserPlugin rankQPPlugin = new RankQParserPlugin()) {
-      QParser parser = rankQPPlugin.createParser("", SolrParams.of(), SolrParams.of(), req());
-      assertNotNull(parser);
-      assertTrue(parser instanceof RankQParser);
-    }
+    RankQParserPlugin rankQPPlugin = new RankQParserPlugin();
+    QParser parser = rankQPPlugin.createParser("", SolrParams.of(), SolrParams.of(), req());
+    assertNotNull(parser);
+    assertTrue(parser instanceof RankQParser);
   }
 
   public void testSyntaxErrors() {
@@ -396,10 +395,8 @@ public class RankQParserPluginTest extends SolrTestCaseJ4 {
     assertThat(se.getMessage(), CoreMatchers.containsString(expectedExceptionMsg));
   }
 
-  private RankQParser getRankQParser(SolrParams localParams, SolrQueryRequest req)
-      throws IOException {
-    try (RankQParserPlugin rankQPPlugin = new RankQParserPlugin()) {
-      return (RankQParser) rankQPPlugin.createParser("", localParams, req.getParams(), req);
-    }
+  private RankQParser getRankQParser(SolrParams localParams, SolrQueryRequest req) {
+    RankQParserPlugin rankQPPlugin = new RankQParserPlugin();
+    return (RankQParser) rankQPPlugin.createParser("", localParams, req.getParams(), req);
   }
 }
diff --git a/solr/core/src/test/org/apache/solr/search/join/BJQParserTest.java b/solr/core/src/test/org/apache/solr/search/join/BJQParserTest.java
index 62ab2528709..66826e34ae8 100644
--- a/solr/core/src/test/org/apache/solr/search/join/BJQParserTest.java
+++ b/solr/core/src/test/org/apache/solr/search/join/BJQParserTest.java
@@ -473,7 +473,6 @@ public class BJQParserTest extends SolrTestCaseJ4 {
     final BlockJoinParentQParserPlugin blockJoinParentQParserPlugin =
         new BlockJoinParentQParserPlugin();
     blockJoinParentQParserPlugin.init(null);
-    blockJoinParentQParserPlugin.close();
   }
 
   private static final String eParent[] =


(solr) 11/33: SOLR-17827: Remove JXM, SLF4J and Graphite reporters (#3443)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 3d35f4defe9708ec7c8c51ed3fa50dd99b898b91
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Wed Aug 6 17:35:10 2025 -0400

    SOLR-17827: Remove JXM, SLF4J and Graphite reporters (#3443)
    
    * Deprecate JMX, SLF4J and Graphite reporters
    
    * Bring back client reporter cache
    
    * Add note to Solr 10 changes
---
 .../java/org/apache/solr/core/SolrXmlConfig.java   |  37 +-
 .../metrics/reporters/SolrGraphiteReporter.java    | 117 ----
 .../solr/metrics/reporters/SolrJmxReporter.java    | 254 -------
 .../solr/metrics/reporters/SolrSlf4jReporter.java  | 189 -----
 .../metrics/reporters/jmx/JmxMetricsReporter.java  | 777 ---------------------
 .../reporters/jmx/JmxObjectNameFactory.java        | 169 -----
 .../solr/metrics/reporters/jmx/package-info.java   |  22 -
 .../org/apache/solr/core/TestJmxIntegration.java   | 279 --------
 .../apache/solr/metrics/SolrMetricManagerTest.java |  71 --
 .../solr/metrics/SolrMetricsIntegrationTest.java   |  64 --
 .../reporters/SolrGraphiteReporterTest.java        | 123 ----
 .../reporters/SolrJmxReporterCloudTest.java        | 128 ----
 .../metrics/reporters/SolrJmxReporterTest.java     | 316 ---------
 .../metrics/reporters/SolrSlf4jReporterTest.java   | 115 ---
 .../pages/major-changes-in-solr-10.adoc            |   3 +-
 .../src/java/org/apache/solr/util/TestHarness.java |  12 +-
 16 files changed, 4 insertions(+), 2672 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java b/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
index 1206dea0d3a..84506708f6b 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
@@ -27,7 +27,6 @@ import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -37,7 +36,6 @@ import java.util.Set;
 import java.util.function.Consumer;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
-import javax.management.MBeanServer;
 import org.apache.solr.client.solrj.impl.SolrHttpConstants;
 import org.apache.solr.cloud.ClusterSingleton;
 import org.apache.solr.cluster.placement.PlacementPluginFactory;
@@ -49,13 +47,11 @@ import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.common.util.Utils;
 import org.apache.solr.logging.LogWatcherConfig;
-import org.apache.solr.metrics.reporters.SolrJmxReporter;
 import org.apache.solr.search.CacheConfig;
 import org.apache.solr.servlet.SolrDispatchFilter;
 import org.apache.solr.update.UpdateShardHandlerConfig;
 import org.apache.solr.util.DOMConfigNode;
 import org.apache.solr.util.DataConfigNode;
-import org.apache.solr.util.JmxUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.xml.sax.InputSource;
@@ -737,8 +733,7 @@ public class SolrXmlConfig {
                   : Integer.parseInt(threadsCachingIntervalSeconds.toString())));
     }
 
-    PluginInfo[] reporterPlugins = getMetricReporterPluginInfos(metrics);
-    return builder.setMetricReporterPlugins(reporterPlugins).build();
+    return builder.build();
   }
 
   private static Map<String, CacheConfig> getCachesConfig(
@@ -768,36 +763,6 @@ public class SolrXmlConfig {
     return o;
   }
 
-  private static PluginInfo[] getMetricReporterPluginInfos(ConfigNode metrics) {
-    List<PluginInfo> configs = new ArrayList<>();
-    boolean hasJmxReporter = false;
-    for (ConfigNode node : metrics.getAll("reporter")) {
-      PluginInfo info = getPluginInfo(node);
-      if (info == null) {
-        continue;
-      }
-      String clazz = info.className;
-      if (clazz != null && clazz.equals(SolrJmxReporter.class.getName())) {
-        hasJmxReporter = true;
-      }
-      configs.add(info);
-    }
-
-    // if there's an MBean server running but there was no JMX reporter then add a default one
-    MBeanServer mBeanServer = JmxUtil.findFirstMBeanServer();
-    if (mBeanServer != null && !hasJmxReporter) {
-      log.debug(
-          "MBean server found: {}, but no JMX reporters were configured - adding default JMX reporter.",
-          mBeanServer);
-      Map<String, Object> attributes = new HashMap<>();
-      attributes.put("name", "default");
-      attributes.put("class", SolrJmxReporter.class.getName());
-      PluginInfo defaultPlugin = new PluginInfo("reporter", attributes);
-      configs.add(defaultPlugin);
-    }
-    return configs.toArray(new PluginInfo[0]);
-  }
-
   /**
    * Deprecated as of 9.3, will be removed in 10.0
    *
diff --git a/solr/core/src/java/org/apache/solr/metrics/reporters/SolrGraphiteReporter.java b/solr/core/src/java/org/apache/solr/metrics/reporters/SolrGraphiteReporter.java
deleted file mode 100644
index b63fbbcd28b..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/reporters/SolrGraphiteReporter.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.reporters;
-
-import com.codahale.metrics.MetricFilter;
-import com.codahale.metrics.graphite.Graphite;
-import com.codahale.metrics.graphite.GraphiteReporter;
-import com.codahale.metrics.graphite.GraphiteSender;
-import com.codahale.metrics.graphite.PickledGraphite;
-import java.io.IOException;
-import java.util.concurrent.TimeUnit;
-import org.apache.solr.metrics.FilteringSolrMetricReporter;
-import org.apache.solr.metrics.SolrMetricManager;
-
-/** Metrics reporter that wraps {@link com.codahale.metrics.graphite.GraphiteReporter}. */
-public class SolrGraphiteReporter extends FilteringSolrMetricReporter {
-
-  private String host = null;
-  private int port = -1;
-  private boolean pickled = false;
-  private String instancePrefix = null;
-  private GraphiteReporter reporter = null;
-
-  private static final ReporterClientCache<GraphiteSender> serviceRegistry =
-      new ReporterClientCache<>();
-
-  /**
-   * Create a Graphite reporter for metrics managed in a named registry.
-   *
-   * @param metricManager metric manager instance that manages the selected registry
-   * @param registryName registry to use, one of registries managed by {@link SolrMetricManager}
-   */
-  public SolrGraphiteReporter(SolrMetricManager metricManager, String registryName) {
-    super(metricManager, registryName);
-  }
-
-  public void setHost(String host) {
-    this.host = host;
-  }
-
-  public void setPort(int port) {
-    this.port = port;
-  }
-
-  public void setPrefix(String prefix) {
-    this.instancePrefix = prefix;
-  }
-
-  public void setPickled(boolean pickled) {
-    this.pickled = pickled;
-  }
-
-  @Override
-  protected void doInit() {
-    if (reporter != null) {
-      throw new IllegalStateException("Already started once?");
-    }
-    GraphiteSender graphite;
-    String id = host + ":" + port + ":" + pickled;
-    if (pickled) {
-      graphite = new PickledGraphite(host, port);
-    } else {
-      graphite = new Graphite(host, port);
-    }
-    if (instancePrefix == null) {
-      instancePrefix = registryName;
-    } else {
-      instancePrefix = instancePrefix + "." + registryName;
-    }
-    GraphiteReporter.Builder builder =
-        GraphiteReporter.forRegistry(metricManager.registry(registryName))
-            .prefixedWith(instancePrefix)
-            .convertRatesTo(TimeUnit.SECONDS)
-            .convertDurationsTo(TimeUnit.MILLISECONDS);
-    final MetricFilter filter = newMetricFilter();
-    builder = builder.filter(filter);
-    reporter = builder.build(graphite);
-    reporter.start(period, TimeUnit.SECONDS);
-  }
-
-  @Override
-  protected void validate() throws IllegalStateException {
-    if (host == null) {
-      throw new IllegalStateException(
-          "Init argument 'host' must be set to a valid Graphite server name.");
-    }
-    if (port == -1) {
-      throw new IllegalStateException(
-          "Init argument 'port' must be set to a valid Graphite server port.");
-    }
-    if (period < 1) {
-      throw new IllegalStateException(
-          "Init argument 'period' is in time unit 'seconds' and must be at least 1.");
-    }
-  }
-
-  @Override
-  public void close() throws IOException {
-    if (reporter != null) {
-      reporter.close();
-    }
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/reporters/SolrJmxReporter.java b/solr/core/src/java/org/apache/solr/metrics/reporters/SolrJmxReporter.java
deleted file mode 100644
index bbcaa8a494a..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/reporters/SolrJmxReporter.java
+++ /dev/null
@@ -1,254 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.reporters;
-
-import com.codahale.metrics.MetricFilter;
-import com.codahale.metrics.MetricRegistry;
-import java.lang.invoke.MethodHandles;
-import java.util.Locale;
-import javax.management.MBeanServer;
-import org.apache.solr.metrics.FilteringSolrMetricReporter;
-import org.apache.solr.metrics.SolrMetricManager;
-import org.apache.solr.metrics.SolrMetricReporter;
-import org.apache.solr.metrics.reporters.jmx.JmxMetricsReporter;
-import org.apache.solr.metrics.reporters.jmx.JmxObjectNameFactory;
-import org.apache.solr.util.JmxUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * A {@link SolrMetricReporter} that finds (or creates) a MBeanServer from the given configuration
- * and registers metrics to it with JMX.
- *
- * <p>NOTE: {@link com.codahale.metrics.jmx.JmxReporter} that this class uses exports only newly
- * added metrics (it doesn't process already existing metrics in a registry)
- */
-// NOCOMMIT: This JMX reporter looks to be wrapped over a Dropwizard registry. We need to migrate
-// this to OTEL. Maybe we can look at available otel shims for JMX?
-public class SolrJmxReporter extends FilteringSolrMetricReporter {
-
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-  private static final ReporterClientCache<MBeanServer> serviceRegistry =
-      new ReporterClientCache<>();
-
-  private String domain;
-  private String agentId;
-  private String serviceUrl;
-  private String rootName;
-
-  private MetricRegistry registry;
-  private MBeanServer mBeanServer;
-  private JmxMetricsReporter reporter;
-  private String instanceTag;
-  private boolean started;
-
-  /**
-   * Creates a new instance of {@link SolrJmxReporter}.
-   *
-   * @param registryName name of the registry to report
-   */
-  public SolrJmxReporter(SolrMetricManager metricManager, String registryName) {
-    super(metricManager, registryName);
-    period = 0; // setting to zero to indicate not applicable
-    setDomain(registryName);
-  }
-
-  @Override
-  protected synchronized void doInit() {
-    if (serviceUrl != null && agentId != null) {
-      mBeanServer = JmxUtil.findFirstMBeanServer();
-      log.warn(
-          "No more than one of serviceUrl({}) and agentId({}) should be configured, using first MBeanServer {} instead of configuration.",
-          serviceUrl,
-          agentId,
-          mBeanServer);
-    } else if (serviceUrl != null) {
-      // reuse existing services
-      mBeanServer =
-          serviceRegistry.getOrCreate(
-              serviceUrl, () -> JmxUtil.findMBeanServerForServiceUrl(serviceUrl));
-    } else if (agentId != null) {
-      mBeanServer = JmxUtil.findMBeanServerForAgentId(agentId);
-    } else {
-      mBeanServer = JmxUtil.findFirstMBeanServer();
-      log.debug(
-          "No serviceUrl or agentId was configured, using first MBeanServer: {}", mBeanServer);
-    }
-
-    if (mBeanServer == null) {
-      log.warn("No JMX server found. Not exposing Solr metrics via JMX.");
-      return;
-    }
-
-    if (domain == null || domain.isEmpty()) {
-      domain = registryName;
-    }
-    String fullDomain = domain;
-    if (rootName != null && !rootName.isEmpty()) {
-      fullDomain = rootName + "." + domain;
-    }
-    JmxObjectNameFactory jmxObjectNameFactory =
-        new JmxObjectNameFactory(pluginInfo.name, fullDomain);
-    registry = metricManager.registry(registryName);
-
-    final MetricFilter filter = newMetricFilter();
-    instanceTag = Integer.toHexString(this.hashCode());
-    reporter =
-        JmxMetricsReporter.forRegistry(registry)
-            .registerWith(mBeanServer)
-            .inDomain(fullDomain)
-            .filter(filter)
-            .createsObjectNamesWith(jmxObjectNameFactory)
-            .withTag(instanceTag)
-            .build();
-    reporter.start();
-    started = true;
-    log.debug(
-        "JMX monitoring for '{}' (registry '{}') enabled at server: {}",
-        fullDomain,
-        registryName,
-        mBeanServer);
-  }
-
-  /** For unit tests. */
-  public String getInstanceTag() {
-    return instanceTag;
-  }
-
-  /** Stops the reporter from publishing metrics. */
-  @Override
-  public synchronized void close() {
-    log.debug("Closing reporter {} for registry {}/{}", this, registryName, registry);
-    started = false;
-    if (reporter != null) {
-      reporter.close();
-      reporter = null;
-    }
-  }
-
-  /**
-   * Validates that the reporter has been correctly configured. Note that all configurable arguments
-   * are currently optional.
-   *
-   * @throws IllegalStateException if the reporter is not properly configured
-   */
-  @Override
-  protected void validate() throws IllegalStateException {
-    if (period != 0) {
-      throw new IllegalStateException(
-          "Init argument 'period' is not supported for " + getClass().getCanonicalName());
-    }
-  }
-
-  /**
-   * Set root name of the JMX hierarchy for this reporter. Default (null or empty) is none, ie. the
-   * hierarchy will start from the domain name.
-   *
-   * @param rootName root name of the JMX name hierarchy, or null or empty for default.
-   */
-  public void setRootName(String rootName) {
-    this.rootName = rootName;
-  }
-
-  /**
-   * Sets the domain with which MBeans are published. If none is set, the domain defaults to the
-   * name of the registry.
-   *
-   * @param domain the domain
-   */
-  public void setDomain(String domain) {
-    if (domain != null) {
-      this.domain = domain;
-    } else {
-      this.domain = registryName;
-    }
-  }
-
-  /**
-   * Sets the service url for a JMX server. Note that this configuration is optional.
-   *
-   * @param serviceUrl the service url
-   */
-  public void setServiceUrl(String serviceUrl) {
-    this.serviceUrl = serviceUrl;
-  }
-
-  /**
-   * Sets the agent id for a JMX server. Note that this configuration is optional.
-   *
-   * @param agentId the agent id
-   */
-  public void setAgentId(String agentId) {
-    this.agentId = agentId;
-  }
-
-  /** Return configured agentId or null. */
-  public String getAgentId() {
-    return agentId;
-  }
-
-  /** Return configured serviceUrl or null. */
-  public String getServiceUrl() {
-    return serviceUrl;
-  }
-
-  /** Return configured domain or null. */
-  public String getDomain() {
-    return domain;
-  }
-
-  /**
-   * Return the reporter's MBeanServer.
-   *
-   * @return the reporter's MBeanServer
-   */
-  public MBeanServer getMBeanServer() {
-    return mBeanServer;
-  }
-
-  /**
-   * For unit tests.
-   *
-   * @return true if this reporter is going to report metrics to JMX.
-   */
-  public boolean isActive() {
-    return reporter != null;
-  }
-
-  /**
-   * For unit tests.
-   *
-   * @return true if this reporter has been started and is reporting metrics to JMX.
-   */
-  public boolean isStarted() {
-    return started;
-  }
-
-  @Override
-  public String toString() {
-    return String.format(
-        Locale.ENGLISH,
-        "[%s@%s: rootName = %s, domain = %s, service url = %s, agent id = %s]",
-        getClass().getName(),
-        Integer.toHexString(hashCode()),
-        rootName,
-        domain,
-        serviceUrl,
-        agentId);
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/reporters/SolrSlf4jReporter.java b/solr/core/src/java/org/apache/solr/metrics/reporters/SolrSlf4jReporter.java
deleted file mode 100644
index 117d414085f..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/reporters/SolrSlf4jReporter.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.reporters;
-
-import com.codahale.metrics.Counter;
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Histogram;
-import com.codahale.metrics.Meter;
-import com.codahale.metrics.MetricFilter;
-import com.codahale.metrics.ScheduledReporter;
-import com.codahale.metrics.Slf4jReporter;
-import com.codahale.metrics.Timer;
-import java.io.IOException;
-import java.lang.invoke.MethodHandles;
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.concurrent.TimeUnit;
-import org.apache.solr.metrics.FilteringSolrMetricReporter;
-import org.apache.solr.metrics.SolrMetricManager;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.slf4j.MDC;
-
-/**
- * Metrics reporter that wraps {@link com.codahale.metrics.Slf4jReporter}. The following init
- * arguments are supported:
- *
- * <ul>
- *   <li><code>period</code>: (optional, int) number of seconds between reports, default is 60,
- *   <li><code>prefix</code>: (optional, str) prefix for metric names, in addition to registry name.
- *       Default is none, ie. just registry name.
- *   <li><code>filter</code>: (optional, str) if not empty only metric names that start with this
- *       value will be reported, default is all metrics from a registry,
- *   <li><code>logger</code>: (optional, str) logger name to use. Default is the metrics group, eg.
- *       <code>solr.jvm</code>, <code>solr.core</code>, etc
- * </ul>
- */
-// NOCOMMIT: Will we still be supporting this? Need to understand how Slf4j works and if it is even
-// possible to wrap OTEL into this like dropwizard.
-public class SolrSlf4jReporter extends FilteringSolrMetricReporter {
-
-  @SuppressWarnings("unused") // we need this to pass validate-source-patterns
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-  private String instancePrefix = null;
-  private String logger = null;
-  private Map<String, String> mdcContext;
-  private Slf4jReporterWrapper reporter;
-  private boolean active;
-
-  // this wrapper allows us to set MDC context - unfortunately it's not possible to
-  // simply override {@link Slf4jReporter#report()} because its constructor is private
-  private class Slf4jReporterWrapper extends ScheduledReporter {
-    final Slf4jReporter delegate;
-    final Map<String, String> mdcContext;
-
-    Slf4jReporterWrapper(
-        String logger,
-        Map<String, String> mdcContext,
-        Slf4jReporter delegate,
-        TimeUnit rateUnit,
-        TimeUnit durationUnit) {
-      super(metricManager.registry(registryName), logger, null, rateUnit, durationUnit);
-      this.delegate = delegate;
-      this.mdcContext = mdcContext;
-    }
-
-    @Override
-    public void report() {
-      // set up MDC
-      MDC.setContextMap(mdcContext);
-      try {
-        delegate.report();
-      } finally {
-        // clear MDC
-        MDC.clear();
-      }
-    }
-
-    @Override
-    @SuppressWarnings(
-        "rawtypes") // super uses raw Gauge, so we're stuck with it until DropWizard 5.0
-    public void report(
-        SortedMap<String, Gauge> gauges,
-        SortedMap<String, Counter> counters,
-        SortedMap<String, Histogram> histograms,
-        SortedMap<String, Meter> meters,
-        SortedMap<String, Timer> timers) {
-      throw new UnsupportedOperationException("this method should never be called here!");
-    }
-
-    @Override
-    public void close() {
-      super.close();
-      delegate.close();
-    }
-  }
-
-  /**
-   * Create a SLF4J reporter for metrics managed in a named registry.
-   *
-   * @param metricManager metric manager instance that manages the selected registry
-   * @param registryName registry to use, one of registries managed by {@link SolrMetricManager}
-   */
-  public SolrSlf4jReporter(SolrMetricManager metricManager, String registryName) {
-    super(metricManager, registryName);
-  }
-
-  public void setPrefix(String prefix) {
-    this.instancePrefix = prefix;
-  }
-
-  public void setLogger(String logger) {
-    this.logger = logger;
-  }
-
-  @Override
-  protected void doInit() {
-    mdcContext = MDC.getCopyOfContextMap();
-    mdcContext.put("registry", "m:" + registryName);
-    Slf4jReporter.Builder builder =
-        Slf4jReporter.forRegistry(metricManager.registry(registryName))
-            .convertRatesTo(TimeUnit.SECONDS)
-            .convertDurationsTo(TimeUnit.MILLISECONDS);
-
-    final MetricFilter filter = newMetricFilter();
-    builder = builder.filter(filter);
-    if (instancePrefix != null) {
-      builder = builder.prefixedWith(instancePrefix);
-    }
-    if (logger == null || logger.isEmpty()) {
-      // construct logger name from Group
-      if (pluginInfo.attributes.containsKey("group")) {
-        logger = SolrMetricManager.enforcePrefix(pluginInfo.attributes.get("group"));
-      } else if (pluginInfo.attributes.containsKey("registry")) {
-        String reg = SolrMetricManager.enforcePrefix(pluginInfo.attributes.get("registry"));
-        String[] names = reg.split("\\.");
-        if (names.length < 2) {
-          logger = reg;
-        } else {
-          logger = names[0] + "." + names[1];
-        }
-      }
-    }
-    builder = builder.outputTo(LoggerFactory.getLogger(logger));
-    // build BUT don't start - scheduled execution is handled by the wrapper
-    Slf4jReporter delegate = builder.build();
-    reporter =
-        new Slf4jReporterWrapper(
-            logger, mdcContext, delegate, TimeUnit.SECONDS, TimeUnit.MILLISECONDS);
-    reporter.start(period, TimeUnit.SECONDS);
-    active = true;
-  }
-
-  @Override
-  protected void validate() throws IllegalStateException {
-    if (period < 1) {
-      throw new IllegalStateException(
-          "Init argument 'period' is in time unit 'seconds' and must be at least 1.");
-    }
-  }
-
-  @Override
-  public void close() throws IOException {
-    if (reporter != null) {
-      reporter.close();
-    }
-    active = false;
-  }
-
-  // for unit tests
-  boolean isActive() {
-    return active;
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/reporters/jmx/JmxMetricsReporter.java b/solr/core/src/java/org/apache/solr/metrics/reporters/jmx/JmxMetricsReporter.java
deleted file mode 100644
index bd9eb1b2b21..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/reporters/jmx/JmxMetricsReporter.java
+++ /dev/null
@@ -1,777 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.reporters.jmx;
-
-import com.codahale.metrics.Counter;
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Histogram;
-import com.codahale.metrics.Meter;
-import com.codahale.metrics.Metered;
-import com.codahale.metrics.Metric;
-import com.codahale.metrics.MetricFilter;
-import com.codahale.metrics.MetricRegistry;
-import com.codahale.metrics.MetricRegistryListener;
-import com.codahale.metrics.Reporter;
-import com.codahale.metrics.Timer;
-import com.codahale.metrics.jmx.DefaultObjectNameFactory;
-import com.codahale.metrics.jmx.ObjectNameFactory;
-import java.io.Closeable;
-import java.lang.invoke.MethodHandles;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.TimeUnit;
-import javax.management.Attribute;
-import javax.management.InstanceAlreadyExistsException;
-import javax.management.InstanceNotFoundException;
-import javax.management.JMException;
-import javax.management.MBeanRegistrationException;
-import javax.management.MBeanServer;
-import javax.management.ObjectInstance;
-import javax.management.ObjectName;
-import javax.management.Query;
-import javax.management.QueryExp;
-import org.apache.solr.metrics.MetricsMap;
-import org.apache.solr.metrics.SolrMetricManager;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * This is a modified copy of Dropwizard's {@link com.codahale.metrics.jmx.JmxReporter} and classes
- * that it internally uses, with a few important differences:
- *
- * <ul>
- *   <li>this class knows that it can directly use {@link MetricsMap} as a dynamic MBean.
- *   <li>this class allows us to "tag" MBean instances so that we can later unregister only
- *       instances registered with the same tag.
- *   <li>this class processes all metrics already existing in the registry at the time when reporter
- *       is started.
- * </ul>
- */
-public class JmxMetricsReporter implements Reporter, Closeable {
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-  public static final String INSTANCE_TAG = "_instanceTag";
-
-  public static Builder forRegistry(MetricRegistry registry) {
-    return new Builder(registry);
-  }
-
-  /** Builder for the {@link JmxMetricsReporter} class. */
-  public static class Builder {
-    private final MetricRegistry registry;
-    private MBeanServer mBeanServer;
-    private TimeUnit rateUnit;
-    private TimeUnit durationUnit;
-    private ObjectNameFactory objectNameFactory;
-    private MetricFilter filter = MetricFilter.ALL;
-    private String domain;
-    private String tag;
-
-    private Builder(MetricRegistry registry) {
-      this.registry = registry;
-      this.rateUnit = TimeUnit.SECONDS;
-      this.durationUnit = TimeUnit.MILLISECONDS;
-      this.domain = "metrics";
-      this.objectNameFactory = new DefaultObjectNameFactory();
-    }
-
-    /**
-     * Register MBeans with the given {@link MBeanServer}.
-     *
-     * @param mBeanServer an {@link MBeanServer}
-     * @return {@code this}
-     */
-    public Builder registerWith(MBeanServer mBeanServer) {
-      this.mBeanServer = mBeanServer;
-      return this;
-    }
-
-    /**
-     * Convert rates to the given time unit.
-     *
-     * @param rateUnit a unit of time
-     * @return {@code this}
-     */
-    public Builder convertRatesTo(TimeUnit rateUnit) {
-      this.rateUnit = rateUnit;
-      return this;
-    }
-
-    public Builder createsObjectNamesWith(ObjectNameFactory onFactory) {
-      if (onFactory == null) {
-        throw new IllegalArgumentException("null objectNameFactory");
-      }
-      this.objectNameFactory = onFactory;
-      return this;
-    }
-
-    /**
-     * Convert durations to the given time unit.
-     *
-     * @param durationUnit a unit of time
-     * @return {@code this}
-     */
-    public Builder convertDurationsTo(TimeUnit durationUnit) {
-      this.durationUnit = durationUnit;
-      return this;
-    }
-
-    /**
-     * Only report metrics which match the given filter.
-     *
-     * @param filter a {@link MetricFilter}
-     * @return {@code this}
-     */
-    public Builder filter(MetricFilter filter) {
-      this.filter = filter;
-      return this;
-    }
-
-    public Builder inDomain(String domain) {
-      this.domain = domain;
-      return this;
-    }
-
-    public Builder withTag(String tag) {
-      this.tag = tag;
-      return this;
-    }
-
-    public JmxMetricsReporter build() {
-      if (tag == null) {
-        tag = Integer.toHexString(this.hashCode());
-      }
-      return new JmxMetricsReporter(
-          mBeanServer, domain, registry, filter, rateUnit, durationUnit, objectNameFactory, tag);
-    }
-  }
-
-  // MBean interfaces and base classes
-  public interface MetricMBean {
-    ObjectName objectName();
-
-    // this strange-looking method name is used for producing "_instanceTag" attribute name
-    String get_instanceTag();
-  }
-
-  private abstract static class AbstractBean implements MetricMBean {
-    private final ObjectName objectName;
-    private final String instanceTag;
-
-    AbstractBean(ObjectName objectName, String instanceTag) {
-      this.objectName = objectName;
-      this.instanceTag = instanceTag;
-    }
-
-    @Override
-    public String get_instanceTag() {
-      return instanceTag;
-    }
-
-    @Override
-    public ObjectName objectName() {
-      return objectName;
-    }
-  }
-
-  public interface JmxGaugeMBean extends MetricMBean {
-    Object getValue();
-  }
-
-  private static class JmxGauge extends AbstractBean implements JmxGaugeMBean {
-    private final Gauge<?> metric;
-
-    private JmxGauge(Gauge<?> metric, ObjectName objectName, String tag) {
-      super(objectName, tag);
-      this.metric = metric;
-    }
-
-    @Override
-    public Object getValue() {
-      return metric.getValue();
-    }
-  }
-
-  public interface JmxCounterMBean extends MetricMBean {
-    long getCount();
-  }
-
-  private static class JmxCounter extends AbstractBean implements JmxCounterMBean {
-    private final Counter metric;
-
-    private JmxCounter(Counter metric, ObjectName objectName, String tag) {
-      super(objectName, tag);
-      this.metric = metric;
-    }
-
-    @Override
-    public long getCount() {
-      return metric.getCount();
-    }
-  }
-
-  public interface JmxHistogramMBean extends MetricMBean {
-    long getCount();
-
-    long getMin();
-
-    long getMax();
-
-    double getMean();
-
-    double getStdDev();
-
-    double get50thPercentile();
-
-    double get75thPercentile();
-
-    double get95thPercentile();
-
-    double get98thPercentile();
-
-    double get99thPercentile();
-
-    double get999thPercentile();
-
-    long[] values();
-
-    long getSnapshotSize();
-  }
-
-  private static class JmxHistogram extends AbstractBean implements JmxHistogramMBean {
-    private final Histogram metric;
-
-    private JmxHistogram(Histogram metric, ObjectName objectName, String tag) {
-      super(objectName, tag);
-      this.metric = metric;
-    }
-
-    @Override
-    public double get50thPercentile() {
-      return metric.getSnapshot().getMedian();
-    }
-
-    @Override
-    public long getCount() {
-      return metric.getCount();
-    }
-
-    @Override
-    public long getMin() {
-      return metric.getSnapshot().getMin();
-    }
-
-    @Override
-    public long getMax() {
-      return metric.getSnapshot().getMax();
-    }
-
-    @Override
-    public double getMean() {
-      return metric.getSnapshot().getMean();
-    }
-
-    @Override
-    public double getStdDev() {
-      return metric.getSnapshot().getStdDev();
-    }
-
-    @Override
-    public double get75thPercentile() {
-      return metric.getSnapshot().get75thPercentile();
-    }
-
-    @Override
-    public double get95thPercentile() {
-      return metric.getSnapshot().get95thPercentile();
-    }
-
-    @Override
-    public double get98thPercentile() {
-      return metric.getSnapshot().get98thPercentile();
-    }
-
-    @Override
-    public double get99thPercentile() {
-      return metric.getSnapshot().get99thPercentile();
-    }
-
-    @Override
-    public double get999thPercentile() {
-      return metric.getSnapshot().get999thPercentile();
-    }
-
-    @Override
-    public long[] values() {
-      return metric.getSnapshot().getValues();
-    }
-
-    @Override
-    public long getSnapshotSize() {
-      return metric.getSnapshot().size();
-    }
-  }
-
-  public interface JmxMeterMBean extends MetricMBean {
-    long getCount();
-
-    double getMeanRate();
-
-    double getOneMinuteRate();
-
-    double getFiveMinuteRate();
-
-    double getFifteenMinuteRate();
-
-    String getRateUnit();
-  }
-
-  private static class JmxMeter extends AbstractBean implements JmxMeterMBean {
-    private final Metered metric;
-    private final double rateFactor;
-    private final String rateUnit;
-
-    private JmxMeter(Metered metric, ObjectName objectName, TimeUnit rateUnit, String tag) {
-      super(objectName, tag);
-      this.metric = metric;
-      this.rateFactor = rateUnit.toSeconds(1);
-      this.rateUnit = ("events/" + calculateRateUnit(rateUnit)).intern();
-    }
-
-    @Override
-    public long getCount() {
-      return metric.getCount();
-    }
-
-    @Override
-    public double getMeanRate() {
-      return metric.getMeanRate() * rateFactor;
-    }
-
-    @Override
-    public double getOneMinuteRate() {
-      return metric.getOneMinuteRate() * rateFactor;
-    }
-
-    @Override
-    public double getFiveMinuteRate() {
-      return metric.getFiveMinuteRate() * rateFactor;
-    }
-
-    @Override
-    public double getFifteenMinuteRate() {
-      return metric.getFifteenMinuteRate() * rateFactor;
-    }
-
-    @Override
-    public String getRateUnit() {
-      return rateUnit;
-    }
-
-    private String calculateRateUnit(TimeUnit unit) {
-      final String s = unit.toString().toLowerCase(Locale.US);
-      return s.substring(0, s.length() - 1);
-    }
-  }
-
-  public interface JmxTimerMBean extends JmxMeterMBean {
-    double getMin();
-
-    double getMax();
-
-    double getMean();
-
-    double getStdDev();
-
-    double get50thPercentile();
-
-    double get75thPercentile();
-
-    double get95thPercentile();
-
-    double get98thPercentile();
-
-    double get99thPercentile();
-
-    double get999thPercentile();
-
-    long[] values();
-
-    String getDurationUnit();
-  }
-
-  private static class JmxTimer extends JmxMeter implements JmxTimerMBean {
-    private final Timer metric;
-    private final double durationFactor;
-    private final String durationUnit;
-
-    private JmxTimer(
-        Timer metric, ObjectName objectName, TimeUnit rateUnit, TimeUnit durationUnit, String tag) {
-      super(metric, objectName, rateUnit, tag);
-      this.metric = metric;
-      this.durationFactor = 1.0 / durationUnit.toNanos(1);
-      this.durationUnit = durationUnit.toString().toLowerCase(Locale.US);
-    }
-
-    @Override
-    public double get50thPercentile() {
-      return metric.getSnapshot().getMedian() * durationFactor;
-    }
-
-    @Override
-    public double getMin() {
-      return metric.getSnapshot().getMin() * durationFactor;
-    }
-
-    @Override
-    public double getMax() {
-      return metric.getSnapshot().getMax() * durationFactor;
-    }
-
-    @Override
-    public double getMean() {
-      return metric.getSnapshot().getMean() * durationFactor;
-    }
-
-    @Override
-    public double getStdDev() {
-      return metric.getSnapshot().getStdDev() * durationFactor;
-    }
-
-    @Override
-    public double get75thPercentile() {
-      return metric.getSnapshot().get75thPercentile() * durationFactor;
-    }
-
-    @Override
-    public double get95thPercentile() {
-      return metric.getSnapshot().get95thPercentile() * durationFactor;
-    }
-
-    @Override
-    public double get98thPercentile() {
-      return metric.getSnapshot().get98thPercentile() * durationFactor;
-    }
-
-    @Override
-    public double get99thPercentile() {
-      return metric.getSnapshot().get99thPercentile() * durationFactor;
-    }
-
-    @Override
-    public double get999thPercentile() {
-      return metric.getSnapshot().get999thPercentile() * durationFactor;
-    }
-
-    @Override
-    public long[] values() {
-      return metric.getSnapshot().getValues();
-    }
-
-    @Override
-    public String getDurationUnit() {
-      return durationUnit;
-    }
-  }
-
-  private static class JmxListener implements MetricRegistryListener {
-
-    private final String name;
-    private final MBeanServer mBeanServer;
-    private final MetricFilter filter;
-    private final TimeUnit rateUnit;
-    private final TimeUnit durationUnit;
-    private final Map<ObjectName, ObjectName> registered;
-    private final ObjectNameFactory objectNameFactory;
-    private final String tag;
-    private final QueryExp exp;
-
-    private JmxListener(
-        MBeanServer mBeanServer,
-        String name,
-        MetricFilter filter,
-        TimeUnit rateUnit,
-        TimeUnit durationUnit,
-        ObjectNameFactory objectNameFactory,
-        String tag) {
-      this.mBeanServer = mBeanServer;
-      this.name = name;
-      this.filter = filter;
-      this.rateUnit = rateUnit;
-      this.durationUnit = durationUnit;
-      this.registered = new ConcurrentHashMap<>();
-      this.objectNameFactory = objectNameFactory;
-      this.tag = tag;
-      this.exp = Query.eq(Query.attr(INSTANCE_TAG), Query.value(tag));
-    }
-
-    private void registerMBean(Object mBean, ObjectName objectName)
-        throws InstanceAlreadyExistsException, JMException {
-      // remove previous bean if exists
-      if (mBeanServer.isRegistered(objectName)) {
-        if (log.isDebugEnabled()) {
-          Set<ObjectInstance> objects = mBeanServer.queryMBeans(objectName, null);
-          if (log.isDebugEnabled()) {
-            log.debug(
-                "## removing existing {} bean(s) for {}, current tag={}:",
-                objects.size(),
-                objectName.getCanonicalName(),
-                tag);
-          }
-          for (ObjectInstance inst : objects) {
-            if (log.isDebugEnabled()) {
-              log.debug(
-                  "## - tag={}", mBeanServer.getAttribute(inst.getObjectName(), INSTANCE_TAG));
-            }
-          }
-        }
-        mBeanServer.unregisterMBean(objectName);
-      }
-      ObjectInstance objectInstance = mBeanServer.registerMBean(mBean, objectName);
-      if (objectInstance != null) {
-        // the websphere mbeanserver rewrites the objectname to include
-        // cell, node & server info
-        // make sure we capture the new objectName for unregistration
-        registered.put(objectName, objectInstance.getObjectName());
-      } else {
-        registered.put(objectName, objectName);
-      }
-      if (log.isDebugEnabled()) {
-        log.debug(
-            "## registered {}, tag={}", objectInstance.getObjectName().getCanonicalName(), tag);
-      }
-    }
-
-    private void unregisterMBean(ObjectName originalObjectName)
-        throws InstanceNotFoundException, MBeanRegistrationException {
-      ObjectName objectName = registered.remove(originalObjectName);
-      if (objectName == null) {
-        objectName = originalObjectName;
-      }
-      Set<ObjectInstance> objects = mBeanServer.queryMBeans(objectName, exp);
-      for (ObjectInstance o : objects) {
-        if (log.isDebugEnabled()) {
-          log.debug("## Unregistered {}, tag={}", o.getObjectName().getCanonicalName(), tag);
-        }
-        mBeanServer.unregisterMBean(o.getObjectName());
-      }
-    }
-
-    @Override
-    public void onGaugeAdded(String name, Gauge<?> gauge) {
-      try {
-        if (filter.matches(name, gauge)) {
-          final ObjectName objectName = createName("gauges", name);
-          if (gauge instanceof SolrMetricManager.GaugeWrapper
-              && ((SolrMetricManager.GaugeWrapper<?>) gauge).getGauge() instanceof MetricsMap mm) {
-            mm.setAttribute(new Attribute(INSTANCE_TAG, tag));
-            // don't wrap it in a JmxGauge, it already supports all necessary JMX attributes
-            registerMBean(mm, objectName);
-          } else {
-            registerMBean(new JmxGauge(gauge, objectName, tag), objectName);
-          }
-        }
-      } catch (InstanceAlreadyExistsException e) {
-        log.debug("Unable to register gauge", e);
-      } catch (JMException e) {
-        log.warn("Unable to register gauge", e);
-      }
-    }
-
-    @Override
-    public void onGaugeRemoved(String name) {
-      try {
-        final ObjectName objectName = createName("gauges", name);
-        unregisterMBean(objectName);
-      } catch (InstanceNotFoundException e) {
-        log.debug("Unable to unregister gauge", e);
-      } catch (MBeanRegistrationException e) {
-        log.warn("Unable to unregister gauge", e);
-      }
-    }
-
-    @Override
-    public void onCounterAdded(String name, Counter counter) {
-      try {
-        if (filter.matches(name, counter)) {
-          final ObjectName objectName = createName("counters", name);
-          registerMBean(new JmxCounter(counter, objectName, tag), objectName);
-        }
-      } catch (InstanceAlreadyExistsException e) {
-        log.debug("Unable to register counter", e);
-      } catch (JMException e) {
-        log.warn("Unable to register counter", e);
-      }
-    }
-
-    @Override
-    public void onCounterRemoved(String name) {
-      try {
-        final ObjectName objectName = createName("counters", name);
-        unregisterMBean(objectName);
-      } catch (InstanceNotFoundException e) {
-        log.debug("Unable to unregister counter", e);
-      } catch (MBeanRegistrationException e) {
-        log.warn("Unable to unregister counter", e);
-      }
-    }
-
-    @Override
-    public void onHistogramAdded(String name, Histogram histogram) {
-      try {
-        if (filter.matches(name, histogram)) {
-          final ObjectName objectName = createName("histograms", name);
-          registerMBean(new JmxHistogram(histogram, objectName, tag), objectName);
-        }
-      } catch (InstanceAlreadyExistsException e) {
-        log.debug("Unable to register histogram", e);
-      } catch (JMException e) {
-        log.warn("Unable to register histogram", e);
-      }
-    }
-
-    @Override
-    public void onHistogramRemoved(String name) {
-      try {
-        final ObjectName objectName = createName("histograms", name);
-        unregisterMBean(objectName);
-      } catch (InstanceNotFoundException e) {
-        log.debug("Unable to unregister histogram", e);
-      } catch (MBeanRegistrationException e) {
-        log.warn("Unable to unregister histogram", e);
-      }
-    }
-
-    @Override
-    public void onMeterAdded(String name, Meter meter) {
-      try {
-        if (filter.matches(name, meter)) {
-          final ObjectName objectName = createName("meters", name);
-          registerMBean(new JmxMeter(meter, objectName, rateUnit, tag), objectName);
-        }
-      } catch (InstanceAlreadyExistsException e) {
-        log.debug("Unable to register meter", e);
-      } catch (JMException e) {
-        log.warn("Unable to register meter", e);
-      }
-    }
-
-    @Override
-    public void onMeterRemoved(String name) {
-      try {
-        final ObjectName objectName = createName("meters", name);
-        unregisterMBean(objectName);
-      } catch (InstanceNotFoundException e) {
-        log.debug("Unable to unregister meter", e);
-      } catch (MBeanRegistrationException e) {
-        log.warn("Unable to unregister meter", e);
-      }
-    }
-
-    @Override
-    public void onTimerAdded(String name, Timer timer) {
-      try {
-        if (filter.matches(name, timer)) {
-          final ObjectName objectName = createName("timers", name);
-          registerMBean(new JmxTimer(timer, objectName, rateUnit, durationUnit, tag), objectName);
-        }
-      } catch (InstanceAlreadyExistsException e) {
-        log.debug("Unable to register timer", e);
-      } catch (JMException e) {
-        log.warn("Unable to register timer", e);
-      }
-    }
-
-    @Override
-    public void onTimerRemoved(String name) {
-      try {
-        final ObjectName objectName = createName("timers", name);
-        unregisterMBean(objectName);
-      } catch (InstanceNotFoundException e) {
-        log.debug("Unable to unregister timer", e);
-      } catch (MBeanRegistrationException e) {
-        log.warn("Unable to unregister timer", e);
-      }
-    }
-
-    private ObjectName createName(String type, String name) {
-      return objectNameFactory.createName(type, this.name, name);
-    }
-
-    void unregisterAll() {
-      for (ObjectName name : registered.keySet()) {
-        try {
-          unregisterMBean(name);
-        } catch (InstanceNotFoundException e) {
-          log.debug("Unable to unregister metric", e);
-        } catch (MBeanRegistrationException e) {
-          log.warn("Unable to unregister metric", e);
-        }
-      }
-      registered.clear();
-    }
-  }
-
-  private final MetricRegistry registry;
-  private final JmxListener listener;
-
-  private JmxMetricsReporter(
-      MBeanServer mBeanServer,
-      String domain,
-      MetricRegistry registry,
-      MetricFilter filter,
-      TimeUnit rateUnit,
-      TimeUnit durationUnit,
-      ObjectNameFactory objectNameFactory,
-      String tag) {
-    this.registry = registry;
-    this.listener =
-        new JmxListener(
-            mBeanServer, domain, filter, rateUnit, durationUnit, objectNameFactory, tag);
-  }
-
-  public void start() {
-    registry.addListener(listener);
-    // process existing metrics
-    Map<String, Metric> metrics = new HashMap<>(registry.getMetrics());
-    metrics.forEach(
-        (k, v) -> {
-          if (v instanceof Counter) {
-            listener.onCounterAdded(k, (Counter) v);
-          } else if (v instanceof Meter) {
-            listener.onMeterAdded(k, (Meter) v);
-          } else if (v instanceof Histogram) {
-            listener.onHistogramAdded(k, (Histogram) v);
-          } else if (v instanceof Timer) {
-            listener.onTimerAdded(k, (Timer) v);
-          } else if (v instanceof Gauge) {
-            listener.onGaugeAdded(k, (Gauge<?>) v);
-          } else {
-            log.warn("Unknown metric type {} for metric '{}', ignoring", v.getClass().getName(), k);
-          }
-        });
-  }
-
-  @Override
-  public void close() {
-    registry.removeListener(listener);
-    listener.unregisterAll();
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/reporters/jmx/JmxObjectNameFactory.java b/solr/core/src/java/org/apache/solr/metrics/reporters/jmx/JmxObjectNameFactory.java
deleted file mode 100644
index 2b19344f569..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/reporters/jmx/JmxObjectNameFactory.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.reporters.jmx;
-
-import com.codahale.metrics.jmx.ObjectNameFactory;
-import java.util.Arrays;
-import javax.management.MalformedObjectNameException;
-import javax.management.ObjectName;
-import org.apache.solr.metrics.SolrMetricInfo;
-
-/** Factory to create MBean names for a given metric. */
-public class JmxObjectNameFactory implements ObjectNameFactory {
-
-  private final String domain;
-  private final String[] subdomains;
-  private final String reporterName;
-  private final String[] props;
-
-  /**
-   * Create ObjectName factory.
-   *
-   * @param reporterName name of the reporter
-   * @param domain JMX domain name
-   * @param additionalProperties additional properties as key, value pairs.
-   */
-  public JmxObjectNameFactory(String reporterName, String domain, String... additionalProperties) {
-    this.reporterName = reporterName.replace(":", "_");
-    this.domain = domain;
-    this.subdomains = domain.replace(":", "_").split("\\.");
-    if (additionalProperties != null && (additionalProperties.length % 2) != 0) {
-      throw new IllegalArgumentException(
-          "additionalProperties length must be even: " + Arrays.toString(additionalProperties));
-    }
-    this.props = additionalProperties;
-  }
-
-  /** Return current domain. */
-  public String getDomain() {
-    return domain;
-  }
-
-  /** Return current reporterName. */
-  public String getReporterName() {
-    return reporterName;
-  }
-
-  /**
-   * Create a hierarchical name.
-   *
-   * @param type metric class, eg. "counters", may be null for non-metric MBeans
-   * @param currentDomain JMX domain
-   * @param name object name
-   */
-  @Override
-  public ObjectName createName(String type, String currentDomain, String name) {
-    SolrMetricInfo metricInfo = SolrMetricInfo.of(name);
-    String safeName = metricInfo != null ? metricInfo.name : name;
-    safeName = safeName.replace(":", "_");
-    // It turns out that ObjectName(String) mostly preserves key ordering
-    // as specified in the constructor (except for the 'type' key that ends
-    // up at top level) - unlike ObjectName(String, Map) constructor
-    // that seems to have a mind of its own...
-    StringBuilder sb = new StringBuilder();
-    if (domain.equals(currentDomain)) {
-      if (subdomains != null && subdomains.length > 1) {
-        // use only first segment as domain
-        sb.append(subdomains[0]);
-        sb.append(':');
-        // use remaining segments as properties
-        for (int i = 1; i < subdomains.length; i++) {
-          if (i > 1) {
-            sb.append(',');
-          }
-          sb.append("dom");
-          sb.append(String.valueOf(i));
-          sb.append('=');
-          sb.append(subdomains[i]);
-        }
-        sb.append(','); // separate from other properties
-      } else {
-        sb.append(currentDomain.replace(":", "_"));
-        sb.append(':');
-      }
-    } else {
-      sb.append(currentDomain);
-      sb.append(':');
-    }
-    if (props != null && props.length > 0) {
-      boolean added = false;
-      for (int i = 0; i < props.length; i += 2) {
-        if (props[i] == null || props[i].isEmpty()) {
-          continue;
-        }
-        if (props[i + 1] == null || props[i + 1].isEmpty()) {
-          continue;
-        }
-        sb.append(',');
-        sb.append(props[i]);
-        sb.append('=');
-        sb.append(props[i + 1]);
-        added = true;
-      }
-      if (added) {
-        sb.append(',');
-      }
-    }
-    if (metricInfo != null) {
-      sb.append("category=");
-      sb.append(metricInfo.category.toString());
-      if (metricInfo.scope != null) {
-        sb.append(",scope=");
-        sb.append(metricInfo.scope);
-      }
-      // we could also split by type, but don't call it 'type' :)
-      // if (type != null) {
-      //   sb.append(",class=");
-      //   sb.append(type);
-      // }
-      sb.append(",name=");
-      sb.append(safeName);
-    } else {
-      // make dotted names into hierarchies
-      String[] path = safeName.split("\\.");
-      for (int i = 0; i < path.length - 1; i++) {
-        if (i > 0) {
-          sb.append(',');
-        }
-        sb.append("name");
-        sb.append(String.valueOf(i));
-        sb.append('=');
-        sb.append(path[i]);
-      }
-      if (path.length > 1) {
-        sb.append(',');
-      }
-      // split by type
-      // if (type != null) {
-      //   sb.append("class=");
-      //   sb.append(type);
-      // }
-      sb.append("name=");
-      sb.append(path[path.length - 1]);
-    }
-
-    ObjectName objectName;
-
-    try {
-      objectName = new ObjectName(sb.toString());
-    } catch (MalformedObjectNameException e) {
-      throw new RuntimeException(sb.toString(), e);
-    }
-
-    return objectName;
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/reporters/jmx/package-info.java b/solr/core/src/java/org/apache/solr/metrics/reporters/jmx/package-info.java
deleted file mode 100644
index 2cdd1d68f8d..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/reporters/jmx/package-info.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * This package contains components that support {@link
- * org.apache.solr.metrics.reporters.SolrJmxReporter}.
- */
-package org.apache.solr.metrics.reporters.jmx;
diff --git a/solr/core/src/test/org/apache/solr/core/TestJmxIntegration.java b/solr/core/src/test/org/apache/solr/core/TestJmxIntegration.java
deleted file mode 100644
index f162fe31dce..00000000000
--- a/solr/core/src/test/org/apache/solr/core/TestJmxIntegration.java
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.core;
-
-import com.codahale.metrics.MetricRegistry;
-import java.lang.invoke.MethodHandles;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
-import javax.management.AttributeNotFoundException;
-import javax.management.MBeanAttributeInfo;
-import javax.management.MBeanInfo;
-import javax.management.MBeanServer;
-import javax.management.MBeanServerFactory;
-import javax.management.ObjectInstance;
-import javax.management.ObjectName;
-import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.metrics.SolrMetricManager;
-import org.apache.solr.metrics.SolrMetricReporter;
-import org.apache.solr.metrics.reporters.SolrJmxReporter;
-import org.apache.solr.metrics.reporters.jmx.JmxMetricsReporter;
-import org.apache.solr.metrics.reporters.jmx.JmxObjectNameFactory;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Test for JMX Integration
- *
- * @since solr 1.3
- */
-public class TestJmxIntegration extends SolrTestCaseJ4 {
-
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-  private static MBeanServer mbeanServer = null;
-  private static MBeanServer newMbeanServer = null;
-  private static JmxObjectNameFactory nameFactory = null;
-  private static String registryName = null;
-
-  @BeforeClass
-  public static void beforeClass() throws Exception {
-    // Make sure that at least one MBeanServer is available
-    // prior to initializing the core
-    //
-    // (test configs are set up to use existing server if any,
-    // otherwise skip JMX)
-    newMbeanServer = MBeanServerFactory.createMBeanServer();
-
-    initCore("solrconfig.xml", "schema.xml");
-
-    // we should be able to see that the core has JmxIntegration enabled
-    registryName = h.getCore().getCoreMetricManager().getRegistryName();
-    SolrMetricManager manager = h.getCoreContainer().getMetricManager();
-    Map<String, SolrMetricReporter> reporters = manager.getReporters(registryName);
-    assertEquals(1, reporters.size());
-    SolrMetricReporter reporter = reporters.values().iterator().next();
-    assertTrue(reporter instanceof SolrJmxReporter);
-    SolrJmxReporter jmx = (SolrJmxReporter) reporter;
-    assertTrue("JMX not enabled", jmx.isActive());
-    // and we should be able to see that the reporter
-    // refers to the JMX server we started
-
-    mbeanServer = jmx.getMBeanServer();
-
-    assertNotNull("No JMX server found in the reporter", mbeanServer);
-
-    // NOTE: we can't guarantee that "mbeanServer == platformServer"
-    // the JVM may have multiple MBean servers running when the test started
-    // and the contract of not specifying one when configuring solr.xml without
-    // agentId or serviceUrl is that it will use whatever the "first" MBean server
-    // returned by the JVM is.
-
-    nameFactory = new JmxObjectNameFactory("default", registryName);
-  }
-
-  @AfterClass
-  public static void afterClass() {
-    if (newMbeanServer != null) {
-      MBeanServerFactory.releaseMBeanServer(newMbeanServer);
-    }
-    mbeanServer = null;
-    newMbeanServer = null;
-  }
-
-  @Before
-  public void resetIndex() {
-    clearIndex();
-    assertU("commit", commit());
-  }
-
-  @Test
-  public void testJmxRegistration() throws Exception {
-    assertTrue("No MBeans found in server", mbeanServer.getMBeanCount() > 0);
-
-    Set<ObjectInstance> objects = mbeanServer.queryMBeans(null, null);
-    assertFalse("No objects found in mbean server", objects.isEmpty());
-    int numDynamicMbeans = 0;
-    for (ObjectInstance o : objects) {
-      ObjectName name = o.getObjectName();
-      assertNotNull("Null name on: " + o, name);
-      MBeanInfo mbeanInfo = mbeanServer.getMBeanInfo(name);
-      if (name.getDomain().equals("solr")) {
-        numDynamicMbeans++;
-        MBeanAttributeInfo[] attrs = mbeanInfo.getAttributes();
-        if (name.getKeyProperty("name")
-            .equals("fetcher")) { // no attributes without active replication
-          continue;
-        }
-        assertTrue(
-            "No Attributes found for mbean: " + o.getObjectName() + ", " + mbeanInfo,
-            0 < attrs.length);
-        for (MBeanAttributeInfo attr : attrs) {
-          // ensure every advertised attribute is gettable
-          try {
-            Object trash = mbeanServer.getAttribute(o.getObjectName(), attr.getName());
-          } catch (javax.management.AttributeNotFoundException e) {
-            throw new RuntimeException(
-                "Unable to fetch attribute for " + o.getObjectName() + ": " + attr.getName(), e);
-          }
-        }
-      }
-    }
-    assertTrue("No MBeans found", 0 < numDynamicMbeans);
-  }
-
-  @Test
-  public void testJmxUpdate() throws Exception {
-
-    SolrInfoBean bean = null;
-    // wait until searcher is registered
-    for (int i = 0; i < 100; i++) {
-      bean = h.getCore().getInfoRegistry().get("searcher");
-      if (bean != null) break;
-      Thread.sleep(250);
-    }
-    if (bean == null) throw new RuntimeException("searcher was never registered");
-    ObjectName searcher = nameFactory.createName("gauge", registryName, "SEARCHER.searcher.*");
-
-    if (log.isInfoEnabled()) {
-      log.info("Mbeans in server: {}", mbeanServer.queryNames(null, null));
-    }
-
-    assertFalse(
-        "No mbean found for SolrIndexSearcher", mbeanServer.queryMBeans(searcher, null).isEmpty());
-
-    ObjectName name = nameFactory.createName("gauge", registryName, "SEARCHER.searcher.numDocs");
-    int oldNumDocs = (Integer) mbeanServer.getAttribute(name, "Value");
-    assertU(adoc("id", "1"));
-    assertU("commit", commit());
-    int numDocs = (Integer) mbeanServer.getAttribute(name, "Value");
-    assertTrue("New numDocs is same as old numDocs as reported by JMX", numDocs > oldNumDocs);
-  }
-
-  @Test
-  @SuppressWarnings({"try"})
-  public void testJmxOnCoreReload() throws Exception {
-    // make sure searcher beans are registered
-    assertQ(req("q", "*:*"), "//result[@numFound='0']");
-
-    SolrMetricManager mgr = h.getCoreContainer().getMetricManager();
-    String registryName = h.getCore().getCoreMetricManager().getRegistryName();
-    String coreName = h.getCore().getName();
-    String coreHashCode = Integer.toHexString(h.getCore().hashCode());
-    Map<String, SolrMetricReporter> reporters = mgr.getReporters(registryName);
-    // take first JMX reporter
-    SolrJmxReporter reporter = null;
-    for (Map.Entry<String, SolrMetricReporter> e : reporters.entrySet()) {
-      if (e.getKey().endsWith(coreHashCode) && e.getValue() instanceof SolrJmxReporter) {
-        reporter = (SolrJmxReporter) e.getValue();
-        break;
-      }
-    }
-    assertNotNull("could not find JMX reporter for " + registryName, reporter);
-    String tag = reporter.getInstanceTag();
-
-    Set<ObjectInstance> oldBeans = mbeanServer.queryMBeans(null, null);
-    int oldNumberOfObjects = 0;
-    for (ObjectInstance bean : oldBeans) {
-      try {
-        if (tag.equals(
-            mbeanServer.getAttribute(bean.getObjectName(), JmxMetricsReporter.INSTANCE_TAG))) {
-          oldNumberOfObjects++;
-        }
-      } catch (AttributeNotFoundException e) {
-        // expected
-      }
-    }
-
-    int totalCoreMetrics = mgr.registry(registryName).getMetrics().size();
-    log.info(
-        "Before Reload: size of all core metrics: {} MBeans: {}",
-        totalCoreMetrics,
-        oldNumberOfObjects);
-    assertEquals(
-        "Number of registered MBeans is not the same as the number of core metrics",
-        totalCoreMetrics,
-        oldNumberOfObjects);
-    h.getCoreContainer().reload(coreName);
-    assertQ(req("q", "*:*"), "//result[@numFound='0']");
-
-    reporters = mgr.getReporters(registryName);
-    coreHashCode = Integer.toHexString(h.getCore().hashCode());
-    // take first JMX reporter
-    reporter = null;
-    for (Map.Entry<String, SolrMetricReporter> e : reporters.entrySet()) {
-      if (e.getKey().endsWith(coreHashCode) && e.getValue() instanceof SolrJmxReporter) {
-        reporter = (SolrJmxReporter) e.getValue();
-        break;
-      }
-    }
-    assertNotNull("could not find JMX reporter for " + registryName, reporter);
-    tag = reporter.getInstanceTag();
-
-    Set<ObjectInstance> newBeans = mbeanServer.queryMBeans(null, null);
-    int newNumberOfObjects = 0;
-    Set<String> metricNames = new TreeSet<>();
-    Set<String> beanNames = new TreeSet<>();
-    try (SolrCore core = h.getCoreContainer().getCore(coreName)) {
-      MetricRegistry registry = mgr.registry(registryName);
-      metricNames.addAll(registry.getNames());
-      totalCoreMetrics = registry.getMetrics().size();
-      for (ObjectInstance bean : newBeans) {
-        try {
-          if (tag.equals(
-              mbeanServer.getAttribute(bean.getObjectName(), JmxMetricsReporter.INSTANCE_TAG))) {
-            String[] name = bean.getObjectName().toString().substring(32).split(",");
-            StringBuilder sb = new StringBuilder();
-            for (String n : name) {
-              if (sb.length() > 0) {
-                sb.append(".");
-              }
-              sb.append(n.split("=")[1]);
-            }
-            beanNames.add(sb.toString());
-            newNumberOfObjects++;
-          }
-        } catch (AttributeNotFoundException e) {
-          // expected
-        }
-      }
-    }
-
-    log.info(
-        "After Reload: size of all core metrics: {} MBeans: {}",
-        totalCoreMetrics,
-        newNumberOfObjects);
-    if (totalCoreMetrics != newNumberOfObjects) {
-      Set<String> errors = new TreeSet<>(beanNames);
-      errors.removeAll(metricNames);
-      log.error("Unexpected bean names: {}", errors);
-      errors = new TreeSet<>(metricNames);
-      errors.removeAll(beanNames);
-      log.error("Unexpected metric names: {}", errors);
-      fail(
-          "Number of registered MBeans is not the same as the number of core metrics: "
-              + totalCoreMetrics
-              + " != "
-              + newNumberOfObjects);
-    }
-  }
-}
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java b/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
index 2c8f51aec6d..d5701d15111 100644
--- a/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
@@ -47,8 +47,6 @@ import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.PluginInfo;
 import org.apache.solr.core.SolrInfoBean;
-import org.apache.solr.core.SolrResourceLoader;
-import org.apache.solr.metrics.reporters.MockMetricReporter;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -219,74 +217,6 @@ public class SolrMetricManagerTest extends SolrTestCaseJ4 {
     assertEquals("solr.core." + name + ".collection1.shard1.replica1", result);
   }
 
-  @Test
-  public void testReporters() throws Exception {
-
-    try (SolrResourceLoader loader = new SolrResourceLoader(createTempDir())) {
-      SolrMetricManager metricManager = new SolrMetricManager();
-
-      PluginInfo[] plugins =
-          new PluginInfo[] {
-            createPluginInfo("universal_foo", null, null),
-            createPluginInfo("multigroup_foo", "jvm, node, core", null),
-            createPluginInfo("multiregistry_foo", null, "solr.node, solr.core.collection1"),
-            createPluginInfo("specific_foo", null, "solr.core.collection1"),
-            createPluginInfo("node_foo", "node", null),
-            createPluginInfo("core_foo", "core", null)
-          };
-      String tag = "xyz";
-      metricManager.loadReporters(plugins, loader, null, null, tag, SolrInfoBean.Group.node);
-      Map<String, SolrMetricReporter> reporters =
-          metricManager.getReporters(SolrMetricManager.getRegistryName(SolrInfoBean.Group.node));
-
-      assertEquals(4, reporters.size());
-      assertTrue(reporters.containsKey("universal_foo@" + tag));
-      assertTrue(reporters.containsKey("multigroup_foo@" + tag));
-      assertTrue(reporters.containsKey("node_foo@" + tag));
-      assertTrue(reporters.containsKey("multiregistry_foo@" + tag));
-
-      metricManager.loadReporters(
-          plugins, loader, null, null, tag, SolrInfoBean.Group.core, "collection1");
-      reporters =
-          metricManager.getReporters(
-              SolrMetricManager.getRegistryName(SolrInfoBean.Group.core, "collection1"));
-
-      assertEquals(5, reporters.size());
-      assertTrue(reporters.containsKey("universal_foo@" + tag));
-      assertTrue(reporters.containsKey("multigroup_foo@" + tag));
-      assertTrue(reporters.containsKey("specific_foo@" + tag));
-      assertTrue(reporters.containsKey("core_foo@" + tag));
-      assertTrue(reporters.containsKey("multiregistry_foo@" + tag));
-
-      metricManager.loadReporters(plugins, loader, null, null, tag, SolrInfoBean.Group.jvm);
-      reporters =
-          metricManager.getReporters(SolrMetricManager.getRegistryName(SolrInfoBean.Group.jvm));
-
-      assertEquals(2, reporters.size());
-      assertTrue(reporters.containsKey("universal_foo@" + tag));
-      assertTrue(reporters.containsKey("multigroup_foo@" + tag));
-
-      metricManager.removeRegistry("solr.jvm");
-      reporters =
-          metricManager.getReporters(SolrMetricManager.getRegistryName(SolrInfoBean.Group.jvm));
-
-      assertEquals(0, reporters.size());
-
-      metricManager.removeRegistry("solr.node");
-      reporters =
-          metricManager.getReporters(SolrMetricManager.getRegistryName(SolrInfoBean.Group.node));
-
-      assertEquals(0, reporters.size());
-
-      metricManager.removeRegistry("solr.core.collection1");
-      reporters =
-          metricManager.getReporters(
-              SolrMetricManager.getRegistryName(SolrInfoBean.Group.core, "collection1"));
-
-      assertEquals(0, reporters.size());
-    }
-  }
-
   @Test
   public void testDefaultCloudReporterPeriodUnchanged() {
     assertEquals(60, SolrMetricManager.DEFAULT_CLOUD_REPORTER_PERIOD);
@@ -295,7 +225,6 @@ public class SolrMetricManagerTest extends SolrTestCaseJ4 {
   private PluginInfo createPluginInfo(String name, String group, String registry) {
     Map<String, String> attrs = new HashMap<>();
     attrs.put("name", name);
-    attrs.put("class", MockMetricReporter.class.getName());
     if (group != null) {
       attrs.put("group", group);
     }
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java b/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java
index e6e7e7c949a..b62f56745a8 100644
--- a/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java
@@ -20,17 +20,14 @@ package org.apache.solr.metrics;
 import com.codahale.metrics.Gauge;
 import com.codahale.metrics.Metric;
 import com.codahale.metrics.MetricRegistry;
-import com.codahale.metrics.Timer;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.List;
 import java.util.Map;
-import java.util.Random;
 import java.util.Set;
 import org.apache.http.client.HttpClient;
 import org.apache.lucene.tests.util.LuceneTestCase;
-import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.impl.HttpClientUtil;
@@ -43,7 +40,6 @@ import org.apache.solr.core.PluginInfo;
 import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.core.SolrXmlConfig;
 import org.apache.solr.embedded.JettySolrRunner;
-import org.apache.solr.metrics.reporters.MockMetricReporter;
 import org.apache.solr.util.JmxUtil;
 import org.apache.solr.util.TestHarness;
 import org.hamcrest.number.OrderingComparison;
@@ -132,14 +128,6 @@ public class SolrMetricsIntegrationTest extends SolrTestCaseJ4 {
     assertTrue(
         "Reporter '" + MULTIREGISTRY + "' missing in solr.node",
         reporters.containsKey(MULTIREGISTRY));
-    SolrMetricReporter reporter = reporters.get(REPORTER_NAMES[0]);
-    assertTrue(
-        "Reporter " + reporter + " is not an instance of " + MockMetricReporter.class.getName(),
-        reporter instanceof MockMetricReporter);
-    reporter = reporters.get(UNIVERSAL);
-    assertTrue(
-        "Reporter " + reporter + " is not an instance of " + MockMetricReporter.class.getName(),
-        reporter instanceof MockMetricReporter);
   }
 
   @After
@@ -149,62 +137,10 @@ public class SolrMetricsIntegrationTest extends SolrTestCaseJ4 {
     }
 
     SolrCoreMetricManager coreMetricManager = h.getCore().getCoreMetricManager();
-    Map<String, SolrMetricReporter> reporters =
-        metricManager.getReporters(coreMetricManager.getRegistryName());
-
     Gauge<?> gauge = (Gauge<?>) coreMetricManager.getRegistry().getMetrics().get("CORE.indexDir");
     assertNotNull(gauge.getValue());
     deleteCore(); // closes TestHarness which closes CoreContainer which closes SolrCore
     assertEquals(metricManager.nullString(), gauge.getValue());
-
-    for (String reporterName : RENAMED_REPORTERS) {
-      SolrMetricReporter reporter = reporters.get(reporterName + "@" + tag);
-      MockMetricReporter mockReporter = (MockMetricReporter) reporter;
-      assertTrue(
-          "Reporter " + reporterName + " was not closed: " + mockReporter, mockReporter.didClose);
-    }
-  }
-
-  @Test
-  public void testConfigureReporter() throws Exception {
-    Random random = random();
-
-    String metricName =
-        SolrMetricManager.mkName(METRIC_NAME, HANDLER_CATEGORY.toString(), HANDLER_NAME);
-    SolrCoreMetricManager coreMetricManager = h.getCore().getCoreMetricManager();
-    Timer timer = metricManager.timer(null, coreMetricManager.getRegistryName(), metricName);
-
-    long initialCount = timer.getCount();
-
-    int iterations = TestUtil.nextInt(random, 0, MAX_ITERATIONS);
-    for (int i = 0; i < iterations; ++i) {
-      h.query(req("*"));
-    }
-
-    long finalCount = timer.getCount();
-    assertEquals("metric counter incorrect", iterations, finalCount - initialCount);
-    Map<String, SolrMetricReporter> reporters =
-        metricManager.getReporters(coreMetricManager.getRegistryName());
-    assertEquals(RENAMED_REPORTERS.length + jmxReporter, reporters.size());
-
-    // SPECIFIC and MULTIREGISTRY were skipped because they were
-    // specific to collection1
-    for (String reporterName : RENAMED_REPORTERS) {
-      SolrMetricReporter reporter = reporters.get(reporterName + "@" + tag);
-      assertNotNull("Reporter " + reporterName + " was not found.", reporter);
-      assertTrue(reporter instanceof MockMetricReporter);
-
-      MockMetricReporter mockReporter = (MockMetricReporter) reporter;
-      assertTrue(
-          "Reporter " + reporterName + " was not initialized: " + mockReporter,
-          mockReporter.didInit);
-      assertTrue(
-          "Reporter " + reporterName + " was not validated: " + mockReporter,
-          mockReporter.didValidate);
-      assertFalse(
-          "Reporter " + reporterName + " was incorrectly closed: " + mockReporter,
-          mockReporter.didClose);
-    }
   }
 
   @Test
diff --git a/solr/core/src/test/org/apache/solr/metrics/reporters/SolrGraphiteReporterTest.java b/solr/core/src/test/org/apache/solr/metrics/reporters/SolrGraphiteReporterTest.java
deleted file mode 100644
index da26b845ef0..00000000000
--- a/solr/core/src/test/org/apache/solr/metrics/reporters/SolrGraphiteReporterTest.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.solr.metrics.reporters;
-
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.core.CoreContainer;
-import org.apache.solr.core.NodeConfig;
-import org.apache.solr.core.SolrXmlConfig;
-import org.apache.solr.metrics.SolrMetricManager;
-import org.apache.solr.metrics.SolrMetricReporter;
-import org.apache.solr.util.JmxUtil;
-import org.apache.solr.util.TestHarness;
-import org.junit.Test;
-
-/** */
-public class SolrGraphiteReporterTest extends SolrTestCaseJ4 {
-
-  @Test
-  public void testReporter() throws Exception {
-    int jmxReporter = JmxUtil.findFirstMBeanServer() != null ? 1 : 0;
-    Path home = TEST_HOME();
-    // define these properties, they are used in solrconfig.xml
-    System.setProperty("solr.test.sys.prop1", "propone");
-    System.setProperty("solr.test.sys.prop2", "proptwo");
-
-    MockGraphite mock = new MockGraphite();
-    try {
-      mock.start();
-      Thread.sleep(1000);
-      // define the port where MockGraphite is running
-      System.setProperty("mock-graphite-port", String.valueOf(mock.port));
-      String solrXml =
-          Files.readString(home.resolve("solr-graphitereporter.xml"), StandardCharsets.UTF_8);
-      NodeConfig cfg = SolrXmlConfig.fromString(home, solrXml);
-      CoreContainer cc =
-          createCoreContainer(
-              cfg,
-              new TestHarness.TestCoresLocator(
-                  DEFAULT_TEST_CORENAME,
-                  initAndGetDataDir().toString(),
-                  "solrconfig.xml",
-                  "schema.xml"));
-
-      h.coreName = DEFAULT_TEST_CORENAME;
-      SolrMetricManager metricManager = cc.getMetricManager();
-      Map<String, SolrMetricReporter> reporters = metricManager.getReporters("solr.node");
-      assertEquals(1 + jmxReporter, reporters.size());
-      SolrMetricReporter reporter = reporters.get("test");
-      assertNotNull(reporter);
-      assertTrue(reporter instanceof SolrGraphiteReporter);
-      Thread.sleep(5000);
-      assertTrue(mock.lines.size() >= 3);
-      String[] frozenLines = mock.lines.toArray(new String[0]);
-      for (String line : frozenLines) {
-        assertTrue(line, line.startsWith("test.solr.node.CONTAINER.cores."));
-      }
-    } finally {
-      mock.close();
-    }
-  }
-
-  private static class MockGraphite extends Thread {
-    private List<String> lines = new ArrayList<>();
-    private ServerSocket server = null;
-    private int port;
-    private boolean stop;
-
-    MockGraphite() throws Exception {
-      server = new ServerSocket(0);
-      port = server.getLocalPort();
-      stop = false;
-    }
-
-    @Override
-    public void run() {
-      while (!stop) {
-        try {
-          Socket s = server.accept();
-          BufferedReader br =
-              new BufferedReader(new InputStreamReader(s.getInputStream(), StandardCharsets.UTF_8));
-          String line;
-          while ((line = br.readLine()) != null) {
-            lines.add(line);
-          }
-        } catch (Exception e) {
-          stop = true;
-        }
-      }
-    }
-
-    public void close() throws Exception {
-      stop = true;
-      if (server != null) {
-        server.close();
-      }
-    }
-  }
-}
diff --git a/solr/core/src/test/org/apache/solr/metrics/reporters/SolrJmxReporterCloudTest.java b/solr/core/src/test/org/apache/solr/metrics/reporters/SolrJmxReporterCloudTest.java
deleted file mode 100644
index 00a201c2beb..00000000000
--- a/solr/core/src/test/org/apache/solr/metrics/reporters/SolrJmxReporterCloudTest.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.reporters;
-
-import java.lang.invoke.MethodHandles;
-import java.lang.management.ManagementFactory;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import javax.management.MBeanServer;
-import javax.management.ObjectInstance;
-import javax.management.Query;
-import javax.management.QueryExp;
-import org.apache.solr.client.solrj.impl.CloudSolrClient;
-import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.cloud.SolrCloudTestCase;
-import org.apache.solr.common.SolrInputDocument;
-import org.apache.solr.common.params.CommonParams;
-import org.apache.solr.embedded.JettySolrRunner;
-import org.apache.solr.metrics.SolrMetricManager;
-import org.apache.solr.metrics.SolrMetricReporter;
-import org.apache.solr.metrics.reporters.jmx.JmxMetricsReporter;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/** */
-public class SolrJmxReporterCloudTest extends SolrCloudTestCase {
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-  private static MBeanServer mBeanServer;
-  private static String COLLECTION = SolrJmxReporterCloudTest.class.getSimpleName() + "_collection";
-
-  @BeforeClass
-  public static void setupCluster() throws Exception {
-    // make sure there's an MBeanServer
-    mBeanServer = ManagementFactory.getPlatformMBeanServer();
-    configureCluster(1).addConfig("conf", configset("cloud-minimal")).configure();
-    CollectionAdminRequest.createCollection(COLLECTION, "conf", 2, 1)
-        .process(cluster.getSolrClient());
-  }
-
-  @AfterClass
-  public static void releaseMBeanServer() {
-    mBeanServer = null;
-  }
-
-  @Test
-  public void testJmxReporter() throws Exception {
-    CollectionAdminRequest.reloadCollection(COLLECTION).processAndWait(cluster.getSolrClient(), 60);
-    CloudSolrClient solrClient = cluster.getSolrClient();
-    // index some docs
-    for (int i = 0; i < 100; i++) {
-      SolrInputDocument doc = new SolrInputDocument();
-      doc.addField("id", "id-" + i);
-      solrClient.add(COLLECTION, doc);
-    }
-    solrClient.commit(COLLECTION);
-    // make sure searcher is present
-    solrClient.query(COLLECTION, params(CommonParams.Q, "*:*"));
-
-    for (JettySolrRunner runner : cluster.getJettySolrRunners()) {
-      SolrMetricManager manager = runner.getCoreContainer().getMetricManager();
-      for (String registry : manager.registryNames()) {
-        Map<String, SolrMetricReporter> reporters = manager.getReporters(registry);
-        long jmxReporters =
-            reporters.entrySet().stream()
-                .filter(e -> e.getValue() instanceof SolrJmxReporter)
-                .count();
-        reporters.forEach(
-            (k, v) -> {
-              if (!(v instanceof SolrJmxReporter)) {
-                return;
-              }
-              if (!((SolrJmxReporter) v).getDomain().startsWith("solr.core")) {
-                return;
-              }
-              if (!((SolrJmxReporter) v).isActive()) {
-                return;
-              }
-              QueryExp exp =
-                  Query.eq(
-                      Query.attr(JmxMetricsReporter.INSTANCE_TAG),
-                      Query.value(Integer.toHexString(v.hashCode())));
-              Set<ObjectInstance> beans = mBeanServer.queryMBeans(null, exp);
-              if (((SolrJmxReporter) v).isStarted() && beans.isEmpty() && jmxReporters < 2) {
-                if (log.isInfoEnabled()) {
-                  log.info("DocCollection: {}", getCollectionState(COLLECTION));
-                }
-                fail(
-                    "JMX reporter "
-                        + k
-                        + " for registry "
-                        + registry
-                        + " failed to register any beans!");
-              } else {
-                Set<String> categories = new HashSet<>();
-                beans.forEach(
-                    bean -> {
-                      String cat = bean.getObjectName().getKeyProperty("category");
-                      if (cat != null) {
-                        categories.add(cat);
-                      }
-                    });
-                log.info("Registered categories: {}", categories);
-                assertTrue("Too few categories: " + categories, categories.size() > 5);
-              }
-            });
-      }
-    }
-  }
-}
diff --git a/solr/core/src/test/org/apache/solr/metrics/reporters/SolrJmxReporterTest.java b/solr/core/src/test/org/apache/solr/metrics/reporters/SolrJmxReporterTest.java
deleted file mode 100644
index 25e433b934e..00000000000
--- a/solr/core/src/test/org/apache/solr/metrics/reporters/SolrJmxReporterTest.java
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.reporters;
-
-import com.codahale.metrics.Counter;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Random;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
-import javax.management.InstanceNotFoundException;
-import javax.management.MBeanServer;
-import javax.management.MBeanServerFactory;
-import javax.management.ObjectInstance;
-import javax.management.ObjectName;
-import org.apache.lucene.tests.util.LuceneTestCase;
-import org.apache.lucene.tests.util.TestUtil;
-import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.common.params.CoreAdminParams;
-import org.apache.solr.core.PluginInfo;
-import org.apache.solr.core.SolrCore;
-import org.apache.solr.core.SolrInfoBean;
-import org.apache.solr.metrics.SolrCoreMetricManager;
-import org.apache.solr.metrics.SolrMetricManager;
-import org.apache.solr.metrics.SolrMetricProducer;
-import org.apache.solr.metrics.SolrMetricReporter;
-import org.apache.solr.metrics.SolrMetricTestUtils;
-import org.apache.solr.schema.FieldType;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-// NOCOMMIT: All tests failing because of migration for some core metrics missing from JMX. See
-// comment on SolrJmxReporter for what we could do.
-@LuceneTestCase.BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17458")
-public class SolrJmxReporterTest extends SolrTestCaseJ4 {
-
-  private static final int MAX_ITERATIONS = 20;
-
-  /** The MbeanServer started by the test, that we expect the SolrJmxReporter to find by agentId */
-  private static MBeanServer TEST_MBEAN_SERVER;
-
-  private static String PREFIX;
-
-  private String domain;
-
-  private SolrCoreMetricManager coreMetricManager;
-  private SolrMetricManager metricManager;
-  private String rootName;
-
-  @BeforeClass
-  public static void init() {
-    TEST_MBEAN_SERVER = MBeanServerFactory.createMBeanServer();
-    PREFIX = getSimpleClassName() + "-";
-  }
-
-  @AfterClass
-  public static void shutdown() {
-    if (null != TEST_MBEAN_SERVER) {
-      MBeanServerFactory.releaseMBeanServer(TEST_MBEAN_SERVER);
-      TEST_MBEAN_SERVER = null;
-    }
-  }
-
-  @Before
-  public void beforeTest() throws Exception {
-    initCore("solrconfig-basic.xml", "schema.xml");
-
-    final SolrCore core = h.getCore();
-    domain = core.getName();
-    rootName = PREFIX + TestUtil.randomSimpleString(random(), 5, 10);
-
-    coreMetricManager = core.getCoreMetricManager();
-    metricManager = core.getCoreContainer().getMetricManager();
-    PluginInfo pluginInfo = createReporterPluginInfo(rootName, true);
-    metricManager.loadReporter(
-        coreMetricManager.getRegistryName(),
-        coreMetricManager.getCore(),
-        pluginInfo,
-        coreMetricManager.getTag());
-
-    Map<String, SolrMetricReporter> reporters =
-        metricManager.getReporters(coreMetricManager.getRegistryName());
-    assertTrue("reporters.size should be > 0, but was + " + reporters.size(), reporters.size() > 0);
-    String reporterName = pluginInfo.name;
-    String taggedName = reporterName + "@" + coreMetricManager.getTag();
-    assertNotNull(
-        "reporter " + taggedName + " not present among " + reporters, reporters.get(taggedName));
-    assertTrue(
-        "wrong reporter class: " + reporters.get(taggedName),
-        reporters.get(taggedName) instanceof SolrJmxReporter);
-
-    SolrJmxReporter reporter = (SolrJmxReporter) reporters.get(taggedName);
-    assertNotNull("MBean server not found on reporter", reporter.getMBeanServer());
-    assertEquals(
-        "Wrong MBeanServer found on reporter", TEST_MBEAN_SERVER, reporter.getMBeanServer());
-  }
-
-  private PluginInfo createReporterPluginInfo(String rootName, boolean enabled) {
-    Random random = random();
-    String className = SolrJmxReporter.class.getName();
-    String reporterName = PREFIX + TestUtil.randomSimpleString(random, 5, 10);
-
-    Map<String, Object> attrs = new HashMap<>();
-    attrs.put(FieldType.CLASS_NAME, className);
-    attrs.put(CoreAdminParams.NAME, reporterName);
-    attrs.put("rootName", rootName);
-    attrs.put("enabled", enabled);
-
-    try {
-      String agentId =
-          (String)
-              TEST_MBEAN_SERVER.getAttribute(
-                  new ObjectName("JMImplementation:type=MBeanServerDelegate"), "MBeanServerId");
-      attrs.put("agentId", agentId);
-    } catch (Exception e) {
-      throw new RuntimeException(
-          "Unable to determine agentId of MBeanServer: " + e.getMessage(), e);
-    }
-    boolean shouldOverrideDomain = random.nextBoolean();
-    if (shouldOverrideDomain) {
-      domain = PREFIX + TestUtil.randomSimpleString(random);
-      attrs.put("domain", domain);
-    }
-
-    return new PluginInfo(TestUtil.randomUnicodeString(random), attrs);
-  }
-
-  @After
-  public void afterTest() throws Exception {
-    if (null == metricManager) {
-      return; // test failed to init, nothing to clean up
-    }
-
-    metricManager.closeReporters(coreMetricManager.getRegistryName());
-
-    // sanity check there are no longer any registered beans in our domain....
-    assertEquals(
-        Collections.emptySet(),
-        TEST_MBEAN_SERVER.queryMBeans(ObjectName.getInstance(domain + ":*"), null));
-
-    coreMetricManager.close();
-    deleteCore();
-  }
-
-  @Test
-  public void testReportMetrics() {
-    Random random = random();
-
-    Map<String, Counter> registered = new HashMap<>();
-    String scope = PREFIX + SolrMetricTestUtils.getRandomScope(random, true);
-    SolrInfoBean.Category category = SolrMetricTestUtils.getRandomCategory(random, true);
-
-    int iterations = TestUtil.nextInt(random, 0, MAX_ITERATIONS);
-    for (int i = 0; i < iterations; ++i) {
-      Map<String, Counter> metrics =
-          SolrMetricTestUtils.getRandomMetricsWithReplacements(random, registered);
-      SolrMetricProducer producer = SolrMetricTestUtils.getProducerOf(category, scope, metrics);
-      coreMetricManager.registerMetricProducer(scope, producer);
-      registered.putAll(metrics);
-      // waitForListener();
-      Set<ObjectInstance> objects = TEST_MBEAN_SERVER.queryMBeans(null, null);
-      assertEquals(
-          registered.size(),
-          objects.stream()
-              .filter(
-                  o ->
-                      scope.equals(o.getObjectName().getKeyProperty("scope"))
-                          && rootName.equals(o.getObjectName().getDomain()))
-              .count());
-    }
-  }
-
-  @Test
-  public void testReloadCore() throws Exception {
-    Random random = random();
-
-    String scope = PREFIX + SolrMetricTestUtils.getRandomScope(random, true);
-    SolrInfoBean.Category category = SolrMetricTestUtils.getRandomCategory(random, true);
-    Map<String, Counter> metrics = SolrMetricTestUtils.getRandomMetrics(random, true);
-    SolrMetricProducer producer = SolrMetricTestUtils.getProducerOf(category, scope, metrics);
-    coreMetricManager.registerMetricProducer(scope, producer);
-    Set<ObjectInstance> objects = TEST_MBEAN_SERVER.queryMBeans(null, null);
-    assertEquals(
-        metrics.size(),
-        objects.stream()
-            .filter(
-                o ->
-                    scope.equals(o.getObjectName().getKeyProperty("scope"))
-                        && o.getObjectName().getDomain().equals(rootName))
-            .count());
-
-    h.getCoreContainer().reload(h.getCore().getName());
-    PluginInfo pluginInfo = createReporterPluginInfo(rootName, true);
-    metricManager.loadReporter(
-        coreMetricManager.getRegistryName(),
-        coreMetricManager.getCore(),
-        pluginInfo,
-        String.valueOf(coreMetricManager.getCore().hashCode()));
-    coreMetricManager.registerMetricProducer(scope, producer);
-
-    objects = TEST_MBEAN_SERVER.queryMBeans(null, null);
-    assertEquals(
-        metrics.size(),
-        objects.stream()
-            .filter(
-                o ->
-                    scope.equals(o.getObjectName().getKeyProperty("scope"))
-                        && rootName.equals(o.getObjectName().getDomain()))
-            .count());
-  }
-
-  @Test
-  public void testClosedCore() throws Exception {
-    Set<ObjectInstance> objects =
-        TEST_MBEAN_SERVER.queryMBeans(new ObjectName("*:category=CORE,name=indexDir,*"), null);
-    assertEquals("Unexpected number of indexDir beans: " + objects.toString(), 1, objects.size());
-    final ObjectInstance inst = objects.iterator().next();
-    final AtomicBoolean running = new AtomicBoolean(true);
-    Thread testThread =
-        new Thread(
-            () -> {
-              while (running.get()) {
-                try {
-                  Object value = TEST_MBEAN_SERVER.getAttribute(inst.getObjectName(), "Value");
-                  assertNotNull(value);
-                } catch (InstanceNotFoundException x) {
-                  // no longer present
-                  break;
-                } catch (Exception e) {
-                  fail("Unexpected error retrieving attribute: " + e);
-                }
-              }
-            },
-            "TestMBeanThread");
-    try {
-      testThread.start();
-      // This should be enough time for the
-      Thread.sleep(500);
-    } finally {
-      running.set(false);
-      testThread.join();
-    }
-
-    h.getCoreContainer().unload(h.getCore().getName());
-    Thread.sleep(2000);
-    objects =
-        TEST_MBEAN_SERVER.queryMBeans(new ObjectName("*:category=CORE,name=indexDir,*"), null);
-    assertEquals("Unexpected number of beans after core closed: " + objects, 0, objects.size());
-  }
-
-  @Test
-  public void testEnabled() throws Exception {
-    String root1 = PREFIX + TestUtil.randomSimpleString(random(), 5, 10);
-    PluginInfo pluginInfo1 = createReporterPluginInfo(root1, true);
-    metricManager.loadReporter(
-        coreMetricManager.getRegistryName(),
-        coreMetricManager.getCore(),
-        pluginInfo1,
-        coreMetricManager.getTag());
-
-    String root2 = PREFIX + TestUtil.randomSimpleString(random(), 5, 10);
-    assertNotEquals(root2, root1);
-    PluginInfo pluginInfo2 = createReporterPluginInfo(root2, false);
-    metricManager.loadReporter(
-        coreMetricManager.getRegistryName(),
-        coreMetricManager.getCore(),
-        pluginInfo2,
-        coreMetricManager.getTag());
-
-    Map<String, SolrMetricReporter> reporters =
-        metricManager.getReporters(coreMetricManager.getRegistryName());
-    assertTrue(reporters.containsKey(pluginInfo1.name + "@" + coreMetricManager.getTag()));
-    assertTrue(reporters.containsKey(pluginInfo2.name + "@" + coreMetricManager.getTag()));
-
-    String scope = PREFIX + SolrMetricTestUtils.getRandomScope(random(), true);
-    SolrInfoBean.Category category = SolrMetricTestUtils.getRandomCategory(random(), true);
-    Map<String, Counter> metrics = SolrMetricTestUtils.getRandomMetrics(random(), true);
-    SolrMetricProducer producer = SolrMetricTestUtils.getProducerOf(category, scope, metrics);
-    coreMetricManager.registerMetricProducer(scope, producer);
-    Set<ObjectInstance> objects = TEST_MBEAN_SERVER.queryMBeans(null, null);
-    assertEquals(
-        metrics.size(),
-        objects.stream()
-            .filter(
-                o ->
-                    scope.equals(o.getObjectName().getKeyProperty("scope"))
-                        && root1.equals(o.getObjectName().getDomain()))
-            .count());
-    assertEquals(
-        0,
-        objects.stream()
-            .filter(
-                o ->
-                    scope.equals(o.getObjectName().getKeyProperty("scope"))
-                        && root2.equals(o.getObjectName().getDomain()))
-            .count());
-  }
-}
diff --git a/solr/core/src/test/org/apache/solr/metrics/reporters/SolrSlf4jReporterTest.java b/solr/core/src/test/org/apache/solr/metrics/reporters/SolrSlf4jReporterTest.java
deleted file mode 100644
index f76f4678dbf..00000000000
--- a/solr/core/src/test/org/apache/solr/metrics/reporters/SolrSlf4jReporterTest.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.solr.metrics.reporters;
-
-import java.lang.invoke.MethodHandles;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Map;
-import org.apache.lucene.tests.util.LuceneTestCase;
-import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.common.SolrDocumentList;
-import org.apache.solr.core.CoreContainer;
-import org.apache.solr.core.NodeConfig;
-import org.apache.solr.core.SolrXmlConfig;
-import org.apache.solr.logging.LogWatcher;
-import org.apache.solr.logging.LogWatcherConfig;
-import org.apache.solr.metrics.SolrMetricManager;
-import org.apache.solr.metrics.SolrMetricReporter;
-import org.apache.solr.util.TestHarness;
-import org.junit.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/** */
-// NOCOMMIT: Need to see if we are still supporting this and if it is possible to wrap otel metrics
-// into slf4j like dropwizard does.
-@LuceneTestCase.BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17458")
-public class SolrSlf4jReporterTest extends SolrTestCaseJ4 {
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-  @Test
-  public void testReporter() throws Exception {
-    ensureLoggingConfiguredAppropriately();
-    Path home = TEST_HOME();
-    // define these properties, they are used in solrconfig.xml
-    System.setProperty("solr.test.sys.prop1", "propone");
-    System.setProperty("solr.test.sys.prop2", "proptwo");
-
-    String solrXml =
-        Files.readString(home.resolve("solr-slf4jreporter.xml"), StandardCharsets.UTF_8);
-    NodeConfig cfg = SolrXmlConfig.fromString(home, solrXml);
-    CoreContainer cc =
-        createCoreContainer(
-            cfg,
-            new TestHarness.TestCoresLocator(
-                DEFAULT_TEST_CORENAME,
-                initAndGetDataDir().toString(),
-                "solrconfig.xml",
-                "schema.xml"));
-
-    h.coreName = DEFAULT_TEST_CORENAME;
-    SolrMetricManager metricManager = cc.getMetricManager();
-    Map<String, SolrMetricReporter> reporters = metricManager.getReporters("solr.node");
-    assertTrue(reporters.toString(), reporters.size() >= 2);
-    SolrMetricReporter reporter1 = reporters.get("test1");
-    assertNotNull(reporter1);
-    assertTrue(reporter1 instanceof SolrSlf4jReporter);
-    SolrMetricReporter reporter2 = reporters.get("test2");
-    assertNotNull(reporter2);
-    assertTrue(reporter2 instanceof SolrSlf4jReporter);
-
-    LogWatcherConfig watcherCfg = new LogWatcherConfig(true, null, null, 100);
-    LogWatcher<?> watcher = LogWatcher.newRegisteredLogWatcher(watcherCfg, null);
-    watcher.setThreshold("INFO");
-
-    watcher.reset();
-    int cnt = 20;
-    boolean active;
-    do {
-      Thread.sleep(1000);
-      cnt--;
-      active =
-          ((SolrSlf4jReporter) reporter1).isActive() && ((SolrSlf4jReporter) reporter2).isActive();
-    } while (!active && cnt > 0);
-    if (!active) {
-      fail("One or more reporters didn't become active in 20 seconds");
-    }
-    Thread.sleep(10000);
-
-    SolrDocumentList history = watcher.getHistory(-1, null);
-    // dot-separated names are treated like class names and collapsed
-    // in regular log output, but here we get the full name
-    if (history.stream().filter(d -> "solr.node".equals(d.getFirstValue("logger"))).count() == 0) {
-      fail("No 'solr.node' logs in: " + history);
-    }
-    if (history.stream().filter(d -> "foobar".equals(d.getFirstValue("logger"))).count() == 0) {
-      fail("No 'foobar' logs in: " + history);
-    }
-    if (history.stream().filter(d -> "collection1".equals(d.getFirstValue("core"))).count() == 0) {
-      fail("No 'solr.core' or MDC context in logs: " + history);
-    }
-  }
-
-  private static void ensureLoggingConfiguredAppropriately() {
-    if (!log.isInfoEnabled()) {
-      fail("Test requires that log-level is at-least INFO, but INFO is disabled");
-    }
-  }
-}
diff --git a/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-10.adoc b/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-10.adoc
index 1821dadbf8e..5b3b74e53df 100644
--- a/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-10.adoc
+++ b/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-10.adoc
@@ -112,7 +112,7 @@ If only one node is exposed to a client, and if the client uses the bin/solr exp
 
 === Modern NLP Models from Apache OpenNLP with Solr
 
-Solr now lets you access models encoded in ONNX format, commonly sourced from HuggingFace.  
+Solr now lets you access models encoded in ONNX format, commonly sourced from HuggingFace.
 The DocumentCategorizerUpdateProcessorFactorythat lets you perform sentiment and other classification tasks on fields.
 It is available as part of the `analysis-extras` module.
 
@@ -168,6 +168,7 @@ Nowadays, the HTTP request is available via internal APIs: `SolrQueryRequest.get
 
 * BlobHandler and the `.system` collection have been removed in favour of FileStore API.  (SOLR-17851).
 
+* JMX, SLF4J and Graphite metric reporters have been removed. Users should migrate to using OTLP or the /admin/metrics endpoint with external tools to get metrics to their preferred backend.
 
 === Security
 
diff --git a/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java b/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java
index 2e18bf4c33b..300aa7c3185 100644
--- a/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java
+++ b/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java
@@ -34,13 +34,11 @@ import org.apache.solr.core.CorePropertiesLocator;
 import org.apache.solr.core.CoresLocator;
 import org.apache.solr.core.MetricsConfig;
 import org.apache.solr.core.NodeConfig;
-import org.apache.solr.core.PluginInfo;
 import org.apache.solr.core.SolrConfig;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrXmlConfig;
 import org.apache.solr.handler.UpdateRequestHandler;
 import org.apache.solr.logging.MDCSnapshot;
-import org.apache.solr.metrics.reporters.SolrJmxReporter;
 import org.apache.solr.request.LocalSolrQueryRequest;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestHandler;
@@ -195,15 +193,7 @@ public class TestHarness extends BaseTestHarness {
                 .setZkHost(System.getProperty("zkHost"))
                 .build();
 
-    // universal default metric reporter
-    Map<String, Object> attributes = new HashMap<>();
-    attributes.put("name", "default");
-    attributes.put("class", SolrJmxReporter.class.getName());
-    PluginInfo defaultPlugin = new PluginInfo("reporter", attributes);
-    MetricsConfig metricsConfig =
-        new MetricsConfig.MetricsConfigBuilder()
-            .setMetricReporterPlugins(new PluginInfo[] {defaultPlugin})
-            .build();
+    MetricsConfig metricsConfig = new MetricsConfig.MetricsConfigBuilder().build();
 
     return new NodeConfig.NodeConfigBuilder("testNode", solrHome)
         .setUseSchemaCache(Boolean.getBoolean("shareSchema"))


(solr) 03/33: SOLR-17793: Create dedicated SdkMeterProviders for Solr core metrics (#3402)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 50af82fbbc124d78cb57368cc6b2f4cdb09de577
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Mon Jun 30 12:50:14 2025 -0400

    SOLR-17793: Create dedicated SdkMeterProviders for Solr core metrics (#3402)
    
    * Create dynamic SDK Meter Providers and tests
    
    * Bad merge from feature branch
    
    * Wrap meter providers and metric readers
    
    * Test cleanup
    
    * Cleanup
    
    * Fix double lookups
    
    * Create a separate test suite for prometheus
    
    * Move back to registry name
    
    * Changes from comments
---
 .../java/org/apache/solr/core/CoreContainer.java   |   6 +-
 .../solr/core/OpenTelemetryConfigurator.java       |  25 +-
 .../apache/solr/handler/RequestHandlerBase.java    |   2 +-
 .../apache/solr/handler/admin/MetricsHandler.java  |   2 +-
 .../apache/solr/metrics/SolrCoreMetricManager.java |   1 +
 .../org/apache/solr/metrics/SolrMetricManager.java | 160 ++++++++++---
 .../apache/solr/metrics/SolrMetricsContext.java    |  22 +-
 .../solr/response/PrometheusResponseWriter.java    | 112 ++++++++-
 .../org/apache/solr/util/stats/MetricUtils.java    |  25 --
 .../apache/solr/core/TestTracerConfigurator.java   |   2 +-
 .../solr/handler/admin/MetricsHandlerTest.java     |   1 +
 .../org/apache/solr/metrics/MetricsConfigTest.java |  10 +-
 .../apache/solr/metrics/SolrMetricManagerTest.java | 262 +++++++++++++++++++++
 .../response/TestPrometheusResponseWriter.java     |  46 +---
 .../TestPrometheusResponseWriterCloud.java         | 164 +++++++++++++
 .../src/java/org/apache/solr/SolrTestCaseJ4.java   |   4 +-
 .../apache/solr/cloud/MiniSolrCloudCluster.java    |   1 +
 17 files changed, 704 insertions(+), 141 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index 75e1d43d60b..02b63fe0c16 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -33,7 +33,6 @@ import com.github.benmanes.caffeine.cache.Interner;
 import com.google.common.annotations.VisibleForTesting;
 import io.opentelemetry.api.common.Attributes;
 import io.opentelemetry.api.trace.Tracer;
-import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
 import jakarta.inject.Singleton;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
@@ -404,9 +403,8 @@ public class CoreContainer {
     this.solrHome = config.getSolrHome();
     this.solrCores = SolrCores.newSolrCores(this);
     this.nodeKeyPair = new SolrNodeKeyPair(cfg.getCloudConfig());
-    PrometheusMetricReader metricReader = new PrometheusMetricReader(true, null);
-    OpenTelemetryConfigurator.initializeOpenTelemetrySdk(cfg, loader, metricReader);
-    this.metricManager = new SolrMetricManager(loader, cfg.getMetricsConfig(), metricReader);
+    OpenTelemetryConfigurator.initializeOpenTelemetrySdk(cfg, loader);
+    this.metricManager = new SolrMetricManager(loader, cfg.getMetricsConfig());
     this.tracer = TraceUtils.getGlobalTracer();
 
     containerHandlers.put(PublicKeyHandler.PATH, new PublicKeyHandler(nodeKeyPair));
diff --git a/solr/core/src/java/org/apache/solr/core/OpenTelemetryConfigurator.java b/solr/core/src/java/org/apache/solr/core/OpenTelemetryConfigurator.java
index 47547b4950f..1be4b0deb6b 100644
--- a/solr/core/src/java/org/apache/solr/core/OpenTelemetryConfigurator.java
+++ b/solr/core/src/java/org/apache/solr/core/OpenTelemetryConfigurator.java
@@ -19,16 +19,12 @@ package org.apache.solr.core;
 
 import com.google.common.annotations.VisibleForTesting;
 import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.OpenTelemetry;
 import io.opentelemetry.api.trace.Tracer;
 import io.opentelemetry.context.Context;
 import io.opentelemetry.context.Scope;
 import io.opentelemetry.context.propagation.ContextPropagators;
 import io.opentelemetry.sdk.OpenTelemetrySdk;
-import io.opentelemetry.sdk.OpenTelemetrySdkBuilder;
-import io.opentelemetry.sdk.metrics.SdkMeterProvider;
-import io.opentelemetry.sdk.metrics.export.MetricReader;
-import io.opentelemetry.sdk.trace.SdkTracerProvider;
-import io.opentelemetry.sdk.trace.samplers.Sampler;
 import java.lang.invoke.MethodHandles;
 import java.util.Locale;
 import java.util.Map;
@@ -62,7 +58,7 @@ public abstract class OpenTelemetryConfigurator implements NamedListInitializedP
    * SDK.
    */
   public static synchronized void initializeOpenTelemetrySdk(
-      NodeConfig cfg, SolrResourceLoader loader, MetricReader metricReader) {
+      NodeConfig cfg, SolrResourceLoader loader) {
     PluginInfo info = (cfg != null) ? cfg.getTracerConfiguratorPluginInfo() : null;
 
     if (info != null && info.isEnabled()) {
@@ -71,28 +67,21 @@ public abstract class OpenTelemetryConfigurator implements NamedListInitializedP
     } else if (OpenTelemetryConfigurator.shouldAutoConfigOTEL()) {
       OpenTelemetryConfigurator.autoConfigureOpenTelemetrySdk(loader);
     } else {
-      // Initializing sampler as always off to replicate no-op Tracer provider
-      OpenTelemetryConfigurator.configureOpenTelemetrySdk(
-          SdkMeterProvider.builder().registerMetricReader(metricReader).build(),
-          SdkTracerProvider.builder().setSampler(Sampler.alwaysOff()).build());
+      OpenTelemetryConfigurator.configureOpenTelemetrySdk();
     }
   }
 
-  private static void configureOpenTelemetrySdk(
-      SdkMeterProvider sdkMeterProvider, SdkTracerProvider sdkTracerProvider) {
+  private static void configureOpenTelemetrySdk() {
     if (loaded) return;
 
-    OpenTelemetrySdkBuilder builder = OpenTelemetrySdk.builder();
     if (TRACE_ID_GEN_ENABLED) {
       log.info("OpenTelemetry tracer enabled with simple propagation only.");
       ExecutorUtil.addThreadLocalProvider(new ContextThreadLocalProvider());
-      builder.setPropagators(ContextPropagators.create(SimplePropagator.getInstance()));
     }
 
-    if (sdkMeterProvider != null) builder.setMeterProvider(sdkMeterProvider);
-    if (sdkTracerProvider != null) builder.setTracerProvider(sdkTracerProvider);
-
-    GlobalOpenTelemetry.set(builder.build());
+    OpenTelemetry otel =
+        OpenTelemetry.propagating(ContextPropagators.create(SimplePropagator.getInstance()));
+    GlobalOpenTelemetry.set(otel);
     loaded = true;
   }
 
diff --git a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
index e633a91113d..6038db647a3 100644
--- a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
+++ b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
@@ -192,7 +192,7 @@ public abstract class RequestHandlerBase
         new HandlerMetrics(
             new SolrMetricsContext(
                 new SolrMetricManager(
-                    null, new MetricsConfig.MetricsConfigBuilder().setEnabled(false).build(), null),
+                    null, new MetricsConfig.MetricsConfigBuilder().setEnabled(false).build()),
                 "NO_OP",
                 "NO_OP"),
             Attributes.empty());
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java
index f2b501b8a41..ba7f6bba34b 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java
@@ -128,7 +128,7 @@ public class MetricsHandler extends RequestHandlerBase implements PermissionName
 
     // TODO SOLR-17458: Make this the default option after dropwizard removal
     if (PROMETHEUS_METRICS_WT.equals(params.get(CommonParams.WT))) {
-      consumer.accept("metrics", metricManager.getMetricReader().collect());
+      consumer.accept("metrics", metricManager.getPrometheusMetricReaders());
       return;
     }
 
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
index 62a342180f2..9d79ada8a85 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
@@ -108,6 +108,7 @@ public class SolrCoreMetricManager implements Closeable {
    * and will be used under the new core name. This method also reloads reporters so that they use
    * the new core name.
    */
+  // NOCOMMIT SOLR-17458: Update for core renaming
   public void afterCoreRename() {
     assert core.getCoreDescriptor().getCloudDescriptor() == null;
     String oldRegistryName = solrMetricsContext.getRegistryName();
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
index bcefc0b3ddc..7056a457861 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
@@ -42,8 +42,8 @@ import io.opentelemetry.api.metrics.LongHistogram;
 import io.opentelemetry.api.metrics.LongHistogramBuilder;
 import io.opentelemetry.api.metrics.LongUpDownCounter;
 import io.opentelemetry.api.metrics.LongUpDownCounterBuilder;
-import io.opentelemetry.api.metrics.MeterProvider;
 import io.opentelemetry.api.metrics.ObservableDoubleCounter;
+import io.opentelemetry.api.metrics.ObservableDoubleGauge;
 import io.opentelemetry.api.metrics.ObservableDoubleMeasurement;
 import io.opentelemetry.api.metrics.ObservableDoubleUpDownCounter;
 import io.opentelemetry.api.metrics.ObservableLongCounter;
@@ -51,6 +51,7 @@ import io.opentelemetry.api.metrics.ObservableLongGauge;
 import io.opentelemetry.api.metrics.ObservableLongMeasurement;
 import io.opentelemetry.api.metrics.ObservableLongUpDownCounter;
 import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
+import io.opentelemetry.sdk.metrics.SdkMeterProvider;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
@@ -81,7 +82,6 @@ import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.core.SolrResourceLoader;
 import org.apache.solr.logging.MDCLoggingContext;
-import org.apache.solr.util.stats.MetricUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.MDC;
@@ -115,6 +115,8 @@ public class SolrMetricManager {
 
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
+  public static final String OTEL_SCOPE_NAME = "org.apache.solr";
+
   /** Common prefix for all registry names that Solr uses. */
   public static final String REGISTRY_NAME_PREFIX = "solr.";
 
@@ -147,9 +149,8 @@ public class SolrMetricManager {
   private final MetricRegistry.MetricSupplier<Timer> timerSupplier;
   private final MetricRegistry.MetricSupplier<Histogram> histogramSupplier;
 
-  private final MeterProvider meterProvider;
-  private final Map<String, io.opentelemetry.api.metrics.Meter> meters = new ConcurrentHashMap<>();
-  private final PrometheusMetricReader metricReader;
+  private final ConcurrentMap<String, MeterProviderAndReaders> meterProviderAndReaders =
+      new ConcurrentHashMap<>();
 
   public SolrMetricManager() {
     metricsConfig = new MetricsConfig.MetricsConfigBuilder().build();
@@ -157,15 +158,10 @@ public class SolrMetricManager {
     meterSupplier = MetricSuppliers.meterSupplier(null, null);
     timerSupplier = MetricSuppliers.timerSupplier(null, null);
     histogramSupplier = MetricSuppliers.histogramSupplier(null, null);
-    meterProvider = MetricUtils.getMeterProvider();
-    metricReader = null;
   }
 
-  public SolrMetricManager(
-      SolrResourceLoader loader, MetricsConfig metricsConfig, PrometheusMetricReader metricReader) {
+  public SolrMetricManager(SolrResourceLoader loader, MetricsConfig metricsConfig) {
     this.metricsConfig = metricsConfig;
-    this.metricReader = metricReader;
-    this.meterProvider = MetricUtils.getMeterProvider();
     counterSupplier = MetricSuppliers.counterSupplier(loader, metricsConfig.getCounterSupplier());
     meterSupplier = MetricSuppliers.meterSupplier(loader, metricsConfig.getMeterSupplier());
     timerSupplier = MetricSuppliers.timerSupplier(loader, metricsConfig.getTimerSupplier());
@@ -176,7 +172,10 @@ public class SolrMetricManager {
   public LongCounter longCounter(
       String registry, String counterName, String description, String unit) {
     LongCounterBuilder builder =
-        meterProvider.get(registry).counterBuilder(counterName).setDescription(description);
+        meterProvider(registry)
+            .get(OTEL_SCOPE_NAME)
+            .counterBuilder(counterName)
+            .setDescription(description);
     if (unit != null) builder.setUnit(unit);
 
     return builder.build();
@@ -185,7 +184,10 @@ public class SolrMetricManager {
   public LongUpDownCounter longUpDownCounter(
       String registry, String counterName, String description, String unit) {
     LongUpDownCounterBuilder builder =
-        meterProvider.get(registry).upDownCounterBuilder(counterName).setDescription(description);
+        meterProvider(registry)
+            .get(OTEL_SCOPE_NAME)
+            .upDownCounterBuilder(counterName)
+            .setDescription(description);
     if (unit != null) builder.setUnit(unit);
 
     return builder.build();
@@ -194,8 +196,8 @@ public class SolrMetricManager {
   public DoubleUpDownCounter doubleUpDownCounter(
       String registry, String counterName, String description, String unit) {
     DoubleUpDownCounterBuilder builder =
-        meterProvider
-            .get(registry)
+        meterProvider(registry)
+            .get(OTEL_SCOPE_NAME)
             .upDownCounterBuilder(counterName)
             .setDescription(description)
             .ofDoubles();
@@ -207,8 +209,8 @@ public class SolrMetricManager {
   public DoubleCounter doubleCounter(
       String registry, String counterName, String description, String unit) {
     DoubleCounterBuilder builder =
-        meterProvider
-            .get(registry)
+        meterProvider(registry)
+            .get(OTEL_SCOPE_NAME)
             .counterBuilder(counterName)
             .setDescription(description)
             .ofDoubles();
@@ -220,7 +222,10 @@ public class SolrMetricManager {
   public DoubleHistogram doubleHistogram(
       String registry, String histogramName, String description, String unit) {
     DoubleHistogramBuilder builder =
-        meterProvider.get(registry).histogramBuilder(histogramName).setDescription(description);
+        meterProvider(registry)
+            .get(OTEL_SCOPE_NAME)
+            .histogramBuilder(histogramName)
+            .setDescription(description);
     if (unit != null) builder.setUnit(unit);
 
     return builder.build();
@@ -229,8 +234,8 @@ public class SolrMetricManager {
   public LongHistogram longHistogram(
       String registry, String histogramName, String description, String unit) {
     LongHistogramBuilder builder =
-        meterProvider
-            .get(registry)
+        meterProvider(registry)
+            .get(OTEL_SCOPE_NAME)
             .histogramBuilder(histogramName)
             .setDescription(description)
             .ofLongs();
@@ -243,7 +248,10 @@ public class SolrMetricManager {
   public DoubleGauge doubleGauge(
       String registry, String gaugeName, String description, String unit) {
     DoubleGaugeBuilder builder =
-        meterProvider.get(registry).gaugeBuilder(gaugeName).setDescription(description);
+        meterProvider(registry)
+            .get(OTEL_SCOPE_NAME)
+            .gaugeBuilder(gaugeName)
+            .setDescription(description);
     if (unit != null) builder.setUnit(unit);
 
     return builder.build();
@@ -251,7 +259,11 @@ public class SolrMetricManager {
 
   public LongGauge longGauge(String registry, String gaugeName, String description, String unit) {
     LongGaugeBuilder builder =
-        meterProvider.get(registry).gaugeBuilder(gaugeName).setDescription(description).ofLongs();
+        meterProvider(registry)
+            .get(OTEL_SCOPE_NAME)
+            .gaugeBuilder(gaugeName)
+            .setDescription(description)
+            .ofLongs();
     if (unit != null) builder.setUnit(unit);
 
     return builder.build();
@@ -264,7 +276,10 @@ public class SolrMetricManager {
       Consumer<ObservableLongMeasurement> callback,
       String unit) {
     LongCounterBuilder builder =
-        meterProvider.get(registry).counterBuilder(counterName).setDescription(description);
+        meterProvider(registry)
+            .get(OTEL_SCOPE_NAME)
+            .counterBuilder(counterName)
+            .setDescription(description);
     if (unit != null) builder.setUnit(unit);
 
     return builder.buildWithCallback(callback);
@@ -277,8 +292,8 @@ public class SolrMetricManager {
       Consumer<ObservableDoubleMeasurement> callback,
       String unit) {
     DoubleCounterBuilder builder =
-        meterProvider
-            .get(registry)
+        meterProvider(registry)
+            .get(OTEL_SCOPE_NAME)
             .counterBuilder(counterName)
             .setDescription(description)
             .ofDoubles();
@@ -294,7 +309,28 @@ public class SolrMetricManager {
       Consumer<ObservableLongMeasurement> callback,
       String unit) {
     LongGaugeBuilder builder =
-        meterProvider.get(registry).gaugeBuilder(gaugeName).setDescription(description).ofLongs();
+        meterProvider(registry)
+            .get(OTEL_SCOPE_NAME)
+            .gaugeBuilder(gaugeName)
+            .setDescription(description)
+            .ofLongs();
+    if (unit != null) builder.setUnit(unit);
+
+    return builder.buildWithCallback(callback);
+  }
+
+  public ObservableDoubleGauge observableDoubleGauge(
+      String registry,
+      String gaugeName,
+      String description,
+      Consumer<ObservableDoubleMeasurement> callback,
+      String unit) {
+    DoubleGaugeBuilder builder =
+        meterProvider(registry)
+            .get(OTEL_SCOPE_NAME)
+            .gaugeBuilder(gaugeName)
+            .setDescription(description);
+
     if (unit != null) builder.setUnit(unit);
 
     return builder.buildWithCallback(callback);
@@ -307,7 +343,10 @@ public class SolrMetricManager {
       Consumer<ObservableLongMeasurement> callback,
       String unit) {
     LongUpDownCounterBuilder builder =
-        meterProvider.get(registry).upDownCounterBuilder(counterName).setDescription(description);
+        meterProvider(registry)
+            .get(OTEL_SCOPE_NAME)
+            .upDownCounterBuilder(counterName)
+            .setDescription(description);
     if (unit != null) builder.setUnit(unit);
 
     return builder.buildWithCallback(callback);
@@ -320,8 +359,8 @@ public class SolrMetricManager {
       Consumer<ObservableDoubleMeasurement> callback,
       String unit) {
     DoubleUpDownCounterBuilder builder =
-        meterProvider
-            .get(registry)
+        meterProvider(registry)
+            .get(OTEL_SCOPE_NAME)
             .upDownCounterBuilder(counterName)
             .setDescription(description)
             .ofDoubles();
@@ -330,10 +369,6 @@ public class SolrMetricManager {
     return builder.buildWithCallback(callback);
   }
 
-  public io.opentelemetry.api.metrics.Meter getOtelRegistry(String registryName) {
-    return meters.get(registryName);
-  }
-
   // for unit tests
   public MetricRegistry.MetricSupplier<Counter> getCounterSupplier() {
     return counterSupplier;
@@ -578,6 +613,7 @@ public class SolrMetricManager {
   }
 
   /** Return a set of existing registry names. */
+  // NOCOMMIT: Remove for OTEL
   public Set<String> registryNames() {
     Set<String> set = new HashSet<>();
     set.addAll(registries.keySet());
@@ -598,6 +634,10 @@ public class SolrMetricManager {
     return names.contains(name);
   }
 
+  public boolean hasMeterProvider(String name) {
+    return meterProviderAndReaders.containsKey(enforcePrefix(name));
+  }
+
   /**
    * Return set of existing registry names that match a regex pattern
    *
@@ -654,7 +694,7 @@ public class SolrMetricManager {
    * @param registry name of the registry
    * @return existing or newly created registry
    */
-  // TODO SOLR-17458: We may not need
+  // NOCOMMIT SOLR-17458: We may not need
   public MetricRegistry registry(String registry) {
     return registry(registry, true);
   }
@@ -684,6 +724,29 @@ public class SolrMetricManager {
     }
   }
 
+  /**
+   * Get (or create if not present) a named {@link SdkMeterProvider} under {@link
+   * MeterProviderAndReaders}. This also registers a corresponding {@link PrometheusMetricReader}
+   * Dropwizards's {@link SolrMetricManager#registry(String)} equivalent
+   *
+   * @param providerName name of the meter provider and prometheus metric reader
+   * @return existing or newly created meter provider
+   */
+  public SdkMeterProvider meterProvider(String providerName) {
+    providerName = enforcePrefix(providerName);
+    return meterProviderAndReaders
+        .computeIfAbsent(
+            providerName,
+            key -> {
+              var reader = new PrometheusMetricReader(true, null);
+              // NOCOMMIT: We need to add a Periodic Metric Reader here if we want to push with OTLP
+              // with an exporter
+              var provider = SdkMeterProvider.builder().registerMetricReader(reader).build();
+              return new MeterProviderAndReaders(provider, reader);
+            })
+        .sdkMeterProvider();
+  }
+
   // TODO SOLR-17458: We may not need
   private static MetricRegistry getOrCreateRegistry(
       ConcurrentMap<String, MetricRegistry> map, String registry) {
@@ -702,12 +765,14 @@ public class SolrMetricManager {
   }
 
   /**
-   * Remove a named registry.
+   * Remove a named registry and close an existing {@link SdkMeterProvider}. Upon closing of
+   * provider, all metric readers registered to it are closed.
    *
    * @param registry name of the registry to remove
    */
   // TODO SOLR-17458: You can't delete OTEL meters
   public void removeRegistry(String registry) {
+    // NOCOMMIT Remove all closing Dropwizard registries
     // close any reporters for this registry first
     closeReporters(registry, null);
     // make sure we use a name with prefix
@@ -722,6 +787,12 @@ public class SolrMetricManager {
         swapLock.unlock();
       }
     }
+    meterProviderAndReaders.computeIfPresent(
+        registry,
+        (key, meterAndReader) -> {
+          meterAndReader.sdkMeterProvider().close();
+          return null;
+        });
   }
 
   /**
@@ -827,8 +898,9 @@ public class SolrMetricManager {
    *     start with the prefix will be removed.
    * @return set of metrics names that have been removed.
    */
-  // TODO SOLR-17458: This is not supported in otel. Metrics are immutable. We can at best filter
-  // them
+  // NOCOMMIT SOLR-17458: This is not supported in otel. Metrics are immutable. We can at best
+  // filter
+  // them or delete the meterProvider entirely
   public Set<String> clearMetrics(String registry, String... metricPath) {
     PrefixFilter filter;
     if (metricPath == null || metricPath.length == 0) {
@@ -1603,7 +1675,17 @@ public class SolrMetricManager {
     return metricsConfig;
   }
 
-  public PrometheusMetricReader getMetricReader() {
-    return this.metricReader;
+  /** Get a shallow copied map of {@link PrometheusMetricReader}. */
+  public Map<String, PrometheusMetricReader> getPrometheusMetricReaders() {
+    return meterProviderAndReaders.entrySet().stream()
+        .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().prometheusMetricReader()));
+  }
+
+  public PrometheusMetricReader getPrometheusMetricReader(String providerName) {
+    MeterProviderAndReaders mpr = meterProviderAndReaders.get(enforcePrefix(providerName));
+    return (mpr != null) ? mpr.prometheusMetricReader() : null;
   }
+
+  private record MeterProviderAndReaders(
+      SdkMeterProvider sdkMeterProvider, PrometheusMetricReader prometheusMetricReader) {}
 }
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java
index 269575798ec..222ce2e0e8a 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java
@@ -30,6 +30,8 @@ import io.opentelemetry.api.metrics.LongCounter;
 import io.opentelemetry.api.metrics.LongGauge;
 import io.opentelemetry.api.metrics.LongHistogram;
 import io.opentelemetry.api.metrics.LongUpDownCounter;
+import io.opentelemetry.api.metrics.ObservableDoubleGauge;
+import io.opentelemetry.api.metrics.ObservableDoubleMeasurement;
 import io.opentelemetry.api.metrics.ObservableLongGauge;
 import io.opentelemetry.api.metrics.ObservableLongMeasurement;
 import java.util.Map;
@@ -206,17 +208,31 @@ public class SolrMetricsContext {
 
   public ObservableLongGauge observableLongGauge(
       String metricName, String description, Consumer<ObservableLongMeasurement> callback) {
-    return metricManager.observableLongGauge(registryName, metricName, description, callback, null);
+    return observableLongGauge(metricName, description, callback, null);
   }
 
   public ObservableLongGauge observableLongGauge(
       String metricName,
       String description,
-      String unit,
-      Consumer<ObservableLongMeasurement> callback) {
+      Consumer<ObservableLongMeasurement> callback,
+      String unit) {
     return metricManager.observableLongGauge(registryName, metricName, description, callback, unit);
   }
 
+  public ObservableDoubleGauge observableDoubleGauge(
+      String metricName, String description, Consumer<ObservableDoubleMeasurement> callback) {
+    return observableDoubleGauge(metricName, description, callback, null);
+  }
+
+  public ObservableDoubleGauge observableDoubleGauge(
+      String metricName,
+      String description,
+      Consumer<ObservableDoubleMeasurement> callback,
+      String unit) {
+    return metricManager.observableDoubleGauge(
+        registryName, metricName, description, callback, unit);
+  }
+
   /**
    * Convenience method for {@link SolrMetricManager#meter(SolrMetricsContext, String, String,
    * String...)}.
diff --git a/solr/core/src/java/org/apache/solr/response/PrometheusResponseWriter.java b/solr/core/src/java/org/apache/solr/response/PrometheusResponseWriter.java
index 5a375bdb408..77fd3642b2a 100644
--- a/solr/core/src/java/org/apache/solr/response/PrometheusResponseWriter.java
+++ b/solr/core/src/java/org/apache/solr/response/PrometheusResponseWriter.java
@@ -16,19 +16,27 @@
  */
 package org.apache.solr.response;
 
+import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
+import io.opentelemetry.sdk.metrics.SdkMeterProvider;
 import io.prometheus.metrics.expositionformats.PrometheusTextFormatWriter;
+import io.prometheus.metrics.model.snapshots.CounterSnapshot;
+import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
+import io.prometheus.metrics.model.snapshots.HistogramSnapshot;
+import io.prometheus.metrics.model.snapshots.InfoSnapshot;
+import io.prometheus.metrics.model.snapshots.MetricSnapshot;
 import io.prometheus.metrics.model.snapshots.MetricSnapshots;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.lang.invoke.MethodHandles;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.solr.handler.admin.MetricsHandler;
 import org.apache.solr.request.SolrQueryRequest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-/**
- * Response writer for Prometheus metrics. This is used only by the {@link
- * org.apache.solr.handler.admin.MetricsHandler}
- */
+/** Response writer for Prometheus metrics. This is used only by the {@link MetricsHandler} */
 @SuppressWarnings(value = "unchecked")
 public class PrometheusResponseWriter implements QueryResponseWriter {
   // not TextQueryResponseWriter because Prometheus libs work with an OutputStream
@@ -40,12 +48,104 @@ public class PrometheusResponseWriter implements QueryResponseWriter {
   public void write(
       OutputStream out, SolrQueryRequest request, SolrQueryResponse response, String contentType)
       throws IOException {
-    var prometheusTextFormatWriter = new PrometheusTextFormatWriter(false);
-    prometheusTextFormatWriter.write(out, (MetricSnapshots) response.getValues().get("metrics"));
+
+    Map<String, PrometheusMetricReader> readers =
+        (Map<String, PrometheusMetricReader>) response.getValues().get("metrics");
+
+    List<MetricSnapshot> snapshots =
+        readers.values().stream().flatMap(r -> r.collect().stream()).toList();
+
+    new PrometheusTextFormatWriter(false).write(out, mergeSnapshots(snapshots));
   }
 
   @Override
   public String getContentType(SolrQueryRequest request, SolrQueryResponse response) {
     return CONTENT_TYPE_PROMETHEUS;
   }
+
+  /**
+   * Merge a collection of individual {@link MetricSnapshot} instances into one {@link
+   * MetricSnapshots}. This is necessary because we create a {@link SdkMeterProvider} per Solr core
+   * resulting in duplicate metric names across cores which is an illegal format if not under the
+   * same prometheus grouping.
+   */
+  private MetricSnapshots mergeSnapshots(List<MetricSnapshot> snapshots) {
+    Map<String, CounterSnapshot.Builder> counterSnapshotMap = new HashMap<>();
+    Map<String, GaugeSnapshot.Builder> gaugeSnapshotMap = new HashMap<>();
+    Map<String, HistogramSnapshot.Builder> histogramSnapshotMap = new HashMap<>();
+    InfoSnapshot otelInfoSnapshots = null;
+
+    for (MetricSnapshot snapshot : snapshots) {
+      String metricName = snapshot.getMetadata().getPrometheusName();
+
+      switch (snapshot) {
+        case CounterSnapshot counterSnapshot -> {
+          CounterSnapshot.Builder builder =
+              counterSnapshotMap.computeIfAbsent(
+                  metricName,
+                  k -> {
+                    var base =
+                        CounterSnapshot.builder()
+                            .name(counterSnapshot.getMetadata().getName())
+                            .help(counterSnapshot.getMetadata().getHelp());
+                    return counterSnapshot.getMetadata().hasUnit()
+                        ? base.unit(counterSnapshot.getMetadata().getUnit())
+                        : base;
+                  });
+          counterSnapshot.getDataPoints().forEach(builder::dataPoint);
+        }
+        case GaugeSnapshot gaugeSnapshot -> {
+          GaugeSnapshot.Builder builder =
+              gaugeSnapshotMap.computeIfAbsent(
+                  metricName,
+                  k -> {
+                    var base =
+                        GaugeSnapshot.builder()
+                            .name(gaugeSnapshot.getMetadata().getName())
+                            .help(gaugeSnapshot.getMetadata().getHelp());
+                    return gaugeSnapshot.getMetadata().hasUnit()
+                        ? base.unit(gaugeSnapshot.getMetadata().getUnit())
+                        : base;
+                  });
+          gaugeSnapshot.getDataPoints().forEach(builder::dataPoint);
+        }
+        case HistogramSnapshot histogramSnapshot -> {
+          HistogramSnapshot.Builder builder =
+              histogramSnapshotMap.computeIfAbsent(
+                  metricName,
+                  k -> {
+                    var base =
+                        HistogramSnapshot.builder()
+                            .name(histogramSnapshot.getMetadata().getName())
+                            .help(histogramSnapshot.getMetadata().getHelp());
+                    return histogramSnapshot.getMetadata().hasUnit()
+                        ? base.unit(histogramSnapshot.getMetadata().getUnit())
+                        : base;
+                  });
+          histogramSnapshot.getDataPoints().forEach(builder::dataPoint);
+        }
+        case InfoSnapshot infoSnapshot -> {
+          // InfoSnapshot is a special case in that each SdkMeterProvider will create a duplicate
+          // metric called target_info containing OTEL SDK metadata. Only one of these need to be
+          // kept
+          if (otelInfoSnapshots == null)
+            otelInfoSnapshots =
+                new InfoSnapshot(infoSnapshot.getMetadata(), infoSnapshot.getDataPoints());
+        }
+        default -> {
+          log.warn(
+              "Unexpected snapshot type: {} for metric {}",
+              snapshot.getClass().getName(),
+              snapshot.getMetadata().getName());
+        }
+      }
+    }
+
+    MetricSnapshots.Builder snapshotsBuilder = MetricSnapshots.builder();
+    counterSnapshotMap.values().forEach(b -> snapshotsBuilder.metricSnapshot(b.build()));
+    gaugeSnapshotMap.values().forEach(b -> snapshotsBuilder.metricSnapshot(b.build()));
+    histogramSnapshotMap.values().forEach(b -> snapshotsBuilder.metricSnapshot(b.build()));
+    if (otelInfoSnapshots != null) snapshotsBuilder.metricSnapshot(otelInfoSnapshots);
+    return snapshotsBuilder.build();
+  }
 }
diff --git a/solr/core/src/java/org/apache/solr/util/stats/MetricUtils.java b/solr/core/src/java/org/apache/solr/util/stats/MetricUtils.java
index 8b79fc26b35..9cff065c06f 100644
--- a/solr/core/src/java/org/apache/solr/util/stats/MetricUtils.java
+++ b/solr/core/src/java/org/apache/solr/util/stats/MetricUtils.java
@@ -26,10 +26,6 @@ import com.codahale.metrics.MetricFilter;
 import com.codahale.metrics.MetricRegistry;
 import com.codahale.metrics.Snapshot;
 import com.codahale.metrics.Timer;
-import io.opentelemetry.api.GlobalOpenTelemetry;
-import io.opentelemetry.api.common.Attributes;
-import io.opentelemetry.api.common.AttributesBuilder;
-import io.opentelemetry.api.metrics.MeterProvider;
 import java.beans.BeanInfo;
 import java.beans.IntrospectionException;
 import java.beans.Introspector;
@@ -55,7 +51,6 @@ import java.util.function.Predicate;
 import org.apache.solr.common.ConditionalKeyMapWriter;
 import org.apache.solr.common.IteratorWriter;
 import org.apache.solr.common.MapWriter;
-import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.SolrInfoBean;
@@ -857,24 +852,4 @@ public class MetricUtils {
       }
     }
   }
-
-  public static MeterProvider getMeterProvider() {
-    return GlobalOpenTelemetry.getMeterProvider();
-  }
-
-  public static Attributes createAttributes(String... attributes) {
-    if (attributes.length % 2 == 1) {
-      throw new SolrException(
-          SolrException.ErrorCode.SERVER_ERROR, "Odd number of field/value strings passed");
-    }
-
-    AttributesBuilder builder = Attributes.builder();
-    for (int i = 0; i < attributes.length; i += 2) {
-      String key = attributes[i];
-      String value = attributes[i + 1];
-      builder.put(key, value);
-    }
-
-    return builder.build();
-  }
 }
diff --git a/solr/core/src/test/org/apache/solr/core/TestTracerConfigurator.java b/solr/core/src/test/org/apache/solr/core/TestTracerConfigurator.java
index ba453efc5d4..5aa8ffec1ed 100644
--- a/solr/core/src/test/org/apache/solr/core/TestTracerConfigurator.java
+++ b/solr/core/src/test/org/apache/solr/core/TestTracerConfigurator.java
@@ -41,7 +41,7 @@ public class TestTracerConfigurator extends SolrTestCaseJ4 {
   public void configuratorClassDoesNotExistTest() {
     assertTrue(OpenTelemetryConfigurator.shouldAutoConfigOTEL());
     SolrResourceLoader loader = new SolrResourceLoader(TEST_PATH().resolve("collection1"));
-    OpenTelemetryConfigurator.initializeOpenTelemetrySdk(null, loader, null);
+    OpenTelemetryConfigurator.initializeOpenTelemetrySdk(null, loader);
     assertEquals(
         "Expecting noop otel after failure to auto-init",
         TracerProvider.noop().get(null),
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 96001e15b63..07f71176591 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
@@ -48,6 +48,7 @@ import org.junit.BeforeClass;
 import org.junit.Test;
 
 /** Test for {@link MetricsHandler} */
+// NOCOMMIT SOLR-17785: Lets move this to SolrCloudTestCase
 public class MetricsHandlerTest extends SolrTestCaseJ4 {
   @BeforeClass
   public static void beforeClass() throws Exception {
diff --git a/solr/core/src/test/org/apache/solr/metrics/MetricsConfigTest.java b/solr/core/src/test/org/apache/solr/metrics/MetricsConfigTest.java
index 32245ffb4c6..eff63f4db72 100644
--- a/solr/core/src/test/org/apache/solr/metrics/MetricsConfigTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/MetricsConfigTest.java
@@ -35,7 +35,7 @@ public class MetricsConfigTest extends SolrTestCaseJ4 {
   public void testDefaults() {
     NodeConfig cfg = loadNodeConfig("solr-metricsconfig.xml");
     SolrMetricManager mgr =
-        new SolrMetricManager(cfg.getSolrResourceLoader(), cfg.getMetricsConfig(), null);
+        new SolrMetricManager(cfg.getSolrResourceLoader(), cfg.getMetricsConfig());
     assertTrue(mgr.getCounterSupplier() instanceof MetricSuppliers.DefaultCounterSupplier);
     assertTrue(mgr.getMeterSupplier() instanceof MetricSuppliers.DefaultMeterSupplier);
     assertTrue(mgr.getTimerSupplier() instanceof MetricSuppliers.DefaultTimerSupplier);
@@ -54,7 +54,7 @@ public class MetricsConfigTest extends SolrTestCaseJ4 {
     System.setProperty("histogram.reservoir", SlidingTimeWindowReservoir.class.getName());
     NodeConfig cfg = loadNodeConfig("solr-metricsconfig.xml");
     SolrMetricManager mgr =
-        new SolrMetricManager(cfg.getSolrResourceLoader(), cfg.getMetricsConfig(), null);
+        new SolrMetricManager(cfg.getSolrResourceLoader(), cfg.getMetricsConfig());
     assertTrue(mgr.getCounterSupplier() instanceof MetricSuppliers.DefaultCounterSupplier);
     assertTrue(mgr.getMeterSupplier() instanceof MetricSuppliers.DefaultMeterSupplier);
     assertTrue(mgr.getTimerSupplier() instanceof MetricSuppliers.DefaultTimerSupplier);
@@ -73,7 +73,7 @@ public class MetricsConfigTest extends SolrTestCaseJ4 {
     System.setProperty("histogram.class", MockHistogramSupplier.class.getName());
     NodeConfig cfg = loadNodeConfig("solr-metricsconfig.xml");
     SolrMetricManager mgr =
-        new SolrMetricManager(cfg.getSolrResourceLoader(), cfg.getMetricsConfig(), null);
+        new SolrMetricManager(cfg.getSolrResourceLoader(), cfg.getMetricsConfig());
     assertTrue(mgr.getCounterSupplier() instanceof MockCounterSupplier);
     assertTrue(mgr.getMeterSupplier() instanceof MockMeterSupplier);
     assertTrue(mgr.getTimerSupplier() instanceof MockTimerSupplier);
@@ -100,7 +100,7 @@ public class MetricsConfigTest extends SolrTestCaseJ4 {
     System.setProperty("metricsEnabled", "false");
     NodeConfig cfg = loadNodeConfig("solr-metricsconfig.xml");
     SolrMetricManager mgr =
-        new SolrMetricManager(cfg.getSolrResourceLoader(), cfg.getMetricsConfig(), null);
+        new SolrMetricManager(cfg.getSolrResourceLoader(), cfg.getMetricsConfig());
     assertTrue(mgr.getCounterSupplier() instanceof MetricSuppliers.NoOpCounterSupplier);
     assertTrue(mgr.getMeterSupplier() instanceof MetricSuppliers.NoOpMeterSupplier);
     assertTrue(mgr.getTimerSupplier() instanceof MetricSuppliers.NoOpTimerSupplier);
@@ -111,7 +111,7 @@ public class MetricsConfigTest extends SolrTestCaseJ4 {
   public void testMissingValuesConfig() {
     NodeConfig cfg = loadNodeConfig("solr-metricsconfig1.xml");
     SolrMetricManager mgr =
-        new SolrMetricManager(cfg.getSolrResourceLoader(), cfg.getMetricsConfig(), null);
+        new SolrMetricManager(cfg.getSolrResourceLoader(), cfg.getMetricsConfig());
     assertNull("nullNumber", mgr.nullNumber());
     assertEquals("notANumber", -1, mgr.notANumber());
     assertEquals("nullNumber", "", mgr.nullString());
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java b/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
index 730fe0989aa..2c8f51aec6d 100644
--- a/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
@@ -20,10 +20,28 @@ package org.apache.solr.metrics;
 import com.codahale.metrics.Counter;
 import com.codahale.metrics.Metric;
 import com.codahale.metrics.MetricRegistry;
+import com.google.common.util.concurrent.AtomicDouble;
+import io.opentelemetry.api.metrics.DoubleCounter;
+import io.opentelemetry.api.metrics.DoubleGauge;
+import io.opentelemetry.api.metrics.DoubleHistogram;
+import io.opentelemetry.api.metrics.DoubleUpDownCounter;
+import io.opentelemetry.api.metrics.LongCounter;
+import io.opentelemetry.api.metrics.LongGauge;
+import io.opentelemetry.api.metrics.LongHistogram;
+import io.opentelemetry.api.metrics.LongUpDownCounter;
+import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
+import io.prometheus.metrics.model.snapshots.CounterSnapshot;
+import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
+import io.prometheus.metrics.model.snapshots.HistogramSnapshot;
+import io.prometheus.metrics.model.snapshots.MetricSnapshot;
+import io.prometheus.metrics.model.snapshots.MetricSnapshots;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Random;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.DoubleAdder;
+import java.util.concurrent.atomic.LongAdder;
 import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.util.NamedList;
@@ -31,10 +49,24 @@ import org.apache.solr.core.PluginInfo;
 import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.core.SolrResourceLoader;
 import org.apache.solr.metrics.reporters.MockMetricReporter;
+import org.junit.Before;
 import org.junit.Test;
 
 public class SolrMetricManagerTest extends SolrTestCaseJ4 {
+  final String METER_PROVIDER_NAME = "test_provider_name";
+  private SolrMetricManager metricManager;
+  private PrometheusMetricReader reader;
+
+  @Before
+  public void setUp() throws Exception {
+    super.setUp();
+    this.metricManager = new SolrMetricManager();
+    // Initialize a metric reader for tests
+    metricManager.meterProvider(METER_PROVIDER_NAME);
+    this.reader = metricManager.getPrometheusMetricReader(METER_PROVIDER_NAME);
+  }
 
+  // NOCOMMIT: We might not be supported core swapping in 10. Maybe remove this test
   @Test
   public void testSwapRegistries() {
     Random r = random();
@@ -76,6 +108,8 @@ public class SolrMetricManagerTest extends SolrTestCaseJ4 {
     }
   }
 
+  // NOCOMMIT: Migration of this to OTEL isn't possible. You can't register instruments to a
+  // meterprovider that the provider itself didn't create
   @Test
   public void testRegisterAll() throws Exception {
     Random r = random();
@@ -104,6 +138,8 @@ public class SolrMetricManagerTest extends SolrTestCaseJ4 {
                 registryName, mr, SolrMetricManager.ResolutionStrategy.ERROR));
   }
 
+  // NOCOMMIT: Migration of this to OTEL isn't possible. You can only delete the whole
+  // sdkMeterProvider and all it's recorded metrics
   @Test
   public void testClearMetrics() {
     Random r = random();
@@ -270,4 +306,230 @@ public class SolrMetricManagerTest extends SolrTestCaseJ4 {
     initArgs.add("configurable", "true");
     return new PluginInfo("SolrMetricReporter", attrs, initArgs, null);
   }
+
+  @Test
+  public void testLongCounter() {
+    LongCounter counter =
+        metricManager.longCounter(METER_PROVIDER_NAME, "my_counter", "desc", null);
+    counter.add(5);
+    counter.add(3);
+    CounterSnapshot actual = snapshot("my_counter", CounterSnapshot.class);
+    assertEquals(8.0, actual.getDataPoints().getFirst().getValue(), 0.0);
+  }
+
+  @Test
+  public void testLongUpDownCounter() {
+    LongUpDownCounter counter =
+        metricManager.longUpDownCounter(METER_PROVIDER_NAME, "long_updown_counter", "desc", null);
+    counter.add(10);
+    counter.add(-4);
+    GaugeSnapshot actual = snapshot("long_updown_counter", GaugeSnapshot.class);
+    assertEquals(6.0, actual.getDataPoints().getFirst().getValue(), 0.0);
+  }
+
+  @Test
+  public void testDoubleUpDownCounter() {
+    DoubleUpDownCounter counter =
+        metricManager.doubleUpDownCounter(
+            METER_PROVIDER_NAME, "double_updown_counter", "desc", null);
+    counter.add(10.0);
+    counter.add(-5.5);
+
+    GaugeSnapshot actual = snapshot("double_updown_counter", GaugeSnapshot.class);
+    assertEquals(4.5, actual.getDataPoints().getFirst().getValue(), 0.0);
+  }
+
+  @Test
+  public void testDoubleCounter() {
+    DoubleCounter counter =
+        metricManager.doubleCounter(METER_PROVIDER_NAME, "double_counter", "desc", null);
+    counter.add(10.0);
+    counter.add(5.5);
+
+    CounterSnapshot actual = snapshot("double_counter", CounterSnapshot.class);
+    assertEquals(15.5, actual.getDataPoints().getFirst().getValue(), 0.0);
+  }
+
+  @Test
+  public void testDoubleHistogram() {
+    DoubleHistogram histogram =
+        metricManager.doubleHistogram(METER_PROVIDER_NAME, "double_histogram", "desc", null);
+    histogram.record(1.1);
+    histogram.record(2.2);
+    histogram.record(3.3);
+
+    HistogramSnapshot actual = snapshot("double_histogram", HistogramSnapshot.class);
+    assertEquals(3, actual.getDataPoints().getFirst().getCount());
+    assertEquals(6.6, actual.getDataPoints().getFirst().getSum(), 0.0);
+  }
+
+  @Test
+  public void testLongHistogram() {
+    LongHistogram histogram =
+        metricManager.longHistogram(METER_PROVIDER_NAME, "long_histogram", "desc", null);
+    histogram.record(1);
+    histogram.record(2);
+    histogram.record(3);
+
+    HistogramSnapshot actual = snapshot("long_histogram", HistogramSnapshot.class);
+    assertEquals(3, actual.getDataPoints().getFirst().getCount());
+    assertEquals(6.0, actual.getDataPoints().getFirst().getSum(), 0.0);
+  }
+
+  @Test
+  public void testDoubleGauge() {
+    DoubleGauge gauge =
+        metricManager.doubleGauge(METER_PROVIDER_NAME, "double_gauge", "desc", null);
+    gauge.set(10.0);
+    gauge.set(5.5);
+
+    GaugeSnapshot actual = snapshot("double_gauge", GaugeSnapshot.class);
+    assertEquals(5.5, actual.getDataPoints().getFirst().getValue(), 0.0);
+  }
+
+  @Test
+  public void testLongGauge() {
+    LongGauge gauge = metricManager.longGauge(METER_PROVIDER_NAME, "long_gauge", "desc", null);
+    gauge.set(10);
+    gauge.set(5);
+
+    GaugeSnapshot actual = snapshot("long_gauge", GaugeSnapshot.class);
+    assertEquals(5.0, actual.getDataPoints().getFirst().getValue(), 0.0);
+  }
+
+  @Test
+  public void testObservableLongCounter() {
+    LongAdder val = new LongAdder();
+    metricManager.observableLongCounter(
+        METER_PROVIDER_NAME, "obs_long_counter", "desc", m -> m.record(val.longValue()), null);
+    val.add(10);
+
+    CounterSnapshot actual = snapshot("obs_long_counter", CounterSnapshot.class);
+    assertEquals(10.0, actual.getDataPoints().getFirst().getValue(), 0.0);
+
+    val.add(20);
+    actual = snapshot("obs_long_counter", CounterSnapshot.class);
+
+    // Observable metrics value changes anytime metricReader collects() to trigger callback
+    assertEquals(30.0, actual.getDataPoints().getFirst().getValue(), 0.0);
+  }
+
+  @Test
+  public void testObservableDoubleCounter() {
+    DoubleAdder val = new DoubleAdder();
+    metricManager.observableDoubleCounter(
+        METER_PROVIDER_NAME, "obs_double_counter", "desc", m -> m.record(val.doubleValue()), null);
+    val.add(10.0);
+
+    CounterSnapshot actual = snapshot("obs_double_counter", CounterSnapshot.class);
+    assertEquals(10.0, actual.getDataPoints().getFirst().getValue(), 0.0);
+
+    val.add(0.1);
+    actual = snapshot("obs_double_counter", CounterSnapshot.class);
+
+    // Observable metrics value changes anytime metricReader collects() to trigger callback
+    assertEquals(10.1, actual.getDataPoints().getFirst().getValue(), 1e-6);
+  }
+
+  @Test
+  public void testObservableLongGauge() {
+    AtomicLong val = new AtomicLong();
+    metricManager.observableLongGauge(
+        METER_PROVIDER_NAME, "obs_long_gauge", "desc", m -> m.record(val.get()), null);
+    val.set(10L);
+
+    GaugeSnapshot actual = snapshot("obs_long_gauge", GaugeSnapshot.class);
+    assertEquals(10.0, actual.getDataPoints().getFirst().getValue(), 0.0);
+
+    val.set(20L);
+    actual = snapshot("obs_long_gauge", GaugeSnapshot.class);
+
+    // Observable metrics value changes anytime metricReader collects() to trigger callback
+    assertEquals(20.0, actual.getDataPoints().getFirst().getValue(), 0.0);
+  }
+
+  @Test
+  public void testObservableDoubleGauge() {
+    AtomicDouble val = new AtomicDouble();
+    metricManager.observableDoubleGauge(
+        METER_PROVIDER_NAME, "obs_double_gauge", "desc", m -> m.record(val.get()), null);
+    val.set(10.0);
+
+    GaugeSnapshot actual = snapshot("obs_double_gauge", GaugeSnapshot.class);
+    assertEquals(10.0, actual.getDataPoints().getFirst().getValue(), 0.0);
+
+    val.set(10.1);
+    actual = snapshot("obs_double_gauge", GaugeSnapshot.class);
+
+    // Observable metrics value changes anytime metricReader collects() to trigger callback
+    assertEquals(10.1, actual.getDataPoints().getFirst().getValue(), 0.0);
+  }
+
+  @Test
+  public void testObservableLongUpDownCounter() {
+    LongAdder val = new LongAdder();
+    metricManager.observableLongUpDownCounter(
+        METER_PROVIDER_NAME, "obs_long_updown_gauge", "desc", m -> m.record(val.longValue()), null);
+    val.add(10L);
+
+    GaugeSnapshot actual = snapshot("obs_long_updown_gauge", GaugeSnapshot.class);
+    assertEquals(10.0, actual.getDataPoints().getFirst().getValue(), 0.0);
+
+    val.add(-20L);
+    actual = snapshot("obs_long_updown_gauge", GaugeSnapshot.class);
+
+    // Observable metrics value changes anytime metricReader collects() to trigger callback
+    assertEquals(-10.0, actual.getDataPoints().getFirst().getValue(), 0.0);
+  }
+
+  @Test
+  public void testObservableDoubleUpDownCounter() {
+    DoubleAdder val = new DoubleAdder();
+    metricManager.observableDoubleUpDownCounter(
+        METER_PROVIDER_NAME,
+        "obs_double_updown_gauge",
+        "desc",
+        m -> m.record(val.doubleValue()),
+        null);
+    val.add(10.0);
+    GaugeSnapshot actual = snapshot("obs_double_updown_gauge", GaugeSnapshot.class);
+    assertEquals(10.0, actual.getDataPoints().getFirst().getValue(), 0.0);
+
+    val.add(-20.1);
+    actual = snapshot("obs_double_updown_gauge", GaugeSnapshot.class);
+
+    // Observable metrics value changes anytime metricReader collects() to trigger callback
+    assertEquals(-10.1, actual.getDataPoints().getFirst().getValue(), 1e-6);
+  }
+
+  @Test
+  public void testCloseMeterProviders() {
+    LongCounter counter =
+        metricManager.longCounter(METER_PROVIDER_NAME, "my_counter", "desc", null);
+    counter.add(5);
+
+    PrometheusMetricReader reader = metricManager.getPrometheusMetricReader(METER_PROVIDER_NAME);
+    MetricSnapshots metrics = reader.collect();
+    CounterSnapshot data =
+        metrics.stream()
+            .filter(m -> m.getMetadata().getPrometheusName().equals("my_counter"))
+            .map(CounterSnapshot.class::cast)
+            .findFirst()
+            .orElseThrow(() -> new AssertionError("LongCounter metric not found"));
+    assertEquals(5, data.getDataPoints().getFirst().getValue(), 0.0);
+
+    metricManager.removeRegistry(METER_PROVIDER_NAME);
+
+    assertNull(metricManager.getPrometheusMetricReader(METER_PROVIDER_NAME));
+  }
+
+  // Helper to grab any snapshot by name and type
+  private <T extends MetricSnapshot> T snapshot(String name, Class<T> cls) {
+    return reader.collect().stream()
+        .filter(m -> m.getMetadata().getPrometheusName().equals(name))
+        .filter(cls::isInstance)
+        .map(cls::cast)
+        .findFirst()
+        .orElseThrow(() -> new AssertionError("MetricSnapshot not found: " + name));
+  }
 }
diff --git a/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriter.java b/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriter.java
index 4792be1021c..a0010476393 100644
--- a/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriter.java
+++ b/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriter.java
@@ -16,10 +16,6 @@
  */
 package org.apache.solr.response;
 
-import com.codahale.metrics.Counter;
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Meter;
-import com.codahale.metrics.SettableGauge;
 import com.codahale.metrics.SharedMetricRegistries;
 import java.lang.invoke.MethodHandles;
 import java.util.Arrays;
@@ -36,10 +32,8 @@ import org.apache.solr.client.solrj.impl.NoOpResponseParser;
 import org.apache.solr.client.solrj.request.GenericSolrRequest;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.util.NamedList;
-import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.util.ExternalPaths;
 import org.apache.solr.util.SolrJettyTestRule;
-import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Test;
@@ -57,25 +51,22 @@ public class TestPrometheusResponseWriter extends SolrTestCaseJ4 {
 
     solrClientTestRule.startSolr(LuceneTestCase.createTempDir());
     solrClientTestRule
-        .newCollection()
+        .newCollection("core1")
+        .withConfigSet(ExternalPaths.DEFAULT_CONFIGSET.toString())
+        .create();
+    solrClientTestRule
+        .newCollection("core2")
         .withConfigSet(ExternalPaths.DEFAULT_CONFIGSET.toString())
         .create();
     var cc = solrClientTestRule.getCoreContainer();
     cc.waitForLoadingCoresToFinish(30000);
 
-    SolrMetricManager manager = cc.getMetricManager();
-    Counter c = manager.counter(null, "solr.core.collection1", "QUERY./dummy/metrics.requests");
-    c.inc(10);
-    c = manager.counter(null, "solr.node", "ADMIN./dummy/metrics.requests");
-    c.inc(20);
-    Meter m = manager.meter(null, "solr.jetty", "dummyMetrics.2xx-responses");
-    m.mark(30);
-    registerGauge(manager, "solr.jvm", "gc.dummyMetrics.count");
-  }
+    // Populate request metrics on both cores
+    ModifiableSolrParams queryParams = new ModifiableSolrParams();
+    queryParams.set("q", "*:*");
 
-  @AfterClass
-  public static void clearMetricsRegistries() {
-    SharedMetricRegistries.clear();
+    solrClientTestRule.getSolrClient("core1").query(queryParams);
+    solrClientTestRule.getSolrClient("core2").query(queryParams);
   }
 
   @Test
@@ -87,7 +78,6 @@ public class TestPrometheusResponseWriter extends SolrTestCaseJ4 {
 
     try (SolrClient adminClient = getHttpSolrClient(solrClientTestRule.getBaseUrl())) {
       NamedList<Object> res = adminClient.request(req);
-      assertNotNull("null response from server", res);
       String output = (String) res.get("response");
 
       Set<String> seenTypeInfo = new HashSet<>();
@@ -130,20 +120,4 @@ public class TestPrometheusResponseWriter extends SolrTestCaseJ4 {
           });
     }
   }
-
-  private static void registerGauge(
-      SolrMetricManager metricManager, String registry, String metricName) {
-    Gauge<Number> metric =
-        new SettableGauge<>() {
-          @Override
-          public void setValue(Number value) {}
-
-          @Override
-          public Number getValue() {
-            return 0;
-          }
-        };
-    metricManager.registerGauge(
-        null, registry, metric, "", SolrMetricManager.ResolutionStrategy.IGNORE, metricName, "");
-  }
 }
diff --git a/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriterCloud.java b/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriterCloud.java
new file mode 100644
index 00000000000..4ed29e43199
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriterCloud.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.response;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.client.solrj.SolrRequest.METHOD;
+import org.apache.solr.client.solrj.SolrRequest.SolrRequestType;
+import org.apache.solr.client.solrj.impl.InputStreamResponseParser;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.request.GenericSolrRequest;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.util.NamedList;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestPrometheusResponseWriterCloud extends SolrCloudTestCase {
+
+  @BeforeClass
+  public static void setupCluster() throws Exception {
+    System.setProperty("metricsEnabled", "true");
+    configureCluster(1)
+        .addConfig(
+            "config", TEST_PATH().resolve("configsets").resolve("cloud-minimal").resolve("conf"))
+        .configure();
+  }
+
+  @Before
+  public void ensureCollectionsExist() throws Exception {
+    SolrClient client = cluster.getSolrClient();
+    try {
+      CollectionAdminRequest.deleteCollection("collection1").process(client);
+    } catch (Exception ignored) {
+    }
+    try {
+      CollectionAdminRequest.deleteCollection("collection2").process(client);
+    } catch (Exception ignored) {
+    }
+
+    CollectionAdminRequest.createCollection("collection1", "config", 1, 1).process(client);
+    CollectionAdminRequest.createCollection("collection2", "config", 1, 1).process(client);
+    cluster.waitForActiveCollection("collection1", 1, 1);
+    cluster.waitForActiveCollection("collection2", 1, 1);
+  }
+
+  @Test
+  public void testPrometheusCloudLabels() throws Exception {
+    var solrClient = cluster.getSolrClient();
+
+    // Increment solr_metrics_core_requests metric for /select
+    SolrQuery query = new SolrQuery("*:*");
+    solrClient.query("collection1", query);
+
+    var req =
+        new GenericSolrRequest(
+            METHOD.GET,
+            "/admin/metrics",
+            SolrRequestType.ADMIN,
+            new ModifiableSolrParams().set("wt", "prometheus"));
+    req.setResponseParser(new InputStreamResponseParser("prometheus"));
+
+    NamedList<Object> resp = solrClient.request(req);
+    try (InputStream in = (InputStream) resp.get("stream")) {
+      String output = new String(in.readAllBytes(), StandardCharsets.UTF_8);
+      assertTrue(
+          "Missing expected Solr cloud mode prometheus metric with cloud labels",
+          output
+              .lines()
+              .anyMatch(
+                  line ->
+                      line.startsWith("solr_metrics_core_requests_total")
+                          && line.contains("handler=\"/select\"")
+                          && line.contains("collection=\"collection1\"")
+                          && line.contains("core=\"collection1_shard1_replica_n1\"")
+                          && line.contains("replica=\"replica_n1\"")
+                          && line.contains("shard=\"shard1\"")
+                          && line.contains("type=\"requests\"")));
+    }
+  }
+
+  @Test
+  public void testCollectionDeletePrometheusOutput() throws Exception {
+    var solrClient = cluster.getSolrClient();
+
+    // Increment solr_metrics_core_requests metric for /select and assert it exists
+    SolrQuery query = new SolrQuery("*:*");
+    solrClient.query("collection1", query);
+    solrClient.query("collection2", query);
+
+    var req =
+        new GenericSolrRequest(
+            METHOD.GET,
+            "/admin/metrics",
+            SolrRequestType.ADMIN,
+            new ModifiableSolrParams().set("wt", "prometheus"));
+    req.setResponseParser(new InputStreamResponseParser("prometheus"));
+
+    NamedList<Object> resp = solrClient.request(req);
+
+    try (InputStream in = (InputStream) resp.get("stream")) {
+      String output = new String(in.readAllBytes(), StandardCharsets.UTF_8);
+
+      assertTrue(
+          "Prometheus output should contains solr_metrics_core_requests for collection1",
+          output
+              .lines()
+              .anyMatch(
+                  line ->
+                      line.startsWith("solr_metrics_core_requests")
+                          && line.contains("collection1")));
+      assertTrue(
+          "Prometheus output should contains solr_metrics_core_requests for collection2",
+          output
+              .lines()
+              .anyMatch(
+                  line ->
+                      line.startsWith("solr_metrics_core_requests")
+                          && line.contains("collection2")));
+    }
+
+    // Delete collection and assert metrics have been removed
+    var deleteRequest = CollectionAdminRequest.deleteCollection("collection1");
+    deleteRequest.process(solrClient);
+
+    resp = solrClient.request(req);
+    try (InputStream in = (InputStream) resp.get("stream")) {
+      String output = new String(in.readAllBytes(), StandardCharsets.UTF_8);
+      assertFalse(
+          "Prometheus output should not contain solr_metrics_core_requests after collection was deleted",
+          output
+              .lines()
+              .anyMatch(
+                  line ->
+                      line.startsWith("solr_metrics_core_requests")
+                          && line.contains("collection1")));
+      assertTrue(
+          "Prometheus output should contains solr_metrics_core_requests for collection2",
+          output
+              .lines()
+              .anyMatch(
+                  line ->
+                      line.startsWith("solr_metrics_core_requests")
+                          && line.contains("collection2")));
+    }
+  }
+}
diff --git a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
index c475f2b23d3..53e1915f7c2 100644
--- a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
+++ b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
@@ -25,7 +25,6 @@ import static org.hamcrest.core.StringContains.containsString;
 import com.carrotsearch.randomizedtesting.RandomizedContext;
 import com.carrotsearch.randomizedtesting.RandomizedTest;
 import com.carrotsearch.randomizedtesting.rules.SystemPropertiesRestoreRule;
-import io.opentelemetry.api.GlobalOpenTelemetry;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.io.Reader;
@@ -113,6 +112,7 @@ import org.apache.solr.common.util.XML;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.CoresLocator;
 import org.apache.solr.core.NodeConfig;
+import org.apache.solr.core.OpenTelemetryConfigurator;
 import org.apache.solr.core.SolrConfig;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrXmlConfig;
@@ -250,7 +250,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
 
   @BeforeClass
   public static void setupTestCases() {
-    GlobalOpenTelemetry.resetForTest();
+    OpenTelemetryConfigurator.resetForTest();
     resetExceptionIgnores();
 
     testExecutor =
diff --git a/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java b/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java
index 4a894521e24..b559a233a80 100644
--- a/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java
+++ b/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java
@@ -1169,6 +1169,7 @@ public class MiniSolrCloudCluster {
       System.setProperty("solr.cloud.overseer.enabled", Boolean.toString(overseerEnabled));
 
       if (!disableTraceIdGeneration && OpenTelemetryConfigurator.TRACE_ID_GEN_ENABLED) {
+        OpenTelemetryConfigurator.initializeOpenTelemetrySdk(null, null);
         injectRandomRecordingFlag();
       }
 


(solr) 02/33: SOLR-17785: Integrate foundational OTEL meter instruments (#3384)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 437767d52081ae1084cbeabb60f84a872bd0230c
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Mon Jun 23 10:32:33 2025 -0400

    SOLR-17785: Integrate foundational OTEL meter instruments (#3384)
    
    * Refactor initialization of open telemetry
    
    * Use OpenTelemetryConfigurator.loadOpenTelemetrySdk
    
    * Remove dependencies
    
    * Typos
    
    * Changes from review comments
    
    * Fix TestTracerConfigurator
    
    * Create a non-recording tracerProvider as a noop
    
    * Initial commit for foundation
    
    * Refactoring
    
    * Fix RequestHandlerBaseTest
    
    * Remove solr core changes for now
    
    * Add prometheus metric reader to metricManager
    
    * Refactoring for initializeMetrics
    
    * Fix up many tests
    
    * Prometheus fix
    
    * Delete prometheues Formatter code
    
    * Add headers and javadocs
    
    * Add additional todos
    
    * Move OTEL out of coreContainer
    
    * Broken tracer tests
    
    * Rename classes
    
    * Cleanup
    
    * Add ms
    
    * Changes based on comments
    
    * Remove print
    
    * Backwards change on RequestHandlerBaseTest
---
 .../java/org/apache/solr/blockcache/Metrics.java   |   5 +-
 .../java/org/apache/solr/core/CoreContainer.java   |  41 +-
 .../apache/solr/core/HttpSolrClientProvider.java   |   4 +-
 .../solr/core/OpenTelemetryConfigurator.java       |  13 +-
 .../src/java/org/apache/solr/core/SolrCore.java    |  14 +-
 .../src/java/org/apache/solr/core/ZkContainer.java |   5 +-
 .../org/apache/solr/handler/ExportHandler.java     |   7 +-
 .../apache/solr/handler/ReplicationHandler.java    |   8 +-
 .../apache/solr/handler/RequestHandlerBase.java    |  70 +++-
 .../solr/handler/admin/CoreAdminHandler.java       |   7 +-
 .../apache/solr/handler/admin/MetricsHandler.java  |  59 +--
 .../handler/component/HttpShardHandlerFactory.java |   7 +-
 .../solr/handler/component/SearchComponent.java    |   5 +-
 .../solr/handler/component/SearchHandler.java      |  31 +-
 .../solr/handler/component/SuggestComponent.java   |   7 +-
 .../solr/highlight/HighlightingPluginBase.java     |   5 +-
 .../apache/solr/jersey/RequestMetricHandling.java  |  15 +-
 .../apache/solr/metrics/SolrCoreMetricManager.java |  22 +-
 .../org/apache/solr/metrics/SolrMetricManager.java | 220 +++++++++-
 .../apache/solr/metrics/SolrMetricProducer.java    |  20 +-
 .../apache/solr/metrics/SolrMetricsContext.java    |  98 ++++-
 .../instruments/AttributedDoubleCounter.java}      |  26 +-
 .../instruments/AttributedDoubleGauge.java}        |  21 +-
 .../instruments/AttributedDoubleHistogram.java}    |  22 +-
 .../AttributedDoubleUpDownCounter.java}            |  30 +-
 .../instruments/AttributedLongCounter.java}        |  26 +-
 .../instruments/AttributedLongGauge.java}          |  22 +-
 .../instruments/AttributedLongHistogram.java}      |  23 +-
 .../otel/instruments/AttributedLongTimer.java      |  55 +++
 .../instruments/AttributedLongUpDownCounter.java}  |  30 +-
 .../jetty => otel/instruments}/package-info.java   |   7 +-
 .../{prometheus/jvm => otel}/package-info.java     |   6 +-
 .../apache/solr/metrics/prometheus/SolrMetric.java |  57 ---
 .../core/PrometheusCoreFormatterInfo.java          |  33 --
 .../prometheus/core/SolrCoreCacheMetric.java       |  51 ---
 .../prometheus/core/SolrCoreHighlighterMetric.java |  49 ---
 .../prometheus/core/SolrCoreIndexMetric.java       |  47 ---
 .../metrics/prometheus/core/SolrCoreMetric.java    |  56 ---
 .../prometheus/core/SolrCoreTlogMetric.java        |  51 ---
 .../core/SolrPrometheusCoreFormatter.java          |  70 ----
 .../solr/metrics/prometheus/core/package-info.java |  22 -
 .../jetty/SolrJettyDispatchesMetric.java           |  46 ---
 .../prometheus/jetty/SolrJettyReqRespMetric.java   |  65 ---
 .../jetty/SolrPrometheusJettyFormatter.java        |  49 ---
 .../prometheus/jvm/PrometheusJvmFormatterInfo.java |  30 --
 .../prometheus/jvm/SolrJvmBuffersMetric.java       |  56 ---
 .../metrics/prometheus/jvm/SolrJvmGcMetrics.java   |  54 ---
 .../prometheus/jvm/SolrJvmMemoryMetric.java        |  65 ---
 .../metrics/prometheus/jvm/SolrJvmOsMetric.java    |  58 ---
 .../prometheus/jvm/SolrPrometheusJvmFormatter.java |  61 ---
 .../node/PrometheusNodeFormatterInfo.java          |  27 --
 .../prometheus/node/SolrNodeContainerMetric.java   |  57 ---
 .../node/SolrPrometheusNodeFormatter.java          | 104 -----
 .../solr/metrics/prometheus/node/package-info.java |  22 -
 .../solr/metrics/prometheus/package-info.java      |  25 --
 .../solr/response/PrometheusResponseWriter.java    |  99 +----
 .../java/org/apache/solr/search/CaffeineCache.java |   5 +-
 .../java/org/apache/solr/search/QParserPlugin.java |   4 +-
 .../org/apache/solr/search/SolrFieldCacheBean.java |   5 +-
 .../org/apache/solr/search/SolrIndexSearcher.java  |  13 +-
 .../apache/solr/security/AuditLoggerPlugin.java    |   5 +-
 .../apache/solr/security/AuthenticationPlugin.java |   5 +-
 .../org/apache/solr/security/MultiAuthPlugin.java  |   8 +-
 .../solr/security/MultiDestinationAuditLogger.java |   9 +-
 .../apache/solr/update/DirectUpdateHandler2.java   |   5 +-
 .../src/java/org/apache/solr/update/PeerSync.java  |   5 +-
 .../org/apache/solr/update/PeerSyncWithLeader.java |   5 +-
 .../src/java/org/apache/solr/update/UpdateLog.java |   5 +-
 .../org/apache/solr/update/UpdateShardHandler.java |   8 +-
 .../stats/InstrumentedHttpListenerFactory.java     |   5 +-
 .../stats/InstrumentedHttpRequestExecutor.java     | 161 ++++++++
 ...rumentedPoolingHttpClientConnectionManager.java |  78 ++++
 .../org/apache/solr/util/stats/MetricUtils.java    |  19 +
 .../src/test/org/apache/solr/SolrInfoBeanTest.java |   4 +-
 .../apache/solr/blockcache/BufferStoreTest.java    |   4 +-
 .../solr/handler/RequestHandlerBaseTest.java       |  69 +++-
 .../solr/handler/admin/MBeansHandlerTest.java      |   5 +-
 .../solr/handler/admin/MetricsHandlerTest.java     | 456 +--------------------
 .../apache/solr/metrics/SolrCoreMetricTest.java    |  82 ----
 .../apache/solr/metrics/SolrMetricTestUtils.java   |   4 +-
 .../response/TestPrometheusResponseWriter.java     |  52 +--
 .../org/apache/solr/search/TestCaffeineCache.java  |   7 +-
 .../org/apache/solr/search/TestSolrCachePerf.java  |   5 +-
 .../apache/solr/search/TestSolrFieldCacheBean.java |   5 +-
 .../test/org/apache/solr/search/TestThinCache.java |   7 +-
 .../src/test/org/apache/solr/search/ThinCache.java |   4 +-
 .../TestSimplePropagatorDistributedTracing.java    |   3 +
 .../handler/MirroringCollectionsHandlerTest.java   |   3 +
 .../CustomTestOtelTracerConfigurator.java          |   2 -
 .../src/java/org/apache/solr/SolrTestCaseJ4.java   |   2 +
 .../core/MockQuerySenderListenerReqHandler.java    |   6 +-
 91 files changed, 1178 insertions(+), 2072 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/blockcache/Metrics.java b/solr/core/src/java/org/apache/solr/blockcache/Metrics.java
index d7e13b9132f..dcdb4550d8c 100644
--- a/solr/core/src/java/org/apache/solr/blockcache/Metrics.java
+++ b/solr/core/src/java/org/apache/solr/blockcache/Metrics.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr.blockcache;
 
+import io.opentelemetry.api.common.Attributes;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicLong;
@@ -54,8 +55,10 @@ public class Metrics extends SolrCacheBase implements SolrInfoBean {
   private SolrMetricsContext solrMetricsContext;
   private long previous = System.nanoTime();
 
+  // TODO SOLR-17458: Migrate to Otel
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
     solrMetricsContext = parentContext.getChildContext(this);
     metricsMap =
         new MetricsMap(
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index 0c45e84dd6e..75e1d43d60b 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -31,6 +31,7 @@ import static org.apache.solr.security.AuthenticationPlugin.AUTHENTICATION_PLUGI
 
 import com.github.benmanes.caffeine.cache.Interner;
 import com.google.common.annotations.VisibleForTesting;
+import io.opentelemetry.api.common.Attributes;
 import io.opentelemetry.api.trace.Tracer;
 import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
 import jakarta.inject.Singleton;
@@ -516,7 +517,9 @@ public class CoreContainer {
               newVersion, getResourceLoader().newInstance(klas, AuditLoggerPlugin.class));
 
       newAuditloggerPlugin.plugin.init(auditConf);
-      newAuditloggerPlugin.plugin.initializeMetrics(solrMetricsContext, "/auditlogging");
+      // TODO SOLR-17458: Add Otel
+      newAuditloggerPlugin.plugin.initializeMetrics(
+          solrMetricsContext, Attributes.empty(), "/auditlogging");
     } else {
       log.debug("Security conf doesn't exist. Skipping setup for audit logging module.");
     }
@@ -581,7 +584,9 @@ public class CoreContainer {
     if (authenticationPlugin != null) {
       authenticationPlugin.plugin.init(authenticationConfig);
       setupHttpClientForAuthPlugin(authenticationPlugin.plugin);
-      authenticationPlugin.plugin.initializeMetrics(solrMetricsContext, "/authentication");
+      // TODO SOLR-17458: Add Otel
+      authenticationPlugin.plugin.initializeMetrics(
+          solrMetricsContext, Attributes.empty(), "/authentication");
     }
     this.authenticationPlugin = authenticationPlugin;
     try {
@@ -776,13 +781,16 @@ public class CoreContainer {
     shardHandlerFactory =
         ShardHandlerFactory.newInstance(cfg.getShardHandlerFactoryPluginInfo(), loader);
     if (shardHandlerFactory instanceof SolrMetricProducer metricProducer) {
-      metricProducer.initializeMetrics(solrMetricsContext, "httpShardHandler");
+      // TODO SOLR-17458: Add Otel
+      metricProducer.initializeMetrics(solrMetricsContext, Attributes.empty(), "httpShardHandler");
     }
 
     updateShardHandler = new UpdateShardHandler(cfg.getUpdateShardHandlerConfig());
     solrClientProvider =
         new HttpSolrClientProvider(cfg.getUpdateShardHandlerConfig(), solrMetricsContext);
-    updateShardHandler.initializeMetrics(solrMetricsContext, "updateShardHandler");
+    // TODO SOLR-17458: Add Otel
+    updateShardHandler.initializeMetrics(
+        solrMetricsContext, Attributes.empty(), "updateShardHandler");
     solrClientCache = new SolrClientCache(solrClientProvider.getSolrClient());
 
     Map<String, CacheConfig> cachesConfig = cfg.getCachesConfig();
@@ -793,7 +801,8 @@ public class CoreContainer {
       for (Map.Entry<String, CacheConfig> e : cachesConfig.entrySet()) {
         SolrCache<?, ?> c = e.getValue().newInstance();
         String cacheName = e.getKey();
-        c.initializeMetrics(solrMetricsContext, "nodeLevelCache/" + cacheName);
+        // TODO SOLR-17458: Add Otel
+        c.initializeMetrics(solrMetricsContext, Attributes.empty(), "nodeLevelCache/" + cacheName);
         m.put(cacheName, c);
       }
       this.caches = Collections.unmodifiableMap(m);
@@ -807,13 +816,18 @@ public class CoreContainer {
     if (isZooKeeperAware()) {
       solrClientCache.setDefaultZKHost(getZkController().getZkServerAddress());
       // initialize ZkClient metrics
-      zkSys.getZkMetricsProducer().initializeMetrics(solrMetricsContext, "zkClient");
+      // TODO SOLR-17458: Add Otel
+      zkSys
+          .getZkMetricsProducer()
+          .initializeMetrics(solrMetricsContext, Attributes.empty(), "zkClient");
       pkiAuthenticationSecurityBuilder =
           new PKIAuthenticationPlugin(
               this,
               zkSys.getZkController().getNodeName(),
               (PublicKeyHandler) containerHandlers.get(PublicKeyHandler.PATH));
-      pkiAuthenticationSecurityBuilder.initializeMetrics(solrMetricsContext, "/authentication/pki");
+      // TODO SOLR-17458: Add Otel
+      pkiAuthenticationSecurityBuilder.initializeMetrics(
+          solrMetricsContext, Attributes.empty(), "/authentication/pki");
 
       fileStore = new DistribFileStore(this);
       registerV2ApiIfEnabled(ClusterFileStore.class);
@@ -877,10 +891,12 @@ public class CoreContainer {
 
     metricsHandler = new MetricsHandler(this);
     containerHandlers.put(METRICS_PATH, metricsHandler);
-    metricsHandler.initializeMetrics(solrMetricsContext, METRICS_PATH);
+    // TODO SOLR-17458: Add Otel
+    metricsHandler.initializeMetrics(solrMetricsContext, Attributes.empty(), METRICS_PATH);
 
     containerHandlers.put(AUTHZ_PATH, securityConfHandler);
-    securityConfHandler.initializeMetrics(solrMetricsContext, AUTHZ_PATH);
+    // TODO SOLR-17458: Add Otel
+    securityConfHandler.initializeMetrics(solrMetricsContext, Attributes.empty(), AUTHZ_PATH);
     containerHandlers.put(AUTHC_PATH, securityConfHandler);
 
     PluginInfo[] metricReporters = cfg.getMetricsConfig().getMetricReporters();
@@ -1002,7 +1018,8 @@ public class CoreContainer {
         "version");
 
     SolrFieldCacheBean fieldCacheBean = new SolrFieldCacheBean();
-    fieldCacheBean.initializeMetrics(solrMetricsContext, null);
+    // TODO SOLR-17458: Otel migration
+    fieldCacheBean.initializeMetrics(solrMetricsContext, Attributes.empty(), "");
 
     if (isZooKeeperAware()) {
       metricManager.loadClusterReporters(metricReporters, this);
@@ -2328,7 +2345,9 @@ public class CoreContainer {
       containerHandlers.put(path, (SolrRequestHandler) handler);
     }
     if (handler instanceof SolrMetricProducer) {
-      ((SolrMetricProducer) handler).initializeMetrics(solrMetricsContext, path);
+      ((SolrMetricProducer) handler)
+          // TODO SOLR-17458: Add Otel
+          .initializeMetrics(solrMetricsContext, Attributes.empty(), path);
     }
     return handler;
   }
diff --git a/solr/core/src/java/org/apache/solr/core/HttpSolrClientProvider.java b/solr/core/src/java/org/apache/solr/core/HttpSolrClientProvider.java
index 2bf25a896f6..68207965e48 100644
--- a/solr/core/src/java/org/apache/solr/core/HttpSolrClientProvider.java
+++ b/solr/core/src/java/org/apache/solr/core/HttpSolrClientProvider.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr.core;
 
+import io.opentelemetry.api.common.Attributes;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 import org.apache.solr.client.solrj.impl.Http2SolrClient;
@@ -68,7 +69,8 @@ final class HttpSolrClientProvider implements AutoCloseable {
     var solrMetricsContext = parentContext.getChildContext(this);
     String expandedScope =
         SolrMetricManager.mkName(METRIC_SCOPE_NAME, SolrInfoBean.Category.HTTP.name());
-    trackHttpSolrMetrics.initializeMetrics(solrMetricsContext, expandedScope);
+    // TODO SOLR-17458: Add Otel
+    trackHttpSolrMetrics.initializeMetrics(solrMetricsContext, Attributes.empty(), expandedScope);
   }
 
   Http2SolrClient getSolrClient() {
diff --git a/solr/core/src/java/org/apache/solr/core/OpenTelemetryConfigurator.java b/solr/core/src/java/org/apache/solr/core/OpenTelemetryConfigurator.java
index 90170866dfe..47547b4950f 100644
--- a/solr/core/src/java/org/apache/solr/core/OpenTelemetryConfigurator.java
+++ b/solr/core/src/java/org/apache/solr/core/OpenTelemetryConfigurator.java
@@ -17,15 +17,16 @@
 
 package org.apache.solr.core;
 
+import com.google.common.annotations.VisibleForTesting;
 import io.opentelemetry.api.GlobalOpenTelemetry;
 import io.opentelemetry.api.trace.Tracer;
 import io.opentelemetry.context.Context;
 import io.opentelemetry.context.Scope;
 import io.opentelemetry.context.propagation.ContextPropagators;
-import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
 import io.opentelemetry.sdk.OpenTelemetrySdk;
 import io.opentelemetry.sdk.OpenTelemetrySdkBuilder;
 import io.opentelemetry.sdk.metrics.SdkMeterProvider;
+import io.opentelemetry.sdk.metrics.export.MetricReader;
 import io.opentelemetry.sdk.trace.SdkTracerProvider;
 import io.opentelemetry.sdk.trace.samplers.Sampler;
 import java.lang.invoke.MethodHandles;
@@ -61,7 +62,7 @@ public abstract class OpenTelemetryConfigurator implements NamedListInitializedP
    * SDK.
    */
   public static synchronized void initializeOpenTelemetrySdk(
-      NodeConfig cfg, SolrResourceLoader loader, PrometheusMetricReader prometheusMetricReader) {
+      NodeConfig cfg, SolrResourceLoader loader, MetricReader metricReader) {
     PluginInfo info = (cfg != null) ? cfg.getTracerConfiguratorPluginInfo() : null;
 
     if (info != null && info.isEnabled()) {
@@ -72,7 +73,7 @@ public abstract class OpenTelemetryConfigurator implements NamedListInitializedP
     } else {
       // Initializing sampler as always off to replicate no-op Tracer provider
       OpenTelemetryConfigurator.configureOpenTelemetrySdk(
-          SdkMeterProvider.builder().registerMetricReader(prometheusMetricReader).build(),
+          SdkMeterProvider.builder().registerMetricReader(metricReader).build(),
           SdkTracerProvider.builder().setSampler(Sampler.alwaysOff()).build());
     }
   }
@@ -185,4 +186,10 @@ public abstract class OpenTelemetryConfigurator implements NamedListInitializedP
   protected static String envNameToSyspropName(String envName) {
     return envName.toLowerCase(Locale.ROOT).replace("_", ".");
   }
+
+  @VisibleForTesting
+  public static void resetForTest() {
+    loaded = false;
+    GlobalOpenTelemetry.resetForTest();
+  }
 }
diff --git a/solr/core/src/java/org/apache/solr/core/SolrCore.java b/solr/core/src/java/org/apache/solr/core/SolrCore.java
index dbfe8f7ee0a..34d0fbdc4a3 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrCore.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java
@@ -23,6 +23,7 @@ import com.codahale.metrics.Counter;
 import com.codahale.metrics.Timer;
 import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.Caffeine;
+import io.opentelemetry.api.common.Attributes;
 import java.io.Closeable;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -1104,7 +1105,8 @@ public class SolrCore implements SolrInfoBean, Closeable {
       setLatestSchema(schema);
 
       // initialize core metrics
-      initializeMetrics(solrMetricsContext, null);
+      // TODO SOLR-17458: Add Otel
+      initializeMetrics(solrMetricsContext, Attributes.empty(), "");
 
       // init pluggable circuit breakers, after metrics because some circuit breakers use metrics
       initPlugins(null, CircuitBreaker.class);
@@ -1112,7 +1114,8 @@ public class SolrCore implements SolrInfoBean, Closeable {
       SolrFieldCacheBean solrFieldCacheBean = new SolrFieldCacheBean();
       // this is registered at the CONTAINER level because it's not core-specific - for now we
       // also register it here for back-compat
-      solrFieldCacheBean.initializeMetrics(solrMetricsContext, "core");
+      // TODO SOLR-17458: Add Otel
+      solrFieldCacheBean.initializeMetrics(solrMetricsContext, Attributes.empty(), "core");
       infoRegistry.put("fieldCache", solrFieldCacheBean);
 
       this.maxWarmingSearchers = solrConfig.maxWarmingSearchers;
@@ -1189,7 +1192,8 @@ public class SolrCore implements SolrInfoBean, Closeable {
       // Allow the directory factory to report metrics
       if (directoryFactory instanceof SolrMetricProducer) {
         ((SolrMetricProducer) directoryFactory)
-            .initializeMetrics(solrMetricsContext, "directoryFactory");
+            // TODO SOLR-17458: Add Otel
+            .initializeMetrics(solrMetricsContext, Attributes.empty(), "directoryFactory");
       }
 
       bufferUpdatesIfConstructing(coreDescriptor);
@@ -1301,8 +1305,10 @@ public class SolrCore implements SolrInfoBean, Closeable {
     return coreMetricManager;
   }
 
+  // TODO SOLR-17458: Migrate to Otel
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
     newSearcherCounter = parentContext.counter("new", Category.SEARCHER.toString());
     newSearcherTimer = parentContext.timer("time", Category.SEARCHER.toString(), "new");
     newSearcherWarmupTimer = parentContext.timer("warmup", Category.SEARCHER.toString(), "new");
diff --git a/solr/core/src/java/org/apache/solr/core/ZkContainer.java b/solr/core/src/java/org/apache/solr/core/ZkContainer.java
index b5fdc3c5718..c6a1b79d283 100644
--- a/solr/core/src/java/org/apache/solr/core/ZkContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/ZkContainer.java
@@ -19,6 +19,7 @@ package org.apache.solr.core;
 import static org.apache.solr.common.cloud.ZkStateReader.HTTPS;
 import static org.apache.solr.common.cloud.ZkStateReader.HTTPS_PORT_PROP;
 
+import io.opentelemetry.api.common.Attributes;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.nio.file.Path;
@@ -145,8 +146,10 @@ public class ZkContainer {
             new SolrMetricProducer() {
               SolrMetricsContext ctx;
 
+              // TODO SOLR-17458: Migrate to Otel
               @Override
-              public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
+              public void initializeMetrics(
+                  SolrMetricsContext parentContext, Attributes attributes, String scope) {
                 ctx = parentContext.getChildContext(this);
                 ctx.gauge(
                     metricsMap, true, scope, null, SolrInfoBean.Category.CONTAINER.toString());
diff --git a/solr/core/src/java/org/apache/solr/handler/ExportHandler.java b/solr/core/src/java/org/apache/solr/handler/ExportHandler.java
index 8fa1ba64e38..6a83eb78783 100644
--- a/solr/core/src/java/org/apache/solr/handler/ExportHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/ExportHandler.java
@@ -19,6 +19,7 @@ package org.apache.solr.handler;
 
 import static org.apache.solr.common.params.CommonParams.JSON;
 
+import io.opentelemetry.api.common.Attributes;
 import java.lang.invoke.MethodHandles;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -84,9 +85,11 @@ public class ExportHandler extends SearchHandler {
     }
   }
 
+  // TODO SOLR-17458: Migrate to Otel
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
-    super.initializeMetrics(parentContext, scope);
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
+    super.initializeMetrics(parentContext, attributes, scope);
     this.writerMetricsPath = SolrMetricManager.mkName("writer", getCategory().toString(), scope);
   }
 
diff --git a/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java b/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
index 8367e9166f9..d3aa17b7b12 100644
--- a/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
@@ -30,6 +30,7 @@ import static org.apache.solr.handler.admin.api.ReplicationAPIBase.OFFSET;
 import static org.apache.solr.handler.admin.api.ReplicationAPIBase.STATUS;
 import static org.apache.solr.handler.admin.api.ReplicationAPIBase.TLOG_FILE;
 
+import io.opentelemetry.api.common.Attributes;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
@@ -836,10 +837,11 @@ public class ReplicationHandler extends RequestHandlerBase
     }
   }
 
-  // TODO: Handle compatibility in 8.x
+  // NOCOMMIT SOLR-17458: Update with OTEL
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
-    super.initializeMetrics(parentContext, scope);
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
+    super.initializeMetrics(parentContext, attributes, scope);
     solrMetricsContext.gauge(
         () ->
             (core != null && !core.isClosed()
diff --git a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
index bc5331ef556..e633a91113d 100644
--- a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
+++ b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
@@ -23,6 +23,8 @@ import com.codahale.metrics.Counter;
 import com.codahale.metrics.Meter;
 import com.codahale.metrics.Timer;
 import java.io.IOException;
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.Attributes;
 import java.lang.invoke.MethodHandles;
 import java.util.Collection;
 import java.util.Collections;
@@ -45,6 +47,8 @@ import org.apache.solr.metrics.SolrDelegateRegistryMetricsContext;
 import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.metrics.SolrMetricProducer;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.instruments.AttributedLongCounter;
+import org.apache.solr.metrics.otel.instruments.AttributedLongTimer;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestHandler;
 import org.apache.solr.response.SolrQueryResponse;
@@ -162,7 +166,8 @@ public abstract class RequestHandlerBase
   }
 
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
     if (aggregateNodeLevelMetricsEnabled) {
       this.solrMetricsContext =
           new SolrDelegateRegistryMetricsContext(
@@ -173,7 +178,10 @@ public abstract class RequestHandlerBase
     } else {
       this.solrMetricsContext = parentContext.getChildContext(this);
     }
-    metrics = new HandlerMetrics(solrMetricsContext, getCategory().toString(), scope);
+
+    metrics = new HandlerMetrics(solrMetricsContext, attributes, getCategory().toString(), scope);
+
+    // NOCOMMIT: I don't see value in this metric
     solrMetricsContext.gauge(
         () -> handlerStart, true, "handlerStart", getCategory().toString(), scope);
   }
@@ -186,7 +194,8 @@ public abstract class RequestHandlerBase
                 new SolrMetricManager(
                     null, new MetricsConfig.MetricsConfigBuilder().setEnabled(false).build(), null),
                 "NO_OP",
-                "NO_OP"));
+                "NO_OP"),
+            Attributes.empty());
 
     public final Meter numErrors;
     public final Meter numServerErrors;
@@ -196,7 +205,16 @@ public abstract class RequestHandlerBase
     public final Timer requestTimes;
     public final Counter totalTime;
 
-    public HandlerMetrics(SolrMetricsContext solrMetricsContext, String... metricPath) {
+    public AttributedLongCounter otelRequests;
+    public AttributedLongCounter otelNumServerErrors;
+    public AttributedLongCounter otelNumClientErrors;
+    public AttributedLongCounter otelNumTimeouts;
+    public AttributedLongTimer otelRequestTimes;
+
+    public HandlerMetrics(
+        SolrMetricsContext solrMetricsContext, Attributes attributes, String... metricPath) {
+
+      // NOCOMMIT SOLR-17458: To be removed
       numErrors = solrMetricsContext.meter("errors", metricPath);
       numServerErrors = solrMetricsContext.meter("serverErrors", metricPath);
       numClientErrors = solrMetricsContext.meter("clientErrors", metricPath);
@@ -204,6 +222,43 @@ public abstract class RequestHandlerBase
       requests = solrMetricsContext.counter("requests", metricPath);
       requestTimes = solrMetricsContext.timer("requestTimes", metricPath);
       totalTime = solrMetricsContext.counter("totalTime", metricPath);
+
+      var baseRequestMetric =
+          solrMetricsContext.longCounter("solr_metrics_core_requests", "HTTP Solr request counts");
+
+      var baseRequestTimeMetric =
+          solrMetricsContext.longHistogram(
+              "solr_metrics_core_requests_times", "HTTP Solr request times", "ms");
+
+      otelRequests =
+          new AttributedLongCounter(
+              baseRequestMetric,
+              Attributes.builder().putAll(attributes).put(TYPE_ATTR, "requests").build());
+
+      otelNumServerErrors =
+          new AttributedLongCounter(
+              baseRequestMetric,
+              Attributes.builder()
+                  .putAll(attributes)
+                  .put(AttributeKey.stringKey("source"), "server")
+                  .put(TYPE_ATTR, "errors")
+                  .build());
+
+      otelNumClientErrors =
+          new AttributedLongCounter(
+              baseRequestMetric,
+              Attributes.builder()
+                  .putAll(attributes)
+                  .put(AttributeKey.stringKey("source"), "client")
+                  .put(TYPE_ATTR, "errors")
+                  .build());
+
+      otelNumTimeouts =
+          new AttributedLongCounter(
+              baseRequestMetric,
+              Attributes.builder().putAll(attributes).put(TYPE_ATTR, "timeouts").build());
+
+      otelRequestTimes = new AttributedLongTimer(baseRequestTimeMetric, attributes);
     }
   }
 
@@ -227,10 +282,13 @@ public abstract class RequestHandlerBase
     if (publishCpuTime) {
       ThreadCpuTimer.beginContext(REQUEST_CPU_TIMER_CONTEXT);
     }
+
     HandlerMetrics metrics = getMetricsForThisRequest(req);
     metrics.requests.inc();
+    metrics.otelRequests.inc();
 
     Timer.Context timer = metrics.requestTimes.time();
+    AttributedLongTimer.MetricTimer otelTimer = metrics.otelRequestTimes.start();
     try {
       TestInjection.injectLeaderTragedy(req.getCore());
       if (pluginInfo != null && pluginInfo.attributes.containsKey(USEPARAM))
@@ -243,6 +301,7 @@ public abstract class RequestHandlerBase
 
       if (!haveCompleteResults(rsp.getResponseHeader())) {
         metrics.numTimeouts.mark();
+        metrics.otelNumTimeouts.inc();
         rsp.setHttpCaching(false);
       }
     } catch (QueryLimitsExceededException e) {
@@ -255,6 +314,7 @@ public abstract class RequestHandlerBase
       try {
         long elapsed = timer.stop();
         metrics.totalTime.inc(elapsed);
+        otelTimer.stop();
 
         if (publishCpuTime) {
           Optional<Long> cpuTime = ThreadCpuTimer.readMSandReset(REQUEST_CPU_TIMER_CONTEXT);
@@ -300,9 +360,11 @@ public abstract class RequestHandlerBase
     if (isClientError) {
       log.error("Client exception", e);
       metrics.numClientErrors.mark();
+      metrics.otelNumClientErrors.inc();
     } else {
       log.error("Server exception", e);
       metrics.numServerErrors.mark();
+      metrics.otelNumServerErrors.inc();
     }
   }
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java
index 86c68759774..b0237978593 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java
@@ -25,6 +25,7 @@ import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.Caffeine;
 import com.github.benmanes.caffeine.cache.Expiry;
 import com.github.benmanes.caffeine.cache.Ticker;
+import io.opentelemetry.api.common.Attributes;
 import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -127,9 +128,11 @@ public class CoreAdminHandler extends RequestHandlerBase implements PermissionNa
             + "it is a special Handler configured directly by the RequestDispatcher");
   }
 
+  // TODO SOLR-17458: Migrate to Otel
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
-    super.initializeMetrics(parentContext, scope);
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
+    super.initializeMetrics(parentContext, attributes, scope);
     coreAdminAsyncTracker.standardExecutor =
         MetricUtils.instrumentedExecutorService(
             coreAdminAsyncTracker.standardExecutor,
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java
index 4bba3414be9..f2b501b8a41 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java
@@ -51,7 +51,6 @@ import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestInfo;
-import org.apache.solr.response.PrometheusResponseWriter;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.security.AuthorizationContext;
 import org.apache.solr.security.PermissionNameProvider;
@@ -127,11 +126,12 @@ public class MetricsHandler extends RequestHandlerBase implements PermissionName
       return;
     }
 
+    // TODO SOLR-17458: Make this the default option after dropwizard removal
     if (PROMETHEUS_METRICS_WT.equals(params.get(CommonParams.WT))) {
-      response = handlePrometheusExport(params);
-      consumer.accept("metrics", response);
+      consumer.accept("metrics", metricManager.getMetricReader().collect());
       return;
     }
+
     String[] keys = params.getParams(KEY_PARAM);
     if (keys != null && keys.length > 0) {
       handleKeyRequest(keys, consumer);
@@ -179,59 +179,6 @@ public class MetricsHandler extends RequestHandlerBase implements PermissionName
     return response;
   }
 
-  private NamedList<Object> handlePrometheusExport(SolrParams params) {
-    NamedList<Object> response = new SimpleOrderedMap<>();
-    MetricFilter mustMatchFilter = parseMustMatchFilter(params);
-    Predicate<CharSequence> propertyFilter = parsePropertyFilter(params);
-    List<MetricType> metricTypes = parseMetricTypes(params);
-    List<MetricFilter> metricFilters =
-        metricTypes.stream().map(MetricType::asMetricFilter).collect(Collectors.toList());
-
-    Set<String> requestedRegistries = parseRegistries(params);
-    MetricRegistry mergedCoreRegistries = new MetricRegistry();
-
-    for (String registryName : requestedRegistries) {
-      MetricRegistry dropwizardRegistry = metricManager.registry(registryName);
-
-      // Merge all core registries into a single registry and
-      // append the core name to the metric to avoid duplicate metrics name
-      if (registryName.startsWith("solr.core")) {
-        mergedCoreRegistries.registerAll(getCoreNameFromRegistry(registryName), dropwizardRegistry);
-        continue;
-      }
-
-      PrometheusResponseWriter.toPrometheus(
-          dropwizardRegistry,
-          registryName,
-          metricFilters,
-          mustMatchFilter,
-          propertyFilter,
-          false,
-          false,
-          true,
-          (registry) -> {
-            response.add(registryName, registry);
-          });
-    }
-
-    if (!mergedCoreRegistries.getMetrics().isEmpty()) {
-      PrometheusResponseWriter.toPrometheus(
-          mergedCoreRegistries,
-          "solr.core",
-          metricFilters,
-          mustMatchFilter,
-          propertyFilter,
-          false,
-          false,
-          true,
-          (registry) -> {
-            response.add("solr.core", registry);
-          });
-    }
-
-    return response;
-  }
-
   private static class MetricsExpr {
     Pattern registryRegex;
     MetricFilter metricFilter;
diff --git a/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java b/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java
index abb5f31b398..dd3630ea203 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java
@@ -18,6 +18,7 @@ package org.apache.solr.handler.component;
 
 import static org.apache.solr.util.stats.InstrumentedHttpListenerFactory.KNOWN_METRIC_NAME_STRATEGIES;
 
+import io.opentelemetry.api.common.Attributes;
 import java.lang.invoke.MethodHandles;
 import java.util.Iterator;
 import java.util.List;
@@ -439,10 +440,12 @@ public class HttpShardHandlerFactory extends ShardHandlerFactory
   }
 
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
     solrMetricsContext = parentContext.getChildContext(this);
     String expandedScope = SolrMetricManager.mkName(scope, SolrInfoBean.Category.QUERY.name());
-    httpListenerFactory.initializeMetrics(solrMetricsContext, expandedScope);
+    // TODO SOLR-17458: Add Otel
+    httpListenerFactory.initializeMetrics(solrMetricsContext, Attributes.empty(), expandedScope);
     commExecutor =
         MetricUtils.instrumentedExecutorService(
             commExecutor,
diff --git a/solr/core/src/java/org/apache/solr/handler/component/SearchComponent.java b/solr/core/src/java/org/apache/solr/handler/component/SearchComponent.java
index 913078c27ee..de041a7220e 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/SearchComponent.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/SearchComponent.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr.handler.component;
 
+import io.opentelemetry.api.common.Attributes;
 import java.io.IOException;
 import java.util.Map;
 import org.apache.solr.core.SolrInfoBean;
@@ -103,8 +104,10 @@ public abstract class SearchComponent implements SolrInfoBean, NamedListInitiali
     return solrMetricsContext;
   }
 
+  // TODO SOLR-17458: Update with Otel
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
     // By default don't register any metrics - but prepare a child context
     this.solrMetricsContext = parentContext.getChildContext(this);
   }
diff --git a/solr/core/src/java/org/apache/solr/handler/component/SearchHandler.java b/solr/core/src/java/org/apache/solr/handler/component/SearchHandler.java
index ed2e71e632a..fdbf1a37f3b 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/SearchHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/SearchHandler.java
@@ -28,6 +28,8 @@ import static org.apache.solr.request.SolrRequestInfo.getQueryLimits;
 import static org.apache.solr.response.SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_DETAILS_KEY;
 
 import com.codahale.metrics.Counter;
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.Attributes;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
@@ -64,7 +66,6 @@ import org.apache.solr.core.PluginInfo;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.logging.MDCLoggingContext;
-import org.apache.solr.metrics.MetricsMap;
 import org.apache.solr.metrics.SolrMetricsContext;
 import org.apache.solr.pkg.PackageAPI;
 import org.apache.solr.pkg.PackageListeners;
@@ -154,18 +155,28 @@ public class SearchHandler extends RequestHandlerBase
     }
   }
 
+  // TODO SOLR-17458: Fix metric Attributes
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
-    super.initializeMetrics(parentContext, scope);
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
+    super.initializeMetrics(
+        parentContext,
+        Attributes.builder()
+            .putAll(attributes)
+            .put(AttributeKey.stringKey("category"), getCategory().toString())
+            .put(AttributeKey.stringKey("internal"), "false")
+            .build(),
+        scope);
     metricsShard =
         new HandlerMetrics( // will register various metrics in the context
-            solrMetricsContext, getCategory().toString(), scope + SHARD_HANDLER_SUFFIX);
-    solrMetricsContext.gauge(
-        new MetricsMap(map -> shardPurposes.forEach((k, v) -> map.putNoEx(k, v.getCount()))),
-        true,
-        "purposes",
-        getCategory().toString(),
-        scope + SHARD_HANDLER_SUFFIX);
+            solrMetricsContext,
+            Attributes.builder()
+                .putAll(attributes)
+                .put(AttributeKey.stringKey("category"), getCategory().toString())
+                .put(AttributeKey.stringKey("internal"), "true")
+                .build(),
+            getCategory().toString(),
+            scope + SHARD_HANDLER_SUFFIX);
   }
 
   @Override
diff --git a/solr/core/src/java/org/apache/solr/handler/component/SuggestComponent.java b/solr/core/src/java/org/apache/solr/handler/component/SuggestComponent.java
index 60b4871c060..f32934451ac 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/SuggestComponent.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/SuggestComponent.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr.handler.component;
 
+import io.opentelemetry.api.common.Attributes;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.nio.file.Files;
@@ -381,9 +382,11 @@ public class SuggestComponent extends SearchComponent
     return "Suggester component";
   }
 
+  // TODO SOLR-17458: Migrate to OTEL
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
-    super.initializeMetrics(parentContext, scope);
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
+    super.initializeMetrics(parentContext, attributes, scope);
 
     this.solrMetricsContext.gauge(
         () -> ramBytesUsed(), true, "totalSizeInBytes", getCategory().toString());
diff --git a/solr/core/src/java/org/apache/solr/highlight/HighlightingPluginBase.java b/solr/core/src/java/org/apache/solr/highlight/HighlightingPluginBase.java
index 7028c8c8e8e..c0e45ef582f 100644
--- a/solr/core/src/java/org/apache/solr/highlight/HighlightingPluginBase.java
+++ b/solr/core/src/java/org/apache/solr/highlight/HighlightingPluginBase.java
@@ -17,6 +17,7 @@
 package org.apache.solr.highlight;
 
 import com.codahale.metrics.Counter;
+import io.opentelemetry.api.common.Attributes;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import org.apache.solr.common.params.SolrParams;
@@ -62,8 +63,10 @@ public abstract class HighlightingPluginBase implements SolrInfoBean {
     return solrMetricsContext;
   }
 
+  // TODO SOLR-17458: Migrate to OTEL
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
     solrMetricsContext = parentContext.getChildContext(this);
     numRequests = solrMetricsContext.counter("requests", getCategory().toString(), scope);
   }
diff --git a/solr/core/src/java/org/apache/solr/jersey/RequestMetricHandling.java b/solr/core/src/java/org/apache/solr/jersey/RequestMetricHandling.java
index 830c78d38b0..6cea50d03d2 100644
--- a/solr/core/src/java/org/apache/solr/jersey/RequestMetricHandling.java
+++ b/solr/core/src/java/org/apache/solr/jersey/RequestMetricHandling.java
@@ -33,6 +33,7 @@ import java.lang.invoke.MethodHandles;
 import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.core.PluginBag;
 import org.apache.solr.handler.RequestHandlerBase;
+import org.apache.solr.metrics.otel.instruments.AttributedLongTimer;
 import org.apache.solr.request.SolrQueryRequest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -89,9 +90,12 @@ public class RequestMetricHandling {
       final RequestHandlerBase.HandlerMetrics metrics =
           handlerBase.getMetricsForThisRequest(solrQueryRequest);
 
-      requestContext.setProperty(HANDLER_METRICS, metrics);
-      requestContext.setProperty(TIMER, metrics.requestTimes.time());
+      // TODO SOLR-17458: Recording both Otel and dropwizard for now. Will remove Dropwizard after
+      // removal
       metrics.requests.inc();
+      requestContext.setProperty(TIMER, metrics.otelRequestTimes.start());
+      requestContext.setProperty("dropwizardTimer", metrics.requestTimes.time());
+      requestContext.setProperty(HANDLER_METRICS, metrics);
     }
   }
 
@@ -117,13 +121,16 @@ public class RequestMetricHandling {
         final SolrJerseyResponse response = (SolrJerseyResponse) responseContext.getEntity();
         if (Boolean.TRUE.equals(response.responseHeader.partialResults)) {
           metrics.numTimeouts.mark();
+          metrics.otelNumTimeouts.inc();
         }
       } else {
         log.debug("Skipping partialResults check because entity was not SolrJerseyResponse");
       }
+      final var dropwizardTimer = (Timer.Context) requestContext.getProperty("dropwizardTimer");
+      metrics.totalTime.inc(dropwizardTimer.stop());
 
-      final Timer.Context timer = (Timer.Context) requestContext.getProperty(TIMER);
-      metrics.totalTime.inc(timer.stop());
+      final var timer = (AttributedLongTimer.MetricTimer) requestContext.getProperty(TIMER);
+      timer.stop();
     }
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
index a1c5f92fd5f..62a342180f2 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
@@ -17,6 +17,8 @@
 package org.apache.solr.metrics;
 
 import com.codahale.metrics.MetricRegistry;
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.Attributes;
 import java.io.Closeable;
 import java.io.IOException;
 import org.apache.solr.cloud.CloudDescriptor;
@@ -33,6 +35,13 @@ import org.apache.solr.core.SolrInfoBean;
  */
 public class SolrCoreMetricManager implements Closeable {
 
+  public static final AttributeKey<String> COLLECTION_ATTR = AttributeKey.stringKey("collection");
+  public static final AttributeKey<String> CORE_ATTR = AttributeKey.stringKey("core");
+  public static final AttributeKey<String> SHARD_ATTR = AttributeKey.stringKey("shard");
+  public static final AttributeKey<String> REPLICA_ATTR = AttributeKey.stringKey("replica");
+  public static final AttributeKey<String> HANDLER_ATTR = AttributeKey.stringKey("handler");
+  public static final AttributeKey<String> SCOPE_ATTR = AttributeKey.stringKey("scope");
+
   private final SolrCore core;
   private SolrMetricsContext solrMetricsContext;
   private SolrMetricManager metricManager;
@@ -135,8 +144,19 @@ public class SolrCoreMetricManager implements Closeable {
               + ", producer = "
               + producer);
     }
+
+    // NOCOMMIT SOLR-17458: These attributes may not work for standalone mode
     // use deprecated method for back-compat, remove in 9.0
-    producer.initializeMetrics(solrMetricsContext, scope);
+    producer.initializeMetrics(
+        solrMetricsContext,
+        Attributes.builder()
+            .put(CORE_ATTR, core.getCoreDescriptor().getName())
+            .put(COLLECTION_ATTR, collectionName)
+            .put(SHARD_ATTR, shardName)
+            .put(REPLICA_ATTR, replicaName)
+            .put((scope.startsWith("/")) ? HANDLER_ATTR : SCOPE_ATTR, scope)
+            .build(),
+        scope);
   }
 
   /** Return the registry used by this SolrCore. */
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
index b8700d3a412..bcefc0b3ddc 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
@@ -26,7 +26,30 @@ import com.codahale.metrics.MetricRegistry;
 import com.codahale.metrics.MetricSet;
 import com.codahale.metrics.SharedMetricRegistries;
 import com.codahale.metrics.Timer;
+import io.opentelemetry.api.metrics.DoubleCounter;
+import io.opentelemetry.api.metrics.DoubleCounterBuilder;
+import io.opentelemetry.api.metrics.DoubleGauge;
+import io.opentelemetry.api.metrics.DoubleGaugeBuilder;
+import io.opentelemetry.api.metrics.DoubleHistogram;
+import io.opentelemetry.api.metrics.DoubleHistogramBuilder;
+import io.opentelemetry.api.metrics.DoubleUpDownCounter;
+import io.opentelemetry.api.metrics.DoubleUpDownCounterBuilder;
+import io.opentelemetry.api.metrics.LongCounter;
+import io.opentelemetry.api.metrics.LongCounterBuilder;
+import io.opentelemetry.api.metrics.LongGauge;
+import io.opentelemetry.api.metrics.LongGaugeBuilder;
+import io.opentelemetry.api.metrics.LongHistogram;
+import io.opentelemetry.api.metrics.LongHistogramBuilder;
+import io.opentelemetry.api.metrics.LongUpDownCounter;
+import io.opentelemetry.api.metrics.LongUpDownCounterBuilder;
 import io.opentelemetry.api.metrics.MeterProvider;
+import io.opentelemetry.api.metrics.ObservableDoubleCounter;
+import io.opentelemetry.api.metrics.ObservableDoubleMeasurement;
+import io.opentelemetry.api.metrics.ObservableDoubleUpDownCounter;
+import io.opentelemetry.api.metrics.ObservableLongCounter;
+import io.opentelemetry.api.metrics.ObservableLongGauge;
+import io.opentelemetry.api.metrics.ObservableLongMeasurement;
+import io.opentelemetry.api.metrics.ObservableLongUpDownCounter;
 import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
@@ -46,6 +69,7 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Consumer;
 import java.util.regex.Pattern;
 import java.util.regex.PatternSyntaxException;
 import java.util.stream.Collectors;
@@ -124,7 +148,8 @@ public class SolrMetricManager {
   private final MetricRegistry.MetricSupplier<Histogram> histogramSupplier;
 
   private final MeterProvider meterProvider;
-  private final PrometheusMetricReader prometheusMetricReader;
+  private final Map<String, io.opentelemetry.api.metrics.Meter> meters = new ConcurrentHashMap<>();
+  private final PrometheusMetricReader metricReader;
 
   public SolrMetricManager() {
     metricsConfig = new MetricsConfig.MetricsConfigBuilder().build();
@@ -133,15 +158,13 @@ public class SolrMetricManager {
     timerSupplier = MetricSuppliers.timerSupplier(null, null);
     histogramSupplier = MetricSuppliers.histogramSupplier(null, null);
     meterProvider = MetricUtils.getMeterProvider();
-    prometheusMetricReader = null;
+    metricReader = null;
   }
 
   public SolrMetricManager(
-      SolrResourceLoader loader,
-      MetricsConfig metricsConfig,
-      PrometheusMetricReader prometheusMetricReader) {
+      SolrResourceLoader loader, MetricsConfig metricsConfig, PrometheusMetricReader metricReader) {
     this.metricsConfig = metricsConfig;
-    this.prometheusMetricReader = prometheusMetricReader;
+    this.metricReader = metricReader;
     this.meterProvider = MetricUtils.getMeterProvider();
     counterSupplier = MetricSuppliers.counterSupplier(loader, metricsConfig.getCounterSupplier());
     meterSupplier = MetricSuppliers.meterSupplier(loader, metricsConfig.getMeterSupplier());
@@ -150,6 +173,167 @@ public class SolrMetricManager {
         MetricSuppliers.histogramSupplier(loader, metricsConfig.getHistogramSupplier());
   }
 
+  public LongCounter longCounter(
+      String registry, String counterName, String description, String unit) {
+    LongCounterBuilder builder =
+        meterProvider.get(registry).counterBuilder(counterName).setDescription(description);
+    if (unit != null) builder.setUnit(unit);
+
+    return builder.build();
+  }
+
+  public LongUpDownCounter longUpDownCounter(
+      String registry, String counterName, String description, String unit) {
+    LongUpDownCounterBuilder builder =
+        meterProvider.get(registry).upDownCounterBuilder(counterName).setDescription(description);
+    if (unit != null) builder.setUnit(unit);
+
+    return builder.build();
+  }
+
+  public DoubleUpDownCounter doubleUpDownCounter(
+      String registry, String counterName, String description, String unit) {
+    DoubleUpDownCounterBuilder builder =
+        meterProvider
+            .get(registry)
+            .upDownCounterBuilder(counterName)
+            .setDescription(description)
+            .ofDoubles();
+    if (unit != null) builder.setUnit(unit);
+
+    return builder.build();
+  }
+
+  public DoubleCounter doubleCounter(
+      String registry, String counterName, String description, String unit) {
+    DoubleCounterBuilder builder =
+        meterProvider
+            .get(registry)
+            .counterBuilder(counterName)
+            .setDescription(description)
+            .ofDoubles();
+    if (unit != null) builder.setUnit(unit);
+
+    return builder.build();
+  }
+
+  public DoubleHistogram doubleHistogram(
+      String registry, String histogramName, String description, String unit) {
+    DoubleHistogramBuilder builder =
+        meterProvider.get(registry).histogramBuilder(histogramName).setDescription(description);
+    if (unit != null) builder.setUnit(unit);
+
+    return builder.build();
+  }
+
+  public LongHistogram longHistogram(
+      String registry, String histogramName, String description, String unit) {
+    LongHistogramBuilder builder =
+        meterProvider
+            .get(registry)
+            .histogramBuilder(histogramName)
+            .setDescription(description)
+            .ofLongs();
+
+    if (unit != null) builder.setUnit(unit);
+
+    return builder.build();
+  }
+
+  public DoubleGauge doubleGauge(
+      String registry, String gaugeName, String description, String unit) {
+    DoubleGaugeBuilder builder =
+        meterProvider.get(registry).gaugeBuilder(gaugeName).setDescription(description);
+    if (unit != null) builder.setUnit(unit);
+
+    return builder.build();
+  }
+
+  public LongGauge longGauge(String registry, String gaugeName, String description, String unit) {
+    LongGaugeBuilder builder =
+        meterProvider.get(registry).gaugeBuilder(gaugeName).setDescription(description).ofLongs();
+    if (unit != null) builder.setUnit(unit);
+
+    return builder.build();
+  }
+
+  public ObservableLongCounter observableLongCounter(
+      String registry,
+      String counterName,
+      String description,
+      Consumer<ObservableLongMeasurement> callback,
+      String unit) {
+    LongCounterBuilder builder =
+        meterProvider.get(registry).counterBuilder(counterName).setDescription(description);
+    if (unit != null) builder.setUnit(unit);
+
+    return builder.buildWithCallback(callback);
+  }
+
+  public ObservableDoubleCounter observableDoubleCounter(
+      String registry,
+      String counterName,
+      String description,
+      Consumer<ObservableDoubleMeasurement> callback,
+      String unit) {
+    DoubleCounterBuilder builder =
+        meterProvider
+            .get(registry)
+            .counterBuilder(counterName)
+            .setDescription(description)
+            .ofDoubles();
+    if (unit != null) builder.setUnit(unit);
+
+    return builder.buildWithCallback(callback);
+  }
+
+  public ObservableLongGauge observableLongGauge(
+      String registry,
+      String gaugeName,
+      String description,
+      Consumer<ObservableLongMeasurement> callback,
+      String unit) {
+    LongGaugeBuilder builder =
+        meterProvider.get(registry).gaugeBuilder(gaugeName).setDescription(description).ofLongs();
+    if (unit != null) builder.setUnit(unit);
+
+    return builder.buildWithCallback(callback);
+  }
+
+  public ObservableLongUpDownCounter observableLongUpDownCounter(
+      String registry,
+      String counterName,
+      String description,
+      Consumer<ObservableLongMeasurement> callback,
+      String unit) {
+    LongUpDownCounterBuilder builder =
+        meterProvider.get(registry).upDownCounterBuilder(counterName).setDescription(description);
+    if (unit != null) builder.setUnit(unit);
+
+    return builder.buildWithCallback(callback);
+  }
+
+  public ObservableDoubleUpDownCounter observableDoubleUpDownCounter(
+      String registry,
+      String counterName,
+      String description,
+      Consumer<ObservableDoubleMeasurement> callback,
+      String unit) {
+    DoubleUpDownCounterBuilder builder =
+        meterProvider
+            .get(registry)
+            .upDownCounterBuilder(counterName)
+            .setDescription(description)
+            .ofDoubles();
+    if (unit != null) builder.setUnit(unit);
+
+    return builder.buildWithCallback(callback);
+  }
+
+  public io.opentelemetry.api.metrics.Meter getOtelRegistry(String registryName) {
+    return meters.get(registryName);
+  }
+
   // for unit tests
   public MetricRegistry.MetricSupplier<Counter> getCounterSupplier() {
     return counterSupplier;
@@ -407,6 +591,7 @@ public class SolrMetricManager {
    * @param name registry name
    * @return true if this name points to a registry that already exists, false otherwise
    */
+  // TODO SOLR-17458: We may not need
   public boolean hasRegistry(String name) {
     Set<String> names = registryNames();
     name = enforcePrefix(name);
@@ -420,6 +605,7 @@ public class SolrMetricManager {
    *     with a wildcard use the full registry name starting with {@link #REGISTRY_NAME_PREFIX}
    * @return set of existing registry names where at least one pattern matched.
    */
+  // TODO SOLR-17458: We may not need? Maybe make a custom OTEL metric reader instead
   public Set<String> registryNames(String... patterns) throws PatternSyntaxException {
     if (patterns == null || patterns.length == 0) {
       return registryNames();
@@ -456,6 +642,7 @@ public class SolrMetricManager {
    * @param registry already normalized name
    * @return true if the name matches one of shared registries
    */
+  // TODO SOLR-17458: We may not need
   private static boolean isSharedRegistry(String registry) {
     return JETTY_REGISTRY.equals(registry) || JVM_REGISTRY.equals(registry);
   }
@@ -467,6 +654,7 @@ public class SolrMetricManager {
    * @param registry name of the registry
    * @return existing or newly created registry
    */
+  // TODO SOLR-17458: We may not need
   public MetricRegistry registry(String registry) {
     return registry(registry, true);
   }
@@ -496,6 +684,7 @@ public class SolrMetricManager {
     }
   }
 
+  // TODO SOLR-17458: We may not need
   private static MetricRegistry getOrCreateRegistry(
       ConcurrentMap<String, MetricRegistry> map, String registry) {
     final MetricRegistry existing = map.get(registry);
@@ -517,6 +706,7 @@ public class SolrMetricManager {
    *
    * @param registry name of the registry to remove
    */
+  // TODO SOLR-17458: You can't delete OTEL meters
   public void removeRegistry(String registry) {
     // close any reporters for this registry first
     closeReporters(registry, null);
@@ -543,6 +733,7 @@ public class SolrMetricManager {
    *     exist, so the swap operation will only rename the existing registry without creating an
    *     empty one under the previous name.
    */
+  // NOCOMMIT SOLR-17458: Don't think we need
   public void swapRegistries(String registry1, String registry2) {
     registry1 = enforcePrefix(registry1);
     registry2 = enforcePrefix(registry2);
@@ -574,6 +765,7 @@ public class SolrMetricManager {
    * Potential conflict resolution strategies when attempting to register a new metric that already
    * exists
    */
+  // TODO SOLR-17458: Don't think we need
   public enum ResolutionStrategy {
     /**
      * The existing metric will be kept and the new metric will be ignored. If no metric exists,
@@ -596,6 +788,7 @@ public class SolrMetricManager {
    * @param metricPath (optional) additional top-most metric name path elements
    * @throws Exception if a metric with this name already exists.
    */
+  // I don't think we do something like this for OTEL?
   public void registerAll(
       String registry, MetricSet metrics, ResolutionStrategy strategy, String... metricPath)
       throws Exception {
@@ -619,6 +812,7 @@ public class SolrMetricManager {
    *
    * @param registry registry name
    */
+  // TODO SOLR-17458: Don't think we need
   public void clearRegistry(String registry) {
     registry(registry).removeMatching(MetricFilter.ALL);
   }
@@ -633,6 +827,8 @@ public class SolrMetricManager {
    *     start with the prefix will be removed.
    * @return set of metrics names that have been removed.
    */
+  // TODO SOLR-17458: This is not supported in otel. Metrics are immutable. We can at best filter
+  // them
   public Set<String> clearMetrics(String registry, String... metricPath) {
     PrefixFilter filter;
     if (metricPath == null || metricPath.length == 0) {
@@ -652,6 +848,8 @@ public class SolrMetricManager {
    * @param metricFilter filter (null is equivalent to {@link MetricFilter#ALL}).
    * @return map of matching names and metrics
    */
+  // TODO SOLR-17458: We will create an OTEL metric reader for this instead. For tests, we can
+  // create an in-memory metric reader
   public Map<String, Metric> getMetrics(String registry, MetricFilter metricFilter) {
     if (metricFilter == null || metricFilter == MetricFilter.ALL) {
       return registry(registry).getMetrics();
@@ -670,6 +868,7 @@ public class SolrMetricManager {
    * @param metricPath (optional) additional top-most metric name path elements
    * @return existing or a newly created {@link Meter}
    */
+  // TODO SOLR-17458: Don't need this
   public Meter meter(
       SolrMetricsContext context, String registry, String metricName, String... metricPath) {
     final String name = mkName(metricName, metricPath);
@@ -688,6 +887,7 @@ public class SolrMetricManager {
    * @param metricPath (optional) additional top-most metric name path elements
    * @return existing or a newly created {@link Timer}
    */
+  // TODO SOLR-17458: Don't need this
   public Timer timer(
       SolrMetricsContext context, String registry, String metricName, String... metricPath) {
     final String name = mkName(metricName, metricPath);
@@ -706,6 +906,7 @@ public class SolrMetricManager {
    * @param metricPath (optional) additional top-most metric name path elements
    * @return existing or a newly created {@link Counter}
    */
+  // TODO SOLR-17458: Don't need this
   public Counter counter(
       SolrMetricsContext context, String registry, String metricName, String... metricPath) {
     final String name = mkName(metricName, metricPath);
@@ -724,6 +925,7 @@ public class SolrMetricManager {
    * @param metricPath (optional) additional top-most metric name path elements
    * @return existing or a newly created {@link Histogram}
    */
+  // TODO SOLR-17458: Don't need this
   public Histogram histogram(
       SolrMetricsContext context, String registry, String metricName, String... metricPath) {
     final String name = mkName(metricName, metricPath);
@@ -737,6 +939,7 @@ public class SolrMetricManager {
    * @deprecated use {@link #registerMetric(SolrMetricsContext, String, Metric, ResolutionStrategy,
    *     String, String...)}
    */
+  // TODO SOLR-17458: Don't need this
   @Deprecated
   public void registerMetric(
       SolrMetricsContext context,
@@ -764,6 +967,7 @@ public class SolrMetricManager {
    *     notation
    * @param metricPath (optional) additional top-most metric name path elements
    */
+  // TODO SOLR-17458: Don't need this
   public void registerMetric(
       SolrMetricsContext context,
       String registry,
@@ -1398,4 +1602,8 @@ public class SolrMetricManager {
   public MetricsConfig getMetricsConfig() {
     return metricsConfig;
   }
+
+  public PrometheusMetricReader getMetricReader() {
+    return this.metricReader;
+  }
 }
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java
index 335a76cb7b8..6e06f59c338 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java
@@ -16,11 +16,15 @@
  */
 package org.apache.solr.metrics;
 
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.Attributes;
 import java.io.IOException;
 
 /** Used by objects that expose metrics through {@link SolrMetricManager}. */
 public interface SolrMetricProducer extends AutoCloseable {
 
+  public static final AttributeKey<String> TYPE_ATTR = AttributeKey.stringKey("type");
+
   /**
    * Unique metric tag identifies components with the same life-cycle, which should be registered /
    * unregistered together. It is in the format of A:B:C, where A is the parent of B is the parent
@@ -40,20 +44,18 @@ public interface SolrMetricProducer extends AutoCloseable {
   }
 
   /**
-   * Initialize metrics specific to this producer.
+   * NOCOMMIT SOLR-17458: The Scope parameter will be removed with Dropwizard
    *
-   * @param parentContext parent metrics context. If this component has the same life-cycle as the
-   *     parent it can simply use the parent context, otherwise it should obtain a child context
-   *     using {@link SolrMetricsContext#getChildContext(Object)} passing <code>this</code> as the
-   *     child object.
-   * @param scope component scope
+   * <p>{@link Attributes} passed is the base or common set of attributes that should be attached to
+   * every metric that will be initialized
    */
-  void initializeMetrics(SolrMetricsContext parentContext, String scope);
+  void initializeMetrics(SolrMetricsContext parentContext, Attributes attributes, String scope);
 
   /**
    * Implementations should return the context used in {@link #initializeMetrics(SolrMetricsContext,
-   * String)} to ensure proper cleanup of metrics at the end of the life-cycle of this component.
-   * This should be the child context if one was created, or null if the parent context was used.
+   * Attributes, String)} to ensure proper cleanup of metrics at the end of the life-cycle of this
+   * component. This should be the child context if one was created, or null if the parent context
+   * was used.
    */
   SolrMetricsContext getSolrMetricsContext();
 
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java
index f98d5c0ba80..269575798ec 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java
@@ -23,9 +23,19 @@ import com.codahale.metrics.Histogram;
 import com.codahale.metrics.Meter;
 import com.codahale.metrics.MetricRegistry;
 import com.codahale.metrics.Timer;
+import io.opentelemetry.api.metrics.DoubleCounter;
+import io.opentelemetry.api.metrics.DoubleGauge;
+import io.opentelemetry.api.metrics.DoubleHistogram;
+import io.opentelemetry.api.metrics.LongCounter;
+import io.opentelemetry.api.metrics.LongGauge;
+import io.opentelemetry.api.metrics.LongHistogram;
+import io.opentelemetry.api.metrics.LongUpDownCounter;
+import io.opentelemetry.api.metrics.ObservableLongGauge;
+import io.opentelemetry.api.metrics.ObservableLongMeasurement;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
 import org.apache.solr.util.stats.MetricUtils;
 
 /**
@@ -48,31 +58,37 @@ public class SolrMetricsContext {
   }
 
   /** See {@link SolrMetricManager#nullNumber()}. */
+  // TODO Remove
   public Object nullNumber() {
     return metricManager.nullNumber();
   }
 
   /** See {@link SolrMetricManager#notANumber()}. */
+  // TODO Remove
   public Object notANumber() {
     return metricManager.notANumber();
   }
 
   /** See {@link SolrMetricManager#nullString()}. */
+  // TODO Remove
   public Object nullString() {
     return metricManager.nullString();
   }
 
   /** See {@link SolrMetricManager#nullObject()}. */
+  // TODO Remove
   public Object nullObject() {
     return metricManager.nullObject();
   }
 
   /** Metrics tag that represents objects with the same life-cycle. */
+  // TODO Remove
   public String getTag() {
     return tag;
   }
 
   /** Return metric registry name used in this context. */
+  // TODO Change this to OTEL Scope
   public String getRegistryName() {
     return registryName;
   }
@@ -94,6 +110,7 @@ public class SolrMetricsContext {
    * </code>) of components that register gauge metrics with references to the current object's
    * instance. Failure to do so may result in hard-to-debug memory leaks.</b>
    */
+  // TODO don't need this
   public void unregister() {
     metricManager.unregisterGauges(registryName, tag);
   }
@@ -105,6 +122,7 @@ public class SolrMetricsContext {
    *
    * @param child child object that produces metrics with a different life-cycle than the parent.
    */
+  // TODO We shouldn't need child context anymore
   public SolrMetricsContext getChildContext(Object child) {
     SolrMetricsContext childContext =
         new SolrMetricsContext(
@@ -116,21 +134,94 @@ public class SolrMetricsContext {
    * Register a metric name that this component reports. This method is called by various metric
    * registration methods in {@link org.apache.solr.metrics.SolrMetricManager} in order to capture
    * what metric names are reported from this component (which in turn is called from {@link
-   * org.apache.solr.metrics.SolrMetricProducer#initializeMetrics(SolrMetricsContext, String)}).
+   * SolrMetricProducer#initializeMetrics(SolrMetricsContext,
+   * io.opentelemetry.api.common.Attributes, String)}).
    */
+  // TODO We can continue to register metric names
   public void registerMetricName(String name) {
     metricNames.add(name);
   }
 
   /** Return a snapshot of metric values that this component reports. */
+  // TODO Don't think this is needed anymore
   public Map<String, Object> getMetricsSnapshot() {
     return MetricUtils.convertMetrics(getMetricRegistry(), metricNames);
   }
 
+  public LongCounter longCounter(String metricName, String description) {
+    return longCounter(metricName, description, null);
+  }
+
+  public LongCounter longCounter(String metricName, String description, String unit) {
+    return metricManager.longCounter(registryName, metricName, description, unit);
+  }
+
+  public LongUpDownCounter longUpDownCounter(String metricName, String description) {
+    return longUpDownCounter(metricName, description, null);
+  }
+
+  public LongUpDownCounter longUpDownCounter(String metricName, String description, String unit) {
+    return metricManager.longUpDownCounter(registryName, metricName, description, unit);
+  }
+
+  public DoubleCounter doubleCounter(String metricName, String description) {
+    return doubleCounter(metricName, description, null);
+  }
+
+  public DoubleCounter doubleCounter(String metricName, String description, String unit) {
+    return metricManager.doubleCounter(registryName, metricName, description, unit);
+  }
+
+  public DoubleHistogram doubleHistogram(String metricName, String description) {
+    return metricManager.doubleHistogram(registryName, metricName, description, null);
+  }
+
+  public DoubleHistogram doubleHistogram(String metricName, String description, String unit) {
+    return metricManager.doubleHistogram(registryName, metricName, description, unit);
+  }
+
+  public LongHistogram longHistogram(String metricName, String description) {
+    return metricManager.longHistogram(registryName, metricName, description, null);
+  }
+
+  public LongHistogram longHistogram(String metricName, String description, String unit) {
+    return metricManager.longHistogram(registryName, metricName, description, unit);
+  }
+
+  public LongGauge longGauge(String metricName, String description) {
+    return metricManager.longGauge(registryName, metricName, description, null);
+  }
+
+  public LongGauge longGauge(String metricName, String description, String unit) {
+    return metricManager.longGauge(registryName, metricName, description, unit);
+  }
+
+  public DoubleGauge doubleGauge(String metricName, String description) {
+    return metricManager.doubleGauge(registryName, metricName, description, null);
+  }
+
+  public DoubleGauge doubleGauge(String metricName, String description, String unit) {
+    return metricManager.doubleGauge(registryName, metricName, description, unit);
+  }
+
+  public ObservableLongGauge observableLongGauge(
+      String metricName, String description, Consumer<ObservableLongMeasurement> callback) {
+    return metricManager.observableLongGauge(registryName, metricName, description, callback, null);
+  }
+
+  public ObservableLongGauge observableLongGauge(
+      String metricName,
+      String description,
+      String unit,
+      Consumer<ObservableLongMeasurement> callback) {
+    return metricManager.observableLongGauge(registryName, metricName, description, callback, unit);
+  }
+
   /**
    * Convenience method for {@link SolrMetricManager#meter(SolrMetricsContext, String, String,
    * String...)}.
    */
+  // TODO Remove
   public Meter meter(String metricName, String... metricPath) {
     return metricManager.meter(this, registryName, metricName, metricPath);
   }
@@ -139,6 +230,7 @@ public class SolrMetricsContext {
    * Convenience method for {@link SolrMetricManager#counter(SolrMetricsContext, String, String,
    * String...)}.
    */
+  // TODO Remove
   public Counter counter(String metricName, String... metricPath) {
     return metricManager.counter(this, registryName, metricName, metricPath);
   }
@@ -147,6 +239,7 @@ public class SolrMetricsContext {
    * Convenience method for {@link SolrMetricManager#registerGauge(SolrMetricsContext, String,
    * Gauge, String, SolrMetricManager.ResolutionStrategy, String, String...)}.
    */
+  // TODO Remove
   public void gauge(Gauge<?> gauge, boolean force, String metricName, String... metricPath) {
     metricManager.registerGauge(
         this,
@@ -164,6 +257,7 @@ public class SolrMetricsContext {
    * Convenience method for {@link SolrMetricManager#meter(SolrMetricsContext, String, String,
    * String...)}.
    */
+  // TODO Remove
   public Timer timer(String metricName, String... metricPath) {
     return metricManager.timer(this, registryName, metricName, metricPath);
   }
@@ -172,6 +266,7 @@ public class SolrMetricsContext {
    * Convenience method for {@link SolrMetricManager#histogram(SolrMetricsContext, String, String,
    * String...)}.
    */
+  // TODO Remove
   public Histogram histogram(String metricName, String... metricPath) {
     return metricManager.histogram(this, registryName, metricName, metricPath);
   }
@@ -179,6 +274,7 @@ public class SolrMetricsContext {
   /**
    * Get the {@link MetricRegistry} instance that is used for registering metrics in this context.
    */
+  // TODO Change this to OTEL Scope?
   public MetricRegistry getMetricRegistry() {
     return metricManager.registry(registryName);
   }
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/jetty/SolrJettyMetric.java b/solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedDoubleCounter.java
similarity index 60%
rename from solr/core/src/java/org/apache/solr/metrics/prometheus/jetty/SolrJettyMetric.java
rename to solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedDoubleCounter.java
index 95211f6d9eb..43adf8ad087 100644
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/jetty/SolrJettyMetric.java
+++ b/solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedDoubleCounter.java
@@ -14,14 +14,26 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.solr.metrics.prometheus.jetty;
+package org.apache.solr.metrics.otel.instruments;
 
-import com.codahale.metrics.Metric;
-import org.apache.solr.metrics.prometheus.SolrMetric;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.DoubleCounter;
 
-/** Base class is a wrapper to export a solr.jetty {@link com.codahale.metrics.Metric} */
-public abstract class SolrJettyMetric extends SolrMetric {
-  public SolrJettyMetric(Metric dropwizardMetric, String metricName) {
-    super(dropwizardMetric, metricName);
+public class AttributedDoubleCounter {
+
+  private final DoubleCounter counter;
+  private final Attributes attributes;
+
+  public AttributedDoubleCounter(DoubleCounter counter, Attributes attributes) {
+    this.counter = counter;
+    this.attributes = attributes;
+  }
+
+  public void inc() {
+    add(1.0);
+  }
+
+  public void add(Double value) {
+    counter.add(value, attributes);
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/jvm/SolrJvmMetric.java b/solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedDoubleGauge.java
similarity index 63%
rename from solr/core/src/java/org/apache/solr/metrics/prometheus/jvm/SolrJvmMetric.java
rename to solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedDoubleGauge.java
index 75541640488..ba809a0fd80 100644
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/jvm/SolrJvmMetric.java
+++ b/solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedDoubleGauge.java
@@ -14,14 +14,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.solr.metrics.prometheus.jvm;
+package org.apache.solr.metrics.otel.instruments;
 
-import com.codahale.metrics.Metric;
-import org.apache.solr.metrics.prometheus.SolrMetric;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.DoubleGauge;
 
-/** Base class is a wrapper to export a solr.jvm {@link com.codahale.metrics.Metric} */
-public abstract class SolrJvmMetric extends SolrMetric {
-  public SolrJvmMetric(Metric dropwizardMetric, String metricName) {
-    super(dropwizardMetric, metricName);
+public class AttributedDoubleGauge {
+  private final DoubleGauge gauge;
+  private final Attributes attributes;
+
+  public AttributedDoubleGauge(DoubleGauge gauge, Attributes attributes) {
+    this.gauge = gauge;
+    this.attributes = attributes;
+  }
+
+  public void set(Double value) {
+    gauge.set(value, attributes);
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/node/SolrNodeMetric.java b/solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedDoubleHistogram.java
similarity index 61%
rename from solr/core/src/java/org/apache/solr/metrics/prometheus/node/SolrNodeMetric.java
rename to solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedDoubleHistogram.java
index 34795e240de..7f573a3022a 100644
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/node/SolrNodeMetric.java
+++ b/solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedDoubleHistogram.java
@@ -14,16 +14,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.solr.metrics.prometheus.node;
+package org.apache.solr.metrics.otel.instruments;
 
-import com.codahale.metrics.Metric;
-import org.apache.solr.metrics.prometheus.SolrMetric;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.DoubleHistogram;
 
-/** Base class is a wrapper to export a solr.node {@link com.codahale.metrics.Metric} */
-public abstract class SolrNodeMetric extends SolrMetric {
-  public static final String NODE_THREAD_POOL = "solr_metrics_node_thread_pool";
+public class AttributedDoubleHistogram {
 
-  public SolrNodeMetric(Metric dropwizardMetric, String metricName) {
-    super(dropwizardMetric, metricName);
+  private final DoubleHistogram histogram;
+  private final Attributes attributes;
+
+  public AttributedDoubleHistogram(DoubleHistogram histogram, Attributes attributes) {
+    this.histogram = histogram;
+    this.attributes = attributes;
+  }
+
+  public void record(Double value) {
+    histogram.record(value, attributes);
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/SolrNoOpMetric.java b/solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedDoubleUpDownCounter.java
similarity index 56%
copy from solr/core/src/java/org/apache/solr/metrics/prometheus/SolrNoOpMetric.java
copy to solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedDoubleUpDownCounter.java
index 4d3dcbe87ab..23c0d3b9a53 100644
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/SolrNoOpMetric.java
+++ b/solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedDoubleUpDownCounter.java
@@ -14,16 +14,30 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.solr.metrics.prometheus;
+package org.apache.solr.metrics.otel.instruments;
 
-public class SolrNoOpMetric extends SolrMetric {
-  public SolrNoOpMetric() {}
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.DoubleUpDownCounter;
 
-  @Override
-  public SolrMetric parseLabels() {
-    return this;
+public class AttributedDoubleUpDownCounter {
+
+  private final DoubleUpDownCounter upDownCounter;
+  private final Attributes attributes;
+
+  public AttributedDoubleUpDownCounter(DoubleUpDownCounter upDownCounter, Attributes attributes) {
+    this.upDownCounter = upDownCounter;
+    this.attributes = attributes;
+  }
+
+  public void inc() {
+    add(1.0);
   }
 
-  @Override
-  public void toPrometheus(SolrPrometheusFormatter formatter) {}
+  public void dec() {
+    add(-1.0);
+  }
+
+  public void add(Double value) {
+    upDownCounter.add(value, attributes);
+  }
 }
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/SolrNoOpMetric.java b/solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedLongCounter.java
similarity index 59%
copy from solr/core/src/java/org/apache/solr/metrics/prometheus/SolrNoOpMetric.java
copy to solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedLongCounter.java
index 4d3dcbe87ab..f33b4784c53 100644
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/SolrNoOpMetric.java
+++ b/solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedLongCounter.java
@@ -14,16 +14,26 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.solr.metrics.prometheus;
+package org.apache.solr.metrics.otel.instruments;
 
-public class SolrNoOpMetric extends SolrMetric {
-  public SolrNoOpMetric() {}
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.LongCounter;
 
-  @Override
-  public SolrMetric parseLabels() {
-    return this;
+public class AttributedLongCounter {
+
+  private final LongCounter baseCounter;
+  private final io.opentelemetry.api.common.Attributes attributes;
+
+  public AttributedLongCounter(LongCounter baseCounter, Attributes attributes) {
+    this.baseCounter = baseCounter;
+    this.attributes = attributes;
   }
 
-  @Override
-  public void toPrometheus(SolrPrometheusFormatter formatter) {}
+  public void inc() {
+    add(1L);
+  }
+
+  public void add(Long value) {
+    baseCounter.add(value, attributes);
+  }
 }
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/SolrNoOpMetric.java b/solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedLongGauge.java
similarity index 64%
copy from solr/core/src/java/org/apache/solr/metrics/prometheus/SolrNoOpMetric.java
copy to solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedLongGauge.java
index 4d3dcbe87ab..5b34f0dc31a 100644
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/SolrNoOpMetric.java
+++ b/solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedLongGauge.java
@@ -14,16 +14,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.solr.metrics.prometheus;
+package org.apache.solr.metrics.otel.instruments;
 
-public class SolrNoOpMetric extends SolrMetric {
-  public SolrNoOpMetric() {}
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.LongGauge;
 
-  @Override
-  public SolrMetric parseLabels() {
-    return this;
+public class AttributedLongGauge {
+
+  private final LongGauge gauge;
+  private final Attributes attributes;
+
+  public AttributedLongGauge(LongGauge gauge, Attributes attributes) {
+    this.gauge = gauge;
+    this.attributes = attributes;
   }
 
-  @Override
-  public void toPrometheus(SolrPrometheusFormatter formatter) {}
+  public void set(Long value) {
+    gauge.set(value, attributes);
+  }
 }
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/SolrNoOpMetric.java b/solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedLongHistogram.java
similarity index 60%
copy from solr/core/src/java/org/apache/solr/metrics/prometheus/SolrNoOpMetric.java
copy to solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedLongHistogram.java
index 4d3dcbe87ab..6984fadbfa5 100644
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/SolrNoOpMetric.java
+++ b/solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedLongHistogram.java
@@ -14,16 +14,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.solr.metrics.prometheus;
+package org.apache.solr.metrics.otel.instruments;
 
-public class SolrNoOpMetric extends SolrMetric {
-  public SolrNoOpMetric() {}
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.LongHistogram;
 
-  @Override
-  public SolrMetric parseLabels() {
-    return this;
+public class AttributedLongHistogram {
+
+  protected final LongHistogram histogram;
+  protected final Attributes attributes;
+
+  public AttributedLongHistogram(
+      LongHistogram histogram, io.opentelemetry.api.common.Attributes attributes) {
+    this.histogram = histogram;
+    this.attributes = attributes;
   }
 
-  @Override
-  public void toPrometheus(SolrPrometheusFormatter formatter) {}
+  public void record(Long value) {
+    histogram.record(value, attributes);
+  }
 }
diff --git a/solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedLongTimer.java b/solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedLongTimer.java
new file mode 100644
index 00000000000..588a81507db
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedLongTimer.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.metrics.otel.instruments;
+
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.LongHistogram;
+import org.apache.solr.util.RTimer;
+
+/**
+ * This class records the elapsed time (in milliseconds) to a {@link LongHistogram} with the use of
+ * a {@link MetricTimer}
+ */
+public class AttributedLongTimer extends AttributedLongHistogram {
+
+  public AttributedLongTimer(LongHistogram histogram, Attributes attributes) {
+    super(histogram, attributes);
+  }
+
+  /**
+   * Return a {@link MetricTimer} and starts the timer. When the timer calls {@link
+   * MetricTimer#stop()}, the elapsed time is recorded
+   */
+  public MetricTimer start() {
+    return new MetricTimer(this);
+  }
+
+  public static class MetricTimer extends RTimer {
+    private final AttributedLongTimer bound;
+
+    private MetricTimer(AttributedLongTimer bound) {
+      this.bound = bound;
+    }
+
+    @Override
+    public double stop() {
+      double elapsedTime = super.stop();
+      bound.record(Double.valueOf(elapsedTime).longValue());
+      return elapsedTime;
+    }
+  }
+}
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/SolrNoOpMetric.java b/solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedLongUpDownCounter.java
similarity index 57%
rename from solr/core/src/java/org/apache/solr/metrics/prometheus/SolrNoOpMetric.java
rename to solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedLongUpDownCounter.java
index 4d3dcbe87ab..91487c9e677 100644
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/SolrNoOpMetric.java
+++ b/solr/core/src/java/org/apache/solr/metrics/otel/instruments/AttributedLongUpDownCounter.java
@@ -14,16 +14,30 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.solr.metrics.prometheus;
+package org.apache.solr.metrics.otel.instruments;
 
-public class SolrNoOpMetric extends SolrMetric {
-  public SolrNoOpMetric() {}
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.LongUpDownCounter;
 
-  @Override
-  public SolrMetric parseLabels() {
-    return this;
+public class AttributedLongUpDownCounter {
+
+  private final LongUpDownCounter upDownCounter;
+  private final Attributes attributes;
+
+  public AttributedLongUpDownCounter(LongUpDownCounter upDownCounter, Attributes attributes) {
+    this.upDownCounter = upDownCounter;
+    this.attributes = attributes;
+  }
+
+  public void inc() {
+    add(1L);
   }
 
-  @Override
-  public void toPrometheus(SolrPrometheusFormatter formatter) {}
+  public void dec() {
+    add(-1L);
+  }
+
+  public void add(Long value) {
+    upDownCounter.add(value, attributes);
+  }
 }
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/jetty/package-info.java b/solr/core/src/java/org/apache/solr/metrics/otel/instruments/package-info.java
similarity index 74%
rename from solr/core/src/java/org/apache/solr/metrics/prometheus/jetty/package-info.java
rename to solr/core/src/java/org/apache/solr/metrics/otel/instruments/package-info.java
index c3c039c879e..fcbb9581d92 100644
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/jetty/package-info.java
+++ b/solr/core/src/java/org/apache/solr/metrics/otel/instruments/package-info.java
@@ -16,7 +16,8 @@
  */
 
 /**
- * The {@link org.apache.solr.metrics.prometheus.jetty.SolrPrometheusJettyFormatter} is responsible
- * for exporting solr.jetty registry metrics to Prometheus.
+ * A collection of bound OTEL instruments which are reusable wrappers around OpenTelemetry metric
+ * instruments that are pre-bound to a fixed set of Attributes. This avoids repeated attribute
+ * allocation on every metric recording.
  */
-package org.apache.solr.metrics.prometheus.jetty;
+package org.apache.solr.metrics.otel.instruments;
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/jvm/package-info.java b/solr/core/src/java/org/apache/solr/metrics/otel/package-info.java
similarity index 79%
rename from solr/core/src/java/org/apache/solr/metrics/prometheus/jvm/package-info.java
rename to solr/core/src/java/org/apache/solr/metrics/otel/package-info.java
index 5150b3e2a19..91ec79ab3ff 100644
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/jvm/package-info.java
+++ b/solr/core/src/java/org/apache/solr/metrics/otel/package-info.java
@@ -16,7 +16,7 @@
  */
 
 /**
- * The {@link org.apache.solr.metrics.prometheus.jvm.SolrPrometheusJvmFormatter} is responsible for
- * exporting solr.jvm registry metrics to Prometheus.
+ * Interfaces for creating wrappers around Open Telemetry instruments created from {@link
+ * io.opentelemetry.api.metrics.MeterProvider} to record and measure metrics
  */
-package org.apache.solr.metrics.prometheus.jvm;
+package org.apache.solr.metrics.otel;
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/SolrMetric.java b/solr/core/src/java/org/apache/solr/metrics/prometheus/SolrMetric.java
deleted file mode 100644
index e95316c0565..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/SolrMetric.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.prometheus;
-
-import com.codahale.metrics.Metric;
-import io.prometheus.metrics.model.snapshots.Labels;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Base class is a wrapper to categorize and export {@link com.codahale.metrics.Metric} to {@link
- * io.prometheus.metrics.model.snapshots.DataPointSnapshot} and register to a {@link
- * SolrPrometheusFormatter}. {@link com.codahale.metrics.MetricRegistry} does not support tags
- * unlike prometheus. Metrics registered to the registry need to be parsed out from the metric name
- * to be exported to {@link io.prometheus.metrics.model.snapshots.DataPointSnapshot}
- */
-public abstract class SolrMetric {
-  public Metric dropwizardMetric;
-  public String metricName;
-  public Map<String, String> labels = new HashMap<>();
-
-  public SolrMetric() {}
-
-  public SolrMetric(Metric dropwizardMetric, String metricName) {
-    this.dropwizardMetric = dropwizardMetric;
-    this.metricName = metricName;
-  }
-
-  /*
-   * Parse labels from the Dropwizard Metric name to be exported
-   */
-  public abstract SolrMetric parseLabels();
-
-  /*
-   * Export metric to Prometheus with labels
-   */
-  public abstract void toPrometheus(SolrPrometheusFormatter formatter);
-
-  public Labels getLabels() {
-    return Labels.of(new ArrayList<>(labels.keySet()), new ArrayList<>(labels.values()));
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/core/PrometheusCoreFormatterInfo.java b/solr/core/src/java/org/apache/solr/metrics/prometheus/core/PrometheusCoreFormatterInfo.java
deleted file mode 100644
index b80b406d356..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/core/PrometheusCoreFormatterInfo.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.prometheus.core;
-
-public interface PrometheusCoreFormatterInfo {
-  /** Category of prefix Solr Core dropwizard handler metric names */
-  enum CoreCategory {
-    ADMIN,
-    QUERY,
-    UPDATE,
-    REPLICATION,
-    TLOG,
-    CACHE,
-    SEARCHER,
-    HIGHLIGHTER,
-    INDEX,
-    CORE
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrCoreCacheMetric.java b/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrCoreCacheMetric.java
deleted file mode 100644
index d4d9627c222..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrCoreCacheMetric.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.prometheus.core;
-
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Metric;
-import org.apache.solr.metrics.prometheus.SolrPrometheusFormatter;
-
-/** Dropwizard metrics of name CACHE.* */
-public class SolrCoreCacheMetric extends SolrCoreMetric {
-  public static final String CORE_CACHE_SEARCHER_METRICS = "solr_metrics_core_cache";
-
-  public SolrCoreCacheMetric(Metric dropwizardMetric, String metricName) {
-    super(dropwizardMetric, metricName);
-  }
-
-  /*
-   * Metric examples being exported
-   * CACHE.searcher.documentCache
-   */
-
-  @Override
-  public SolrCoreMetric parseLabels() {
-    String[] parsedMetric = metricName.split("\\.");
-    if (dropwizardMetric instanceof Gauge) {
-      labels.put("cacheType", parsedMetric[2]);
-    }
-    return this;
-  }
-
-  @Override
-  public void toPrometheus(SolrPrometheusFormatter formatter) {
-    if (dropwizardMetric instanceof Gauge) {
-      formatter.exportGauge(CORE_CACHE_SEARCHER_METRICS, (Gauge<?>) dropwizardMetric, getLabels());
-    }
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrCoreHighlighterMetric.java b/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrCoreHighlighterMetric.java
deleted file mode 100644
index d3da3d3426f..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrCoreHighlighterMetric.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.prometheus.core;
-
-import com.codahale.metrics.Counter;
-import com.codahale.metrics.Metric;
-import org.apache.solr.metrics.prometheus.SolrPrometheusFormatter;
-
-/** Dropwizard metrics of name HIGHLIGHTER.* */
-public class SolrCoreHighlighterMetric extends SolrCoreMetric {
-  public static final String CORE_HIGHLIGHER_METRICS = "solr_metrics_core_highlighter_requests";
-
-  public SolrCoreHighlighterMetric(Metric dropwizardMetric, String metricName) {
-    super(dropwizardMetric, metricName);
-  }
-
-  /*
-   * Metric examples being exported
-   * HIGHLIGHTER.SolrBoundaryScanner.default.requests
-   * HIGHLIGHTER.SolrFragmentsBuilder.colored.requests
-   */
-
-  @Override
-  public SolrCoreMetric parseLabels() {
-    String[] parsedMetric = metricName.split("\\.");
-    labels.put("type", parsedMetric[1]);
-    labels.put("item", parsedMetric[2]);
-    return this;
-  }
-
-  @Override
-  public void toPrometheus(SolrPrometheusFormatter formatter) {
-    formatter.exportCounter(CORE_HIGHLIGHER_METRICS, (Counter) dropwizardMetric, getLabels());
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrCoreIndexMetric.java b/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrCoreIndexMetric.java
deleted file mode 100644
index e45e1e955bd..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrCoreIndexMetric.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.prometheus.core;
-
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Metric;
-import org.apache.solr.metrics.prometheus.SolrPrometheusFormatter;
-
-/** Dropwizard metrics of name INDEX.* */
-public class SolrCoreIndexMetric extends SolrCoreMetric {
-  public static final String CORE_INDEX_METRICS = "solr_metrics_core_index_size_bytes";
-
-  public SolrCoreIndexMetric(Metric dropwizardMetric, String metricName) {
-    super(dropwizardMetric, metricName);
-  }
-
-  /*
-   * Metric examples being exported
-   * INDEX.sizeInBytes
-   */
-
-  @Override
-  public SolrCoreMetric parseLabels() {
-    return this;
-  }
-
-  @Override
-  public void toPrometheus(SolrPrometheusFormatter formatter) {
-    if (metricName.endsWith("sizeInBytes")) {
-      formatter.exportGauge(CORE_INDEX_METRICS, (Gauge<?>) dropwizardMetric, getLabels());
-    }
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrCoreMetric.java b/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrCoreMetric.java
deleted file mode 100644
index 59236b18629..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrCoreMetric.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.prometheus.core;
-
-import com.codahale.metrics.Metric;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import org.apache.solr.common.SolrException;
-import org.apache.solr.metrics.prometheus.SolrMetric;
-
-/** Base class is a wrapper to export a solr.core {@link com.codahale.metrics.Metric} */
-public abstract class SolrCoreMetric extends SolrMetric {
-
-  public static Pattern CLOUD_CORE_PATTERN =
-      Pattern.compile(
-          "(?<core>^core_(?<collection>.*)_(?<shard>shard[0-9]+)_(?<replica>replica_.[0-9]+)).(.*)$");
-  public static Pattern STANDALONE_CORE_PATTERN = Pattern.compile("^core_(?<core>.*?)\\.(.*)$");
-
-  public String coreName;
-
-  public SolrCoreMetric(Metric dropwizardMetric, String metricName) {
-    // Remove Core Name prefix from metric as it is no longer needed for tag parsing from this point
-    super(dropwizardMetric, metricName.substring(metricName.indexOf(".") + 1));
-    Matcher cloudPattern = CLOUD_CORE_PATTERN.matcher(metricName);
-    Matcher standalonePattern = STANDALONE_CORE_PATTERN.matcher(metricName);
-    if (cloudPattern.find()) {
-      coreName = cloudPattern.group("core");
-      cloudPattern
-          .namedGroups()
-          .forEach((key, value) -> labels.put(key, cloudPattern.group(value)));
-    } else if (standalonePattern.find()) {
-      coreName = standalonePattern.group("core");
-      standalonePattern
-          .namedGroups()
-          .forEach((key, value) -> labels.put(key, standalonePattern.group(value)));
-    } else {
-      throw new SolrException(
-          SolrException.ErrorCode.SERVER_ERROR,
-          "Core name does not match pattern for parsing in metric " + metricName);
-    }
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrCoreTlogMetric.java b/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrCoreTlogMetric.java
deleted file mode 100644
index 7286c99d986..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrCoreTlogMetric.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.prometheus.core;
-
-import com.codahale.metrics.Meter;
-import com.codahale.metrics.Metric;
-import org.apache.solr.metrics.prometheus.SolrPrometheusFormatter;
-
-/** Dropwizard metrics of name TLOG.* */
-public class SolrCoreTlogMetric extends SolrCoreMetric {
-  public static final String CORE_TLOG_METRICS = "solr_metrics_core_tlog";
-
-  public SolrCoreTlogMetric(Metric dropwizardMetric, String metricName) {
-    super(dropwizardMetric, metricName);
-  }
-
-  /*
-   * Metric examples being exported
-   * TLOG.buffered.ops
-   */
-
-  @Override
-  public SolrCoreMetric parseLabels() {
-    String[] parsedMetric = metricName.split("\\.");
-    if (dropwizardMetric instanceof Meter) {
-      labels.put("item", parsedMetric[1]);
-    }
-    return this;
-  }
-
-  @Override
-  public void toPrometheus(SolrPrometheusFormatter formatter) {
-    if (dropwizardMetric instanceof Meter) {
-      formatter.exportMeter(CORE_TLOG_METRICS, (Meter) dropwizardMetric, getLabels());
-    }
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrPrometheusCoreFormatter.java b/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrPrometheusCoreFormatter.java
deleted file mode 100644
index dd3ed025ddf..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/core/SolrPrometheusCoreFormatter.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.prometheus.core;
-
-import com.codahale.metrics.Metric;
-import com.google.common.base.Enums;
-import org.apache.solr.metrics.prometheus.SolrMetric;
-import org.apache.solr.metrics.prometheus.SolrNoOpMetric;
-import org.apache.solr.metrics.prometheus.SolrPrometheusFormatter;
-
-/**
- * This class maintains a {@link io.prometheus.metrics.model.snapshots.MetricSnapshot}s exported
- * from solr.core {@link com.codahale.metrics.MetricRegistry}
- */
-public class SolrPrometheusCoreFormatter extends SolrPrometheusFormatter
-    implements PrometheusCoreFormatterInfo {
-
-  public SolrPrometheusCoreFormatter() {
-    super();
-  }
-
-  @Override
-  public void exportDropwizardMetric(Metric dropwizardMetric, String metricName) {
-    SolrMetric solrCoreMetric = categorizeMetric(dropwizardMetric, metricName);
-    solrCoreMetric.parseLabels().toPrometheus(this);
-  }
-
-  @Override
-  public SolrMetric categorizeMetric(Metric dropwizardMetric, String metricName) {
-    String metricCategory = metricName.split("\\.", 3)[1];
-    if (!Enums.getIfPresent(PrometheusCoreFormatterInfo.CoreCategory.class, metricCategory)
-        .isPresent()) {
-      return new SolrNoOpMetric();
-    }
-    switch (CoreCategory.valueOf(metricCategory)) {
-      case ADMIN:
-      case QUERY:
-      case UPDATE:
-      case REPLICATION:
-        return new SolrCoreHandlerMetric(dropwizardMetric, metricName);
-      case TLOG:
-        return new SolrCoreTlogMetric(dropwizardMetric, metricName);
-      case CACHE:
-        return new SolrCoreCacheMetric(dropwizardMetric, metricName);
-      case SEARCHER:
-        return new SolrCoreSearcherMetric(dropwizardMetric, metricName);
-      case HIGHLIGHTER:
-        return new SolrCoreHighlighterMetric(dropwizardMetric, metricName);
-      case INDEX:
-        return new SolrCoreIndexMetric(dropwizardMetric, metricName);
-      case CORE:
-      default:
-        return new SolrNoOpMetric();
-    }
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/core/package-info.java b/solr/core/src/java/org/apache/solr/metrics/prometheus/core/package-info.java
deleted file mode 100644
index acabe654867..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/core/package-info.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * The {@link org.apache.solr.metrics.prometheus.core.SolrPrometheusCoreFormatter} is responsible
- * for exporting solr.core registry metrics to Prometheus.
- */
-package org.apache.solr.metrics.prometheus.core;
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/jetty/SolrJettyDispatchesMetric.java b/solr/core/src/java/org/apache/solr/metrics/prometheus/jetty/SolrJettyDispatchesMetric.java
deleted file mode 100644
index cd057418845..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/jetty/SolrJettyDispatchesMetric.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.prometheus.jetty;
-
-import com.codahale.metrics.Metric;
-import com.codahale.metrics.Timer;
-import org.apache.solr.metrics.prometheus.SolrMetric;
-import org.apache.solr.metrics.prometheus.SolrPrometheusFormatter;
-
-/* Dropwizard metrics of name *.dispatches */
-public class SolrJettyDispatchesMetric extends SolrJettyMetric {
-  public static final String JETTY_DISPATCHES_TOTAL = "solr_metrics_jetty_dispatches";
-
-  public SolrJettyDispatchesMetric(Metric dropwizardMetric, String metricName) {
-    super(dropwizardMetric, metricName);
-  }
-
-  /*
-   * Metric examples being exported
-   * org.eclipse.jetty.server.handler.DefaultHandler.dispatches
-   */
-
-  @Override
-  public SolrMetric parseLabels() {
-    return this;
-  }
-
-  @Override
-  public void toPrometheus(SolrPrometheusFormatter formatter) {
-    formatter.exportTimerCount(JETTY_DISPATCHES_TOTAL, (Timer) dropwizardMetric, getLabels());
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/jetty/SolrJettyReqRespMetric.java b/solr/core/src/java/org/apache/solr/metrics/prometheus/jetty/SolrJettyReqRespMetric.java
deleted file mode 100644
index 374f0bdfd59..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/jetty/SolrJettyReqRespMetric.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.prometheus.jetty;
-
-import com.codahale.metrics.Counter;
-import com.codahale.metrics.Meter;
-import com.codahale.metrics.Metric;
-import com.codahale.metrics.Timer;
-import org.apache.solr.metrics.prometheus.SolrMetric;
-import org.apache.solr.metrics.prometheus.SolrPrometheusFormatter;
-
-/* Dropwizard metrics of name *.xx-responses and *-requests */
-public class SolrJettyReqRespMetric extends SolrJettyMetric {
-  public static final String JETTY_RESPONSES_TOTAL = "solr_metrics_jetty_response";
-  public static final String JETTY_REQUESTS_TOTAL = "solr_metrics_jetty_requests";
-
-  public SolrJettyReqRespMetric(Metric dropwizardMetric, String metricName) {
-    super(dropwizardMetric, metricName);
-  }
-
-  /*
-   * Metric examples being exported
-   * org.eclipse.jetty.server.handler.DefaultHandler.2xx-responses
-   * org.eclipse.jetty.server.handler.DefaultHandler.get-requests
-   */
-
-  @Override
-  public SolrMetric parseLabels() {
-    String[] parsedMetric = metricName.split("\\.");
-    String label = parsedMetric[parsedMetric.length - 1].split("-")[0];
-    if (metricName.endsWith("xx-responses")) {
-      labels.put("status", label);
-    } else {
-      labels.put("method", label);
-    }
-    return this;
-  }
-
-  @Override
-  public void toPrometheus(SolrPrometheusFormatter formatter) {
-    if (metricName.endsWith("xx-responses")) {
-      formatter.exportMeter(JETTY_RESPONSES_TOTAL, (Meter) dropwizardMetric, getLabels());
-    } else if (metricName.endsWith("-requests")) {
-      if (dropwizardMetric instanceof Counter) {
-        formatter.exportCounter(JETTY_REQUESTS_TOTAL, (Counter) dropwizardMetric, getLabels());
-      } else if (dropwizardMetric instanceof Timer) {
-        formatter.exportTimerCount(JETTY_REQUESTS_TOTAL, (Timer) dropwizardMetric, getLabels());
-      }
-    }
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/jetty/SolrPrometheusJettyFormatter.java b/solr/core/src/java/org/apache/solr/metrics/prometheus/jetty/SolrPrometheusJettyFormatter.java
deleted file mode 100644
index 88eae10d39f..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/jetty/SolrPrometheusJettyFormatter.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.prometheus.jetty;
-
-import com.codahale.metrics.Metric;
-import org.apache.solr.metrics.prometheus.SolrMetric;
-import org.apache.solr.metrics.prometheus.SolrNoOpMetric;
-import org.apache.solr.metrics.prometheus.SolrPrometheusFormatter;
-
-/**
- * This class maintains a {@link io.prometheus.metrics.model.snapshots.MetricSnapshot}s exported
- * from solr.jetty {@link com.codahale.metrics.MetricRegistry}
- */
-public class SolrPrometheusJettyFormatter extends SolrPrometheusFormatter {
-  public SolrPrometheusJettyFormatter() {
-    super();
-  }
-
-  @Override
-  public void exportDropwizardMetric(Metric dropwizardMetric, String metricName) {
-    SolrMetric solrJettyMetric = categorizeMetric(dropwizardMetric, metricName);
-    solrJettyMetric.parseLabels().toPrometheus(this);
-  }
-
-  @Override
-  public SolrMetric categorizeMetric(Metric dropwizardMetric, String metricName) {
-    if (metricName.endsWith("xx-responses") || metricName.endsWith("-requests")) {
-      return new SolrJettyReqRespMetric(dropwizardMetric, metricName);
-    } else if (metricName.endsWith(".dispatches")) {
-      return new SolrJettyDispatchesMetric(dropwizardMetric, metricName);
-    } else {
-      return new SolrNoOpMetric();
-    }
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/jvm/PrometheusJvmFormatterInfo.java b/solr/core/src/java/org/apache/solr/metrics/prometheus/jvm/PrometheusJvmFormatterInfo.java
deleted file mode 100644
index 660154ece4a..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/jvm/PrometheusJvmFormatterInfo.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.prometheus.jvm;
-
-public interface PrometheusJvmFormatterInfo {
-  /** Category of prefix Solr JVM dropwizard handler metric names */
-  enum JvmCategory {
-    buffers,
-    gc,
-    memory,
-    os,
-    threads,
-    classes,
-    system
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/jvm/SolrJvmBuffersMetric.java b/solr/core/src/java/org/apache/solr/metrics/prometheus/jvm/SolrJvmBuffersMetric.java
deleted file mode 100644
index 58f7ae697a5..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/jvm/SolrJvmBuffersMetric.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.prometheus.jvm;
-
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Metric;
-import org.apache.solr.metrics.prometheus.SolrPrometheusFormatter;
-
-/* Dropwizard metrics of name buffers.* */
-public class SolrJvmBuffersMetric extends SolrJvmMetric {
-  public static final String JVM_BUFFERS = "solr_metrics_jvm_buffers";
-  public static final String JVM_BUFFERS_BYTES = "solr_metrics_jvm_buffers_bytes";
-
-  public SolrJvmBuffersMetric(Metric dropwizardMetric, String metricName) {
-    super(dropwizardMetric, metricName);
-  }
-
-  /*
-   * Metric examples being exported
-   * buffers.direct.MemoryUsed
-   * buffers.mapped.Count
-   */
-
-  @Override
-  public SolrJvmMetric parseLabels() {
-    String[] parsedMetric = metricName.split("\\.");
-    labels.put("pool", parsedMetric[1]);
-    labels.put("item", parsedMetric[2]);
-    return this;
-  }
-
-  @Override
-  public void toPrometheus(SolrPrometheusFormatter formatter) {
-    String[] parsedMetric = metricName.split("\\.");
-    String metricType = parsedMetric[parsedMetric.length - 1];
-    if (metricType.equals("Count")) {
-      formatter.exportGauge(JVM_BUFFERS, (Gauge<?>) dropwizardMetric, getLabels());
-    } else if (metricType.equals(("MemoryUsed")) || metricType.equals(("TotalCapacity"))) {
-      formatter.exportGauge(JVM_BUFFERS_BYTES, (Gauge<?>) dropwizardMetric, getLabels());
-    }
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/jvm/SolrJvmGcMetrics.java b/solr/core/src/java/org/apache/solr/metrics/prometheus/jvm/SolrJvmGcMetrics.java
deleted file mode 100644
index 7dbb8baa817..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/jvm/SolrJvmGcMetrics.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.prometheus.jvm;
-
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Metric;
-import org.apache.solr.metrics.prometheus.SolrMetric;
-import org.apache.solr.metrics.prometheus.SolrPrometheusFormatter;
-
-/* Dropwizard metrics of name gc.* */
-public class SolrJvmGcMetrics extends SolrJvmMetric {
-  public static final String JVM_GC = "solr_metrics_jvm_gc";
-  public static final String JVM_GC_SECONDS = "solr_metrics_jvm_gc_seconds";
-
-  public SolrJvmGcMetrics(Metric dropwizardMetric, String metricName) {
-    super(dropwizardMetric, metricName);
-  }
-
-  /*
-   * Metric examples being exported
-   * gc.G1-Old-Generation.time
-   * gc.G1-Young-Generation.count
-   */
-
-  @Override
-  public SolrMetric parseLabels() {
-    String[] parsedMetric = metricName.split("\\.");
-    labels.put("item", parsedMetric[1]);
-    return this;
-  }
-
-  @Override
-  public void toPrometheus(SolrPrometheusFormatter formatter) {
-    if (metricName.endsWith(".count")) {
-      formatter.exportGauge(JVM_GC, (Gauge<?>) dropwizardMetric, getLabels());
-    } else if (metricName.endsWith(".time")) {
-      formatter.exportGauge(JVM_GC_SECONDS, (Gauge<?>) dropwizardMetric, getLabels());
-    }
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/jvm/SolrJvmMemoryMetric.java b/solr/core/src/java/org/apache/solr/metrics/prometheus/jvm/SolrJvmMemoryMetric.java
deleted file mode 100644
index 5ba1189c283..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/jvm/SolrJvmMemoryMetric.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.prometheus.jvm;
-
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Metric;
-import org.apache.solr.metrics.prometheus.SolrMetric;
-import org.apache.solr.metrics.prometheus.SolrPrometheusFormatter;
-
-/* Dropwizard metrics of name memory.* */
-public class SolrJvmMemoryMetric extends SolrJvmMetric {
-  public static final String JVM_MEMORY_POOL_BYTES = "solr_metrics_jvm_memory_pools_bytes";
-  public static final String JVM_MEMORY = "solr_metrics_jvm_heap";
-
-  public SolrJvmMemoryMetric(Metric dropwizardMetric, String metricName) {
-    super(dropwizardMetric, metricName);
-  }
-
-  /*
-   * Metric examples being exported
-   * memory.heap.usage
-   * memory.non-heap.committed
-   * memory.total.committed
-   * memory.pools.CodeHeap-'non-nmethods'.committed
-   */
-
-  @Override
-  public SolrMetric parseLabels() {
-    String[] parsedMetric = metricName.split("\\.");
-    labels.put("item", parsedMetric[parsedMetric.length - 1]);
-    return this;
-  }
-
-  @Override
-  public void toPrometheus(SolrPrometheusFormatter formatter) {
-    String[] parsedMetric = metricName.split("\\.");
-    String metricType = parsedMetric[1];
-    switch (metricType) {
-      case "heap":
-      case "non-heap":
-      case "total":
-        labels.put("memory", parsedMetric[1]);
-        formatter.exportGauge(JVM_MEMORY, (Gauge<?>) dropwizardMetric, getLabels());
-        break;
-      case "pools":
-        labels.put("space", parsedMetric[2]);
-        formatter.exportGauge(JVM_MEMORY_POOL_BYTES, (Gauge<?>) dropwizardMetric, getLabels());
-        break;
-    }
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/jvm/SolrJvmOsMetric.java b/solr/core/src/java/org/apache/solr/metrics/prometheus/jvm/SolrJvmOsMetric.java
deleted file mode 100644
index 9b5d4ce1d69..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/jvm/SolrJvmOsMetric.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.prometheus.jvm;
-
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Metric;
-import org.apache.solr.metrics.prometheus.SolrMetric;
-import org.apache.solr.metrics.prometheus.SolrPrometheusFormatter;
-
-/* Dropwizard metrics of name os.* and threads.* */
-public class SolrJvmOsMetric extends SolrJvmMetric {
-  public static final String JVM_OS_THREADS = "solr_metrics_jvm_threads";
-  public static final String JVM_OS = "solr_metrics_os";
-
-  public SolrJvmOsMetric(Metric dropwizardMetric, String metricName) {
-    super(dropwizardMetric, metricName);
-  }
-
-  /*
-   * Metric examples being exported
-   * os.availableProcessors
-   * threads.peak.count
-   */
-
-  @Override
-  public SolrMetric parseLabels() {
-    String[] parsedMetric = metricName.split("\\.");
-    if (parsedMetric[0].equals("threads")) {
-      labels.put("item", parsedMetric[1]);
-    } else {
-      labels.put("item", parsedMetric[parsedMetric.length - 1]);
-    }
-    return this;
-  }
-
-  @Override
-  public void toPrometheus(SolrPrometheusFormatter formatter) {
-    if (metricName.startsWith("threads.")) {
-      formatter.exportGauge(JVM_OS_THREADS, (Gauge<?>) dropwizardMetric, getLabels());
-    } else {
-      formatter.exportGauge(JVM_OS, (Gauge<?>) dropwizardMetric, getLabels());
-    }
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/jvm/SolrPrometheusJvmFormatter.java b/solr/core/src/java/org/apache/solr/metrics/prometheus/jvm/SolrPrometheusJvmFormatter.java
deleted file mode 100644
index 888a0cb9927..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/jvm/SolrPrometheusJvmFormatter.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.prometheus.jvm;
-
-import com.codahale.metrics.Metric;
-import com.google.common.base.Enums;
-import org.apache.solr.metrics.prometheus.SolrMetric;
-import org.apache.solr.metrics.prometheus.SolrNoOpMetric;
-import org.apache.solr.metrics.prometheus.SolrPrometheusFormatter;
-
-/**
- * This class maintains a {@link io.prometheus.metrics.model.snapshots.MetricSnapshot}s exported
- * from solr.jvm {@link com.codahale.metrics.MetricRegistry}
- */
-public class SolrPrometheusJvmFormatter extends SolrPrometheusFormatter
-    implements PrometheusJvmFormatterInfo {
-  public SolrPrometheusJvmFormatter() {
-    super();
-  }
-
-  @Override
-  public void exportDropwizardMetric(Metric dropwizardMetric, String metricName) {
-    SolrMetric solrJvmMetric = categorizeMetric(dropwizardMetric, metricName);
-    solrJvmMetric.parseLabels().toPrometheus(this);
-  }
-
-  @Override
-  public SolrMetric categorizeMetric(Metric dropwizardMetric, String metricName) {
-    String metricCategory = metricName.split("\\.", 2)[0];
-    if (!Enums.getIfPresent(JvmCategory.class, metricCategory).isPresent()) {
-      return new SolrNoOpMetric();
-    }
-    switch (JvmCategory.valueOf(metricCategory)) {
-      case gc:
-        return new SolrJvmGcMetrics(dropwizardMetric, metricName);
-      case memory:
-        return new SolrJvmMemoryMetric(dropwizardMetric, metricName);
-      case os:
-      case threads:
-        return new SolrJvmOsMetric(dropwizardMetric, metricName);
-      case buffers:
-        return new SolrJvmBuffersMetric(dropwizardMetric, metricName);
-      default:
-        return new SolrNoOpMetric();
-    }
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/node/PrometheusNodeFormatterInfo.java b/solr/core/src/java/org/apache/solr/metrics/prometheus/node/PrometheusNodeFormatterInfo.java
deleted file mode 100644
index ab462751d60..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/node/PrometheusNodeFormatterInfo.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.prometheus.node;
-
-public interface PrometheusNodeFormatterInfo {
-  /** Category of prefix Solr Node dropwizard handler metric names */
-  enum NodeCategory {
-    ADMIN,
-    UPDATE,
-    CONTAINER,
-    CACHE
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/node/SolrNodeContainerMetric.java b/solr/core/src/java/org/apache/solr/metrics/prometheus/node/SolrNodeContainerMetric.java
deleted file mode 100644
index 2e3e16645c2..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/node/SolrNodeContainerMetric.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.prometheus.node;
-
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Metric;
-import org.apache.solr.metrics.prometheus.SolrMetric;
-import org.apache.solr.metrics.prometheus.SolrPrometheusFormatter;
-
-/* Dropwizard metrics of name CONTAINER.* */
-public class SolrNodeContainerMetric extends SolrNodeMetric {
-  public static final String NODE_CORES = "solr_metrics_node_cores";
-  public static final String NODE_CORE_FS_BYTES = "solr_metrics_node_core_root_fs_bytes";
-
-  public SolrNodeContainerMetric(Metric dropwizardMetric, String metricName) {
-    super(dropwizardMetric, metricName);
-  }
-
-  /*
-   * Metric examples being exported
-   * CONTAINER.fs.coreRoot.totalSpace
-   * CONTAINER.cores.loaded
-   */
-
-  @Override
-  public SolrMetric parseLabels() {
-    String[] parsedMetric = metricName.split("\\.");
-    labels.put("category", parsedMetric[0]);
-    return this;
-  }
-
-  @Override
-  public void toPrometheus(SolrPrometheusFormatter formatter) {
-    String[] parsedMetric = metricName.split("\\.");
-    if (metricName.startsWith("CONTAINER.cores.")) {
-      labels.put("item", parsedMetric[2]);
-      formatter.exportGauge(NODE_CORES, (Gauge<?>) dropwizardMetric, getLabels());
-    } else if (metricName.startsWith("CONTAINER.fs.coreRoot.")) {
-      labels.put("item", parsedMetric[3]);
-      formatter.exportGauge(NODE_CORE_FS_BYTES, (Gauge<?>) dropwizardMetric, getLabels());
-    }
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/node/SolrPrometheusNodeFormatter.java b/solr/core/src/java/org/apache/solr/metrics/prometheus/node/SolrPrometheusNodeFormatter.java
deleted file mode 100644
index eb542e05b77..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/node/SolrPrometheusNodeFormatter.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics.prometheus.node;
-
-import static org.apache.solr.metrics.prometheus.node.SolrNodeMetric.NODE_THREAD_POOL;
-
-import com.codahale.metrics.Counter;
-import com.codahale.metrics.Meter;
-import com.codahale.metrics.Metric;
-import com.google.common.base.Enums;
-import io.prometheus.metrics.model.snapshots.Labels;
-import org.apache.solr.metrics.prometheus.SolrMetric;
-import org.apache.solr.metrics.prometheus.SolrNoOpMetric;
-import org.apache.solr.metrics.prometheus.SolrPrometheusFormatter;
-
-/**
- * This class maintains a {@link io.prometheus.metrics.model.snapshots.MetricSnapshot}s exported
- * from solr.node {@link com.codahale.metrics.MetricRegistry}
- */
-public class SolrPrometheusNodeFormatter extends SolrPrometheusFormatter
-    implements PrometheusNodeFormatterInfo {
-  public SolrPrometheusNodeFormatter() {
-    super();
-  }
-
-  @Override
-  public void exportDropwizardMetric(Metric dropwizardMetric, String metricName) {
-    if (metricName.contains(".threadPool.")) {
-      exportThreadPoolMetric(dropwizardMetric, metricName);
-      return;
-    }
-
-    SolrMetric solrNodeMetric = categorizeMetric(dropwizardMetric, metricName);
-    solrNodeMetric.parseLabels().toPrometheus(this);
-  }
-
-  @Override
-  public SolrMetric categorizeMetric(Metric dropwizardMetric, String metricName) {
-    String metricCategory = metricName.split("\\.", 2)[0];
-    if (!Enums.getIfPresent(PrometheusNodeFormatterInfo.NodeCategory.class, metricCategory)
-        .isPresent()) {
-      return new SolrNoOpMetric();
-    }
-    switch (NodeCategory.valueOf(metricCategory)) {
-      case ADMIN:
-      case UPDATE:
-        return new SolrNodeHandlerMetric(dropwizardMetric, metricName);
-      case CONTAINER:
-        return new SolrNodeContainerMetric(dropwizardMetric, metricName);
-      default:
-        return new SolrNoOpMetric();
-    }
-  }
-
-  /*
-   * Metric examples being exported
-   * ADMIN./admin/cores.threadPool.parallelCoreAdminExecutor.completed
-   * CONTAINER.threadPool.coreContainerWorkExecutor.completed
-   */
-  private void exportThreadPoolMetric(Metric dropwizardMetric, String metricName) {
-    Labels labels;
-    String[] parsedMetric = metricName.split("\\.");
-    if (parsedMetric.length >= 5) {
-      labels =
-          Labels.of(
-              "category",
-              parsedMetric[0],
-              "handler",
-              parsedMetric[1],
-              "executer",
-              parsedMetric[3],
-              "task",
-              parsedMetric[parsedMetric.length - 1]);
-    } else {
-      labels =
-          Labels.of(
-              "category",
-              parsedMetric[0],
-              "executer",
-              parsedMetric[2],
-              "task",
-              parsedMetric[parsedMetric.length - 1]);
-    }
-    if (dropwizardMetric instanceof Counter) {
-      exportCounter(NODE_THREAD_POOL, (Counter) dropwizardMetric, labels);
-    } else if (dropwizardMetric instanceof Meter) {
-      exportMeter(NODE_THREAD_POOL, (Meter) dropwizardMetric, labels);
-    }
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/node/package-info.java b/solr/core/src/java/org/apache/solr/metrics/prometheus/node/package-info.java
deleted file mode 100644
index 64de64aa73c..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/node/package-info.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * The {@link org.apache.solr.metrics.prometheus.node.SolrPrometheusNodeFormatter} is responsible
- * for exporting solr.node registry metrics to Prometheus.
- */
-package org.apache.solr.metrics.prometheus.node;
diff --git a/solr/core/src/java/org/apache/solr/metrics/prometheus/package-info.java b/solr/core/src/java/org/apache/solr/metrics/prometheus/package-info.java
deleted file mode 100644
index 68f0c2a983e..00000000000
--- a/solr/core/src/java/org/apache/solr/metrics/prometheus/package-info.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * The {@link org.apache.solr.metrics.prometheus.SolrPrometheusFormatter} is responsible for
- * collecting Prometheus metrics from exporting {@link com.codahale.metrics.Metric}'s from {@link
- * com.codahale.metrics.MetricRegistry} {@link org.apache.solr.metrics.prometheus.SolrMetric} is a
- * wrapper to export {@link com.codahale.metrics.Metric} to {@link
- * io.prometheus.metrics.model.snapshots.DataPointSnapshot}
- */
-package org.apache.solr.metrics.prometheus;
diff --git a/solr/core/src/java/org/apache/solr/response/PrometheusResponseWriter.java b/solr/core/src/java/org/apache/solr/response/PrometheusResponseWriter.java
index 5919a0ec858..5a375bdb408 100644
--- a/solr/core/src/java/org/apache/solr/response/PrometheusResponseWriter.java
+++ b/solr/core/src/java/org/apache/solr/response/PrometheusResponseWriter.java
@@ -16,29 +16,12 @@
  */
 package org.apache.solr.response;
 
-import com.codahale.metrics.Histogram;
-import com.codahale.metrics.Metric;
-import com.codahale.metrics.MetricFilter;
-import com.codahale.metrics.MetricRegistry;
-import com.codahale.metrics.Timer;
 import io.prometheus.metrics.expositionformats.PrometheusTextFormatWriter;
+import io.prometheus.metrics.model.snapshots.MetricSnapshots;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.lang.invoke.MethodHandles;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-import org.apache.solr.common.SolrException;
-import org.apache.solr.common.util.NamedList;
-import org.apache.solr.metrics.AggregateMetric;
-import org.apache.solr.metrics.prometheus.SolrPrometheusFormatter;
-import org.apache.solr.metrics.prometheus.core.SolrPrometheusCoreFormatter;
-import org.apache.solr.metrics.prometheus.jetty.SolrPrometheusJettyFormatter;
-import org.apache.solr.metrics.prometheus.jvm.SolrPrometheusJvmFormatter;
-import org.apache.solr.metrics.prometheus.node.SolrPrometheusNodeFormatter;
 import org.apache.solr.request.SolrQueryRequest;
-import org.apache.solr.util.stats.MetricUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -57,90 +40,12 @@ public class PrometheusResponseWriter implements QueryResponseWriter {
   public void write(
       OutputStream out, SolrQueryRequest request, SolrQueryResponse response, String contentType)
       throws IOException {
-    NamedList<Object> prometheusRegistries =
-        (NamedList<Object>) response.getValues().get("metrics");
     var prometheusTextFormatWriter = new PrometheusTextFormatWriter(false);
-    for (Map.Entry<String, Object> prometheusRegistry : prometheusRegistries) {
-      var prometheusFormatter = (SolrPrometheusFormatter) prometheusRegistry.getValue();
-      prometheusTextFormatWriter.write(out, prometheusFormatter.collect());
-    }
+    prometheusTextFormatWriter.write(out, (MetricSnapshots) response.getValues().get("metrics"));
   }
 
   @Override
   public String getContentType(SolrQueryRequest request, SolrQueryResponse response) {
     return CONTENT_TYPE_PROMETHEUS;
   }
-
-  /**
-   * Provides a representation of the given Dropwizard metric registry as {@link
-   * SolrPrometheusCoreFormatter}-s. Only those metrics are converted which match at least one of
-   * the given MetricFilter instances.
-   *
-   * @param registry the {@link MetricRegistry} to be converted
-   * @param shouldMatchFilters a list of {@link MetricFilter} instances. A metric must match <em>any
-   *     one</em> of the filters from this list to be included in the output
-   * @param mustMatchFilter a {@link MetricFilter}. A metric <em>must</em> match this filter to be
-   *     included in the output.
-   * @param propertyFilter limit what properties of a metric are returned
-   * @param skipHistograms discard any {@link Histogram}-s and histogram parts of {@link Timer}-s.
-   * @param skipAggregateValues discard internal values of {@link AggregateMetric}-s.
-   * @param compact use compact representation for counters and gauges.
-   * @param consumer consumer that accepts produced {@link SolrPrometheusCoreFormatter}-s
-   */
-  public static void toPrometheus(
-      MetricRegistry registry,
-      String registryName,
-      List<MetricFilter> shouldMatchFilters,
-      MetricFilter mustMatchFilter,
-      Predicate<CharSequence> propertyFilter,
-      boolean skipHistograms,
-      boolean skipAggregateValues,
-      boolean compact,
-      Consumer<SolrPrometheusFormatter> consumer) {
-    Map<String, Metric> dropwizardMetrics = registry.getMetrics();
-    var formatter = getFormatterType(registryName);
-    if (formatter == null) {
-      return;
-    }
-
-    MetricUtils.toMaps(
-        registry,
-        shouldMatchFilters,
-        mustMatchFilter,
-        propertyFilter,
-        skipHistograms,
-        skipAggregateValues,
-        compact,
-        false,
-        (metricName, metric) -> {
-          try {
-            Metric dropwizardMetric = dropwizardMetrics.get(metricName);
-            formatter.exportDropwizardMetric(dropwizardMetric, metricName);
-          } catch (Exception e) {
-            throw new SolrException(
-                SolrException.ErrorCode.SERVER_ERROR,
-                "Error occurred exporting Dropwizard Metric to Prometheus",
-                e);
-          }
-        });
-
-    consumer.accept(formatter);
-  }
-
-  public static SolrPrometheusFormatter getFormatterType(String registryName) {
-    String[] parsedRegistry = registryName.split("\\.");
-
-    switch (parsedRegistry[1]) {
-      case "core":
-        return new SolrPrometheusCoreFormatter();
-      case "jvm":
-        return new SolrPrometheusJvmFormatter();
-      case "jetty":
-        return new SolrPrometheusJettyFormatter();
-      case "node":
-        return new SolrPrometheusNodeFormatter();
-      default:
-        return null;
-    }
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/search/CaffeineCache.java b/solr/core/src/java/org/apache/solr/search/CaffeineCache.java
index 091dd984fa5..8251cd0be96 100644
--- a/solr/core/src/java/org/apache/solr/search/CaffeineCache.java
+++ b/solr/core/src/java/org/apache/solr/search/CaffeineCache.java
@@ -24,6 +24,7 @@ import com.github.benmanes.caffeine.cache.RemovalCause;
 import com.github.benmanes.caffeine.cache.RemovalListener;
 import com.github.benmanes.caffeine.cache.stats.CacheStats;
 import com.google.common.annotations.VisibleForTesting;
+import io.opentelemetry.api.common.Attributes;
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.lang.invoke.MethodHandles;
@@ -473,8 +474,10 @@ public class CaffeineCache<K, V> extends SolrCacheBase
     return name() + (cacheMap != null ? cacheMap.getValue().toString() : "");
   }
 
+  // TODO SOLR-17458: Migrate to Otel
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
     solrMetricsContext = parentContext.getChildContext(this);
     cacheMap =
         new MetricsMap(
diff --git a/solr/core/src/java/org/apache/solr/search/QParserPlugin.java b/solr/core/src/java/org/apache/solr/search/QParserPlugin.java
index 4917a083d60..9dcca155e8d 100644
--- a/solr/core/src/java/org/apache/solr/search/QParserPlugin.java
+++ b/solr/core/src/java/org/apache/solr/search/QParserPlugin.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr.search;
 
+import io.opentelemetry.api.common.Attributes;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -122,7 +123,8 @@ public abstract class QParserPlugin implements NamedListInitializedPlugin, SolrI
   }
 
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
     // by default do nothing
   }
 
diff --git a/solr/core/src/java/org/apache/solr/search/SolrFieldCacheBean.java b/solr/core/src/java/org/apache/solr/search/SolrFieldCacheBean.java
index 8d0755f159d..265360bd7b9 100644
--- a/solr/core/src/java/org/apache/solr/search/SolrFieldCacheBean.java
+++ b/solr/core/src/java/org/apache/solr/search/SolrFieldCacheBean.java
@@ -17,6 +17,7 @@
 package org.apache.solr.search;
 
 import org.apache.solr.common.util.EnvUtils;
+import io.opentelemetry.api.common.Attributes;
 import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.metrics.MetricsMap;
 import org.apache.solr.metrics.SolrMetricsContext;
@@ -52,8 +53,10 @@ public class SolrFieldCacheBean implements SolrInfoBean {
     return solrMetricsContext;
   }
 
+  // TODO SOLR-17458: Migrate to Otel
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
     this.solrMetricsContext = parentContext;
     MetricsMap metricsMap =
         new MetricsMap(
diff --git a/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java b/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
index 5498d9202a8..e92b1001b79 100644
--- a/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
+++ b/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
@@ -19,6 +19,7 @@ package org.apache.solr.search;
 import static org.apache.solr.search.CpuAllowedLimit.TIMING_CONTEXT;
 
 import com.codahale.metrics.Gauge;
+import io.opentelemetry.api.common.Attributes;
 import java.io.Closeable;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
@@ -602,9 +603,13 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
     this.solrMetricsContext = core.getSolrMetricsContext().getChildContext(this);
     for (SolrCache<?, ?> cache : cacheList) {
       cache.initializeMetrics(
-          solrMetricsContext, SolrMetricManager.mkName(cache.name(), STATISTICS_KEY));
+          // TODO SOLR-17458: Add Otel
+          solrMetricsContext,
+          Attributes.empty(),
+          SolrMetricManager.mkName(cache.name(), STATISTICS_KEY));
     }
-    initializeMetrics(solrMetricsContext, STATISTICS_KEY);
+    // TODO SOLR-17458: Add Otel
+    initializeMetrics(solrMetricsContext, Attributes.empty(), STATISTICS_KEY);
     registerTime = new Date();
   }
 
@@ -2601,8 +2606,10 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
     return solrMetricsContext;
   }
 
+  // TODO SOLR-17458: Migrate to Otel
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
     parentContext.gauge(() -> name, true, "searcherName", Category.SEARCHER.toString(), scope);
     parentContext.gauge(() -> cachingEnabled, true, "caching", Category.SEARCHER.toString(), scope);
     parentContext.gauge(() -> openTime, true, "openedAt", Category.SEARCHER.toString(), scope);
diff --git a/solr/core/src/java/org/apache/solr/security/AuditLoggerPlugin.java b/solr/core/src/java/org/apache/solr/security/AuditLoggerPlugin.java
index a3a8a7c15ca..78dfdd4d572 100644
--- a/solr/core/src/java/org/apache/solr/security/AuditLoggerPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/AuditLoggerPlugin.java
@@ -22,6 +22,7 @@ import com.codahale.metrics.Timer;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.SerializationFeature;
+import io.opentelemetry.api.common.Attributes;
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.StringWriter;
@@ -260,8 +261,10 @@ public abstract class AuditLoggerPlugin implements Closeable, Runnable, SolrInfo
     this.formatter = formatter;
   }
 
+  // TODO SOLR-17458: Migrate to Otel
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, final String scope) {
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, final String scope) {
     solrMetricsContext = parentContext.getChildContext(this);
     String className = this.getClass().getSimpleName();
     log.debug("Initializing metrics for {}", className);
diff --git a/solr/core/src/java/org/apache/solr/security/AuthenticationPlugin.java b/solr/core/src/java/org/apache/solr/security/AuthenticationPlugin.java
index 5a847cef0c4..8d588aada46 100644
--- a/solr/core/src/java/org/apache/solr/security/AuthenticationPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/AuthenticationPlugin.java
@@ -19,6 +19,7 @@ package org.apache.solr.security;
 import com.codahale.metrics.Counter;
 import com.codahale.metrics.Meter;
 import com.codahale.metrics.Timer;
+import io.opentelemetry.api.common.Attributes;
 import jakarta.servlet.FilterChain;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletRequestWrapper;
@@ -141,8 +142,10 @@ public abstract class AuthenticationPlugin implements SolrInfoBean {
     return solrMetricsContext;
   }
 
+  // TODO SOLR-17458: Migrate to Otel
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
     this.solrMetricsContext = parentContext.getChildContext(this);
     // Metrics
     numErrors = this.solrMetricsContext.meter("errors", getCategory().toString(), scope);
diff --git a/solr/core/src/java/org/apache/solr/security/MultiAuthPlugin.java b/solr/core/src/java/org/apache/solr/security/MultiAuthPlugin.java
index f8a557c9e8d..903218eed56 100644
--- a/solr/core/src/java/org/apache/solr/security/MultiAuthPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/MultiAuthPlugin.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr.security;
 
+import io.opentelemetry.api.common.Attributes;
 import jakarta.servlet.FilterChain;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
@@ -193,10 +194,13 @@ public class MultiAuthPlugin extends AuthenticationPlugin
     }
   }
 
+  // TODO SOLR-17458: Migrate to Otel
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
     for (AuthenticationPlugin plugin : pluginMap.values()) {
-      plugin.initializeMetrics(parentContext, scope);
+      // TODO SOLR-17458: Add Otel
+      plugin.initializeMetrics(parentContext, Attributes.empty(), scope);
     }
   }
 
diff --git a/solr/core/src/java/org/apache/solr/security/MultiDestinationAuditLogger.java b/solr/core/src/java/org/apache/solr/security/MultiDestinationAuditLogger.java
index 5dc04422f78..d834d3b3c3b 100644
--- a/solr/core/src/java/org/apache/solr/security/MultiDestinationAuditLogger.java
+++ b/solr/core/src/java/org/apache/solr/security/MultiDestinationAuditLogger.java
@@ -18,6 +18,7 @@ package org.apache.solr.security;
 
 import static org.apache.solr.common.SolrException.ErrorCode.SERVER_ERROR;
 
+import io.opentelemetry.api.common.Attributes;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
@@ -129,9 +130,11 @@ public class MultiDestinationAuditLogger extends AuditLoggerPlugin implements Re
   }
 
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
-    super.initializeMetrics(parentContext, scope);
-    plugins.forEach(p -> p.initializeMetrics(solrMetricsContext, scope));
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
+    super.initializeMetrics(parentContext, attributes, scope);
+    // TODO SOLR-17458: Add Otel
+    plugins.forEach(p -> p.initializeMetrics(solrMetricsContext, Attributes.empty(), scope));
   }
 
   @Override
diff --git a/solr/core/src/java/org/apache/solr/update/DirectUpdateHandler2.java b/solr/core/src/java/org/apache/solr/update/DirectUpdateHandler2.java
index f990f3cf58f..a9e74fbcdb5 100644
--- a/solr/core/src/java/org/apache/solr/update/DirectUpdateHandler2.java
+++ b/solr/core/src/java/org/apache/solr/update/DirectUpdateHandler2.java
@@ -17,6 +17,7 @@
 package org.apache.solr.update;
 
 import com.codahale.metrics.Meter;
+import io.opentelemetry.api.common.Attributes;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.lang.reflect.Array;
@@ -205,8 +206,10 @@ public class DirectUpdateHandler2 extends UpdateHandler
     }
   }
 
+  // TODO SOLR-17458: Migrate to Otel
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
     if (core.getSolrConfig().getUpdateHandlerInfo().aggregateNodeLevelMetricsEnabled) {
       this.solrMetricsContext =
           new SolrDelegateRegistryMetricsContext(
diff --git a/solr/core/src/java/org/apache/solr/update/PeerSync.java b/solr/core/src/java/org/apache/solr/update/PeerSync.java
index a37edab2db3..0036c856a6b 100644
--- a/solr/core/src/java/org/apache/solr/update/PeerSync.java
+++ b/solr/core/src/java/org/apache/solr/update/PeerSync.java
@@ -24,6 +24,7 @@ import static org.apache.solr.update.processor.DistributingUpdateProcessorFactor
 import com.codahale.metrics.Counter;
 import com.codahale.metrics.Timer;
 import com.google.common.annotations.VisibleForTesting;
+import io.opentelemetry.api.common.Attributes;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.net.ConnectException;
@@ -143,8 +144,10 @@ public class PeerSync implements SolrMetricProducer {
     return solrMetricsContext;
   }
 
+  // TODO SOLR-17458: Migrate to Otel
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
     this.solrMetricsContext = parentContext.getChildContext(this);
     syncTime = solrMetricsContext.timer("time", scope, METRIC_SCOPE);
     syncErrors = solrMetricsContext.counter("errors", scope, METRIC_SCOPE);
diff --git a/solr/core/src/java/org/apache/solr/update/PeerSyncWithLeader.java b/solr/core/src/java/org/apache/solr/update/PeerSyncWithLeader.java
index 8dff6d15432..d6630dc46b5 100644
--- a/solr/core/src/java/org/apache/solr/update/PeerSyncWithLeader.java
+++ b/solr/core/src/java/org/apache/solr/update/PeerSyncWithLeader.java
@@ -24,6 +24,7 @@ import static org.apache.solr.update.PeerSync.percentile;
 
 import com.codahale.metrics.Counter;
 import com.codahale.metrics.Timer;
+import io.opentelemetry.api.common.Attributes;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.util.List;
@@ -98,8 +99,10 @@ public class PeerSyncWithLeader implements SolrMetricProducer {
     return solrMetricsContext;
   }
 
+  // TODO SOLR-17458: Migrate to Otel
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
     this.solrMetricsContext = parentContext.getChildContext(this);
     syncTime = solrMetricsContext.timer("time", scope, METRIC_SCOPE);
     syncErrors = solrMetricsContext.counter("errors", scope, METRIC_SCOPE);
diff --git a/solr/core/src/java/org/apache/solr/update/UpdateLog.java b/solr/core/src/java/org/apache/solr/update/UpdateLog.java
index cd7e1bbae3f..3d379c5d808 100644
--- a/solr/core/src/java/org/apache/solr/update/UpdateLog.java
+++ b/solr/core/src/java/org/apache/solr/update/UpdateLog.java
@@ -23,6 +23,7 @@ import com.carrotsearch.hppc.LongHashSet;
 import com.carrotsearch.hppc.LongSet;
 import com.codahale.metrics.Gauge;
 import com.codahale.metrics.Meter;
+import io.opentelemetry.api.common.Attributes;
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.UncheckedIOException;
@@ -626,8 +627,10 @@ public class UpdateLog implements PluginInfoInitialized, SolrMetricProducer {
     }
   }
 
+  // TODO SOLR-17458: Migrate to Otel
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
     solrMetricsContext = parentContext.getChildContext(this);
     bufferedOpsGauge =
         () -> {
diff --git a/solr/core/src/java/org/apache/solr/update/UpdateShardHandler.java b/solr/core/src/java/org/apache/solr/update/UpdateShardHandler.java
index e214c0cc3a8..157fc1513b0 100644
--- a/solr/core/src/java/org/apache/solr/update/UpdateShardHandler.java
+++ b/solr/core/src/java/org/apache/solr/update/UpdateShardHandler.java
@@ -17,6 +17,7 @@
 package org.apache.solr.update;
 
 import com.google.common.annotations.VisibleForTesting;
+import io.opentelemetry.api.common.Attributes;
 import java.lang.invoke.MethodHandles;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
@@ -162,11 +163,14 @@ public class UpdateShardHandler implements SolrInfoBean {
     return this.getClass().getName();
   }
 
+  // TODO SOLR-17458: Add Otel
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
     solrMetricsContext = parentContext.getChildContext(this);
     String expandedScope = SolrMetricManager.mkName(scope, getCategory().name());
-    trackHttpSolrMetrics.initializeMetrics(solrMetricsContext, expandedScope);
+    // TODO SOLR-17458: Add Otel
+    trackHttpSolrMetrics.initializeMetrics(solrMetricsContext, Attributes.empty(), expandedScope);
     updateExecutor =
         MetricUtils.instrumentedExecutorService(
             updateExecutor,
diff --git a/solr/core/src/java/org/apache/solr/util/stats/InstrumentedHttpListenerFactory.java b/solr/core/src/java/org/apache/solr/util/stats/InstrumentedHttpListenerFactory.java
index 6af4bccaad3..68436cfe76d 100644
--- a/solr/core/src/java/org/apache/solr/util/stats/InstrumentedHttpListenerFactory.java
+++ b/solr/core/src/java/org/apache/solr/util/stats/InstrumentedHttpListenerFactory.java
@@ -20,6 +20,7 @@ package org.apache.solr.util.stats;
 import static org.apache.solr.metrics.SolrMetricManager.mkName;
 
 import com.codahale.metrics.Timer;
+import io.opentelemetry.api.common.Attributes;
 import io.opentelemetry.api.trace.Span;
 import java.util.Locale;
 import java.util.Map;
@@ -124,8 +125,10 @@ public class InstrumentedHttpListenerFactory implements SolrMetricProducer, Http
     return solrMetricsContext.timer(nameStrategy.getNameFor(scope, request));
   }
 
+  // TODO SOLR-17458: Add Otel
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
     this.solrMetricsContext = parentContext;
     this.scope = scope;
   }
diff --git a/solr/core/src/java/org/apache/solr/util/stats/InstrumentedHttpRequestExecutor.java b/solr/core/src/java/org/apache/solr/util/stats/InstrumentedHttpRequestExecutor.java
new file mode 100644
index 00000000000..0473d9e0cee
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/util/stats/InstrumentedHttpRequestExecutor.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.util.stats;
+
+import static org.apache.solr.metrics.SolrMetricManager.mkName;
+
+import com.codahale.metrics.Timer;
+import io.opentelemetry.api.common.Attributes;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.Locale;
+import java.util.Map;
+import org.apache.http.HttpClientConnection;
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.RequestLine;
+import org.apache.http.client.methods.HttpRequestWrapper;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.HttpRequestExecutor;
+import org.apache.solr.common.util.CollectionUtil;
+import org.apache.solr.metrics.SolrMetricProducer;
+import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.util.tracing.TraceUtils;
+
+/**
+ * Sub-class of HttpRequestExecutor which tracks metrics interesting to solr Inspired and partially
+ * copied from dropwizard httpclient library
+ */
+public class InstrumentedHttpRequestExecutor extends HttpRequestExecutor
+    implements SolrMetricProducer {
+  public static final HttpClientMetricNameStrategy QUERYLESS_URL_AND_METHOD =
+      (scope, request) -> {
+        try {
+          final RequestLine requestLine = request.getRequestLine();
+          String schemeHostPort = null;
+          if (request instanceof HttpRequestWrapper wrapper) {
+            if (wrapper.getTarget() != null) {
+              schemeHostPort =
+                  wrapper.getTarget().getSchemeName()
+                      + "://"
+                      + wrapper.getTarget().getHostName()
+                      + ":"
+                      + wrapper.getTarget().getPort();
+            }
+          }
+          final URIBuilder url = new URIBuilder(requestLine.getUri());
+          return mkName(
+              (schemeHostPort != null ? schemeHostPort : "")
+                  + url.removeQuery().build().toString()
+                  + "."
+                  + methodNameString(request),
+              scope);
+        } catch (URISyntaxException e) {
+          throw new IllegalArgumentException(e);
+        }
+      };
+
+  public static final HttpClientMetricNameStrategy METHOD_ONLY =
+      (scope, request) -> mkName(methodNameString(request), scope);
+
+  public static final HttpClientMetricNameStrategy HOST_AND_METHOD =
+      (scope, request) -> {
+        try {
+          final RequestLine requestLine = request.getRequestLine();
+          String schemeHostPort = null;
+          if (request instanceof HttpRequestWrapper wrapper) {
+            if (wrapper.getTarget() != null) {
+              schemeHostPort =
+                  wrapper.getTarget().getSchemeName()
+                      + "://"
+                      + wrapper.getTarget().getHostName()
+                      + ":"
+                      + wrapper.getTarget().getPort();
+            }
+          }
+          final URIBuilder url = new URIBuilder(requestLine.getUri());
+          return mkName(
+              (schemeHostPort != null ? schemeHostPort : "") + "." + methodNameString(request),
+              scope);
+        } catch (URISyntaxException e) {
+          throw new IllegalArgumentException(e);
+        }
+      };
+
+  public static final Map<String, HttpClientMetricNameStrategy> KNOWN_METRIC_NAME_STRATEGIES =
+      CollectionUtil.newHashMap(3);
+
+  static {
+    KNOWN_METRIC_NAME_STRATEGIES.put("queryLessURLAndMethod", QUERYLESS_URL_AND_METHOD);
+    KNOWN_METRIC_NAME_STRATEGIES.put("hostAndMethod", HOST_AND_METHOD);
+    KNOWN_METRIC_NAME_STRATEGIES.put("methodOnly", METHOD_ONLY);
+  }
+
+  protected String scope;
+  protected HttpClientMetricNameStrategy nameStrategy;
+  protected SolrMetricsContext solrMetricsContext;
+
+  public InstrumentedHttpRequestExecutor(
+      int waitForContinue, HttpClientMetricNameStrategy nameStrategy) {
+    super(waitForContinue);
+    this.nameStrategy = nameStrategy;
+  }
+
+  public InstrumentedHttpRequestExecutor(HttpClientMetricNameStrategy nameStrategy) {
+    this.nameStrategy = nameStrategy;
+  }
+
+  private static String methodNameString(HttpRequest request) {
+    return request.getRequestLine().getMethod().toLowerCase(Locale.ROOT) + ".requests";
+  }
+
+  @Override
+  public SolrMetricsContext getSolrMetricsContext() {
+    return solrMetricsContext;
+  }
+
+  @Override
+  public HttpResponse execute(HttpRequest request, HttpClientConnection conn, HttpContext context)
+      throws IOException, HttpException {
+    Timer.Context timerContext = null;
+    if (solrMetricsContext != null) {
+      timerContext = timer(request).time();
+    }
+    TraceUtils.injectTraceContext(request);
+    try {
+      return super.execute(request, conn, context);
+    } finally {
+      if (timerContext != null) {
+        timerContext.stop();
+      }
+    }
+  }
+
+  private Timer timer(HttpRequest request) {
+    return solrMetricsContext.timer(null, nameStrategy.getNameFor(scope, request));
+  }
+
+  @Override
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
+    this.solrMetricsContext = parentContext.getChildContext(this);
+    this.scope = scope;
+  }
+}
diff --git a/solr/core/src/java/org/apache/solr/util/stats/InstrumentedPoolingHttpClientConnectionManager.java b/solr/core/src/java/org/apache/solr/util/stats/InstrumentedPoolingHttpClientConnectionManager.java
new file mode 100644
index 00000000000..8174613c72a
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/util/stats/InstrumentedPoolingHttpClientConnectionManager.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.util.stats;
+
+import io.opentelemetry.api.common.Attributes;
+import org.apache.http.config.Registry;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.metrics.SolrMetricProducer;
+import org.apache.solr.metrics.SolrMetricsContext;
+
+/**
+ * Sub-class of PoolingHttpClientConnectionManager which tracks metrics interesting to Solr.
+ * Inspired by dropwizard metrics-httpclient library implementation.
+ */
+public class InstrumentedPoolingHttpClientConnectionManager
+    extends PoolingHttpClientConnectionManager implements SolrMetricProducer {
+
+  private SolrMetricsContext solrMetricsContext;
+
+  public InstrumentedPoolingHttpClientConnectionManager(
+      Registry<ConnectionSocketFactory> socketFactoryRegistry) {
+    super(socketFactoryRegistry);
+  }
+
+  @Override
+  public SolrMetricsContext getSolrMetricsContext() {
+    return solrMetricsContext;
+  }
+
+  // TODO SOLR-17458: Migrate to Otel
+  @Override
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
+    this.solrMetricsContext = parentContext.getChildContext(this);
+    solrMetricsContext.gauge(
+        () -> getTotalStats().getAvailable(),
+        true,
+        SolrMetricManager.mkName("availableConnections", scope));
+    // this acquires a lock on the connection pool; remove if contention sucks
+    solrMetricsContext.gauge(
+        () -> getTotalStats().getLeased(),
+        true,
+        SolrMetricManager.mkName("leasedConnections", scope));
+    solrMetricsContext.gauge(
+        () -> getTotalStats().getMax(), true, SolrMetricManager.mkName("maxConnections", scope));
+    solrMetricsContext.gauge(
+        () -> getTotalStats().getPending(),
+        true,
+        SolrMetricManager.mkName("pendingConnections", scope));
+  }
+
+  @Override
+  public void close() {
+    super.close();
+    try {
+      SolrMetricProducer.super.close();
+    } catch (Exception e) {
+      throw new RuntimeException("Exception closing.", e);
+    }
+  }
+}
diff --git a/solr/core/src/java/org/apache/solr/util/stats/MetricUtils.java b/solr/core/src/java/org/apache/solr/util/stats/MetricUtils.java
index d0f37de403f..8b79fc26b35 100644
--- a/solr/core/src/java/org/apache/solr/util/stats/MetricUtils.java
+++ b/solr/core/src/java/org/apache/solr/util/stats/MetricUtils.java
@@ -27,6 +27,8 @@ import com.codahale.metrics.MetricRegistry;
 import com.codahale.metrics.Snapshot;
 import com.codahale.metrics.Timer;
 import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.common.AttributesBuilder;
 import io.opentelemetry.api.metrics.MeterProvider;
 import java.beans.BeanInfo;
 import java.beans.IntrospectionException;
@@ -53,6 +55,7 @@ import java.util.function.Predicate;
 import org.apache.solr.common.ConditionalKeyMapWriter;
 import org.apache.solr.common.IteratorWriter;
 import org.apache.solr.common.MapWriter;
+import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.SolrInfoBean;
@@ -858,4 +861,20 @@ public class MetricUtils {
   public static MeterProvider getMeterProvider() {
     return GlobalOpenTelemetry.getMeterProvider();
   }
+
+  public static Attributes createAttributes(String... attributes) {
+    if (attributes.length % 2 == 1) {
+      throw new SolrException(
+          SolrException.ErrorCode.SERVER_ERROR, "Odd number of field/value strings passed");
+    }
+
+    AttributesBuilder builder = Attributes.builder();
+    for (int i = 0; i < attributes.length; i += 2) {
+      String key = attributes[i];
+      String value = attributes[i + 1];
+      builder.put(key, value);
+    }
+
+    return builder.build();
+  }
 }
diff --git a/solr/core/src/test/org/apache/solr/SolrInfoBeanTest.java b/solr/core/src/test/org/apache/solr/SolrInfoBeanTest.java
index c09be4fccfd..e8e61bc5fe5 100644
--- a/solr/core/src/test/org/apache/solr/SolrInfoBeanTest.java
+++ b/solr/core/src/test/org/apache/solr/SolrInfoBeanTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr;
 
+import io.opentelemetry.api.common.Attributes;
 import java.net.URI;
 import java.net.URL;
 import java.nio.file.Files;
@@ -64,7 +65,8 @@ public class SolrInfoBeanTest extends SolrTestCaseJ4 {
       if (SolrInfoBean.class.isAssignableFrom(clazz)) {
         try {
           SolrInfoBean info = clazz.asSubclass(SolrInfoBean.class).getConstructor().newInstance();
-          info.initializeMetrics(solrMetricsContext, scope);
+          // TODO SOLR-17458: Fix test later
+          info.initializeMetrics(solrMetricsContext, Attributes.empty(), scope);
 
           // System.out.println( info.getClass() );
           assertNotNull(info.getClass().getCanonicalName(), info.getName());
diff --git a/solr/core/src/test/org/apache/solr/blockcache/BufferStoreTest.java b/solr/core/src/test/org/apache/solr/blockcache/BufferStoreTest.java
index 77d92378f1a..46bc29685ca 100644
--- a/solr/core/src/test/org/apache/solr/blockcache/BufferStoreTest.java
+++ b/solr/core/src/test/org/apache/solr/blockcache/BufferStoreTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr.blockcache;
 
+import io.opentelemetry.api.common.Attributes;
 import java.math.BigDecimal;
 import java.util.Map;
 import org.apache.lucene.tests.util.TestUtil;
@@ -42,7 +43,8 @@ public class BufferStoreTest extends SolrTestCase {
     String registry = TestUtil.randomSimpleString(random(), 2, 10);
     String scope = TestUtil.randomSimpleString(random(), 2, 10);
     SolrMetricsContext solrMetricsContext = new SolrMetricsContext(metricManager, registry, "foo");
-    metrics.initializeMetrics(solrMetricsContext, scope);
+    // TODO SOLR-17458: Fix test later
+    metrics.initializeMetrics(solrMetricsContext, Attributes.empty(), scope);
     metricsMap =
         (MetricsMap)
             ((SolrMetricManager.GaugeWrapper)
diff --git a/solr/core/src/test/org/apache/solr/handler/RequestHandlerBaseTest.java b/solr/core/src/test/org/apache/solr/handler/RequestHandlerBaseTest.java
index ba8fff85468..6b23c268556 100644
--- a/solr/core/src/test/org/apache/solr/handler/RequestHandlerBaseTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/RequestHandlerBaseTest.java
@@ -17,15 +17,23 @@
 
 package org.apache.solr.handler;
 
+import static org.apache.solr.metrics.SolrMetricProducer.TYPE_ATTR;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
 
 import com.codahale.metrics.Counter;
 import com.codahale.metrics.Meter;
 import com.codahale.metrics.Timer;
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.LongCounter;
+import io.opentelemetry.api.metrics.LongHistogram;
 import java.util.Map;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.SolrException;
@@ -44,8 +52,11 @@ import org.junit.Test;
 /** Unit tests for the metric and exception handling in {@link RequestHandlerBase} */
 public class RequestHandlerBaseTest extends SolrTestCaseJ4 {
 
+  public static final AttributeKey<String> SOURCE_ATTR = AttributeKey.stringKey("source");
   private SolrCore solrCore;
   private CoreContainer coreContainer;
+  private LongCounter mockLongCounter;
+  private LongHistogram mockLongHistogram;
 
   @BeforeClass
   public static void ensureWorkingMockito() {
@@ -56,6 +67,8 @@ public class RequestHandlerBaseTest extends SolrTestCaseJ4 {
   public void initMocks() {
     solrCore = mock(SolrCore.class);
     coreContainer = mock(CoreContainer.class);
+    mockLongCounter = mock(LongCounter.class);
+    mockLongHistogram = mock(LongHistogram.class);
   }
 
   @Test
@@ -65,9 +78,13 @@ public class RequestHandlerBaseTest extends SolrTestCaseJ4 {
 
     RequestHandlerBase.processErrorMetricsOnException(e, metrics);
 
-    verify(metrics.numErrors).mark();
-    verify(metrics.numServerErrors).mark();
-    verifyNoInteractions(metrics.numClientErrors);
+    verify(mockLongCounter, never())
+        .add(
+            eq(1L),
+            argThat(
+                attrs ->
+                    "errors".equals(attrs.get(TYPE_ATTR))
+                        && "source".equals(attrs.get(AttributeKey.stringKey("client")))));
   }
 
   @Test
@@ -77,9 +94,20 @@ public class RequestHandlerBaseTest extends SolrTestCaseJ4 {
 
     RequestHandlerBase.processErrorMetricsOnException(e, metrics);
 
-    verifyNoInteractions(metrics.numErrors);
-    verifyNoInteractions(metrics.numServerErrors);
-    verifyNoInteractions(metrics.numClientErrors);
+    verify(mockLongCounter, never())
+        .add(
+            eq(1L),
+            argThat(
+                attrs ->
+                    "errors".equals(attrs.get(TYPE_ATTR))
+                        && "client".equals(attrs.get(SOURCE_ATTR))));
+    verify(mockLongCounter, never())
+        .add(
+            eq(1L),
+            argThat(
+                attrs ->
+                    "errors".equals(attrs.get(TYPE_ATTR))
+                        && "server".equals(attrs.get(SOURCE_ATTR))));
   }
 
   @Test
@@ -89,9 +117,21 @@ public class RequestHandlerBaseTest extends SolrTestCaseJ4 {
 
     RequestHandlerBase.processErrorMetricsOnException(e, metrics);
 
-    verify(metrics.numErrors).mark();
-    verify(metrics.numClientErrors).mark();
-    verifyNoInteractions(metrics.numServerErrors);
+    verify(mockLongCounter, times(1))
+        .add(
+            eq(1L),
+            argThat(
+                attrs ->
+                    "errors".equals(attrs.get(TYPE_ATTR))
+                        && "client".equals(attrs.get(SOURCE_ATTR))));
+
+    verify(mockLongCounter, never())
+        .add(
+            eq(1L),
+            argThat(
+                attrs ->
+                    "errors".equals(attrs.get(TYPE_ATTR))
+                        && "server".equals(attrs.get(SOURCE_ATTR))));
   }
 
   @Test
@@ -171,10 +211,17 @@ public class RequestHandlerBaseTest extends SolrTestCaseJ4 {
   //  requires a MetricsManager, which requires ...
   private RequestHandlerBase.HandlerMetrics createHandlerMetrics() {
     final SolrMetricsContext metricsContext = mock(SolrMetricsContext.class);
+
     when(metricsContext.timer(any(), any())).thenReturn(mock(Timer.class));
     when(metricsContext.meter(any(), any())).then(invocation -> mock(Meter.class));
     when(metricsContext.counter(any(), any())).thenReturn(mock(Counter.class));
 
-    return new RequestHandlerBase.HandlerMetrics(metricsContext, "someBaseMetricPath");
+    when(metricsContext.longCounter(any(), any())).thenReturn(mockLongCounter);
+    when(metricsContext.longHistogram(any(), any())).thenReturn(mockLongHistogram);
+
+    return new RequestHandlerBase.HandlerMetrics(
+        metricsContext,
+        Attributes.of(AttributeKey.stringKey("scope"), "someBaseMetricPath"),
+        "someBaseMetricPath");
   }
 }
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/MBeansHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/admin/MBeansHandlerTest.java
index 05232b7f05e..e7395188bb0 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/MBeansHandlerTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/MBeansHandlerTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr.handler.admin;
 
+import io.opentelemetry.api.common.Attributes;
 import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -171,7 +172,8 @@ public class MBeansHandlerTest extends SolrTestCaseJ4 {
           }
 
           @Override
-          public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
+          public void initializeMetrics(
+              SolrMetricsContext parentContext, Attributes attributes, String scope) {
             this.solrMetricsContext = parentContext.getChildContext(this);
           }
 
@@ -183,6 +185,7 @@ public class MBeansHandlerTest extends SolrTestCaseJ4 {
     bean.initializeMetrics(
         new SolrMetricsContext(
             h.getCoreContainer().getMetricManager(), "testMetricsSnapshot", "foobar"),
+        Attributes.empty(),
         "foo");
     runSnapshots = true;
     Thread modifier =
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 5e17ca9cf8e..96001e15b63 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
@@ -18,8 +18,7 @@
 package org.apache.solr.handler.admin;
 
 import com.codahale.metrics.Counter;
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.SettableGauge;
+import io.opentelemetry.api.common.Attributes;
 import io.prometheus.metrics.model.snapshots.CounterSnapshot;
 import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
 import io.prometheus.metrics.model.snapshots.Labels;
@@ -37,12 +36,9 @@ import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.core.PluginBag;
 import org.apache.solr.core.PluginInfo;
-import org.apache.solr.core.SolrCore;
 import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.metrics.MetricsMap;
-import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.metrics.SolrMetricsContext;
-import org.apache.solr.metrics.prometheus.SolrPrometheusFormatter;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestHandler;
 import org.apache.solr.response.SolrQueryResponse;
@@ -69,15 +65,6 @@ public class MetricsHandlerTest extends SolrTestCaseJ4 {
     c = h.getCoreContainer().getMetricManager().counter(null, "solr.jetty", "solrtest_foo:bar");
     c.inc(3);
 
-    // Manually register for Prometheus Formatter tests
-    registerGauge("solr.jvm", "gc.G1-Old-Generation.count");
-    registerGauge("solr.jvm", "gc.G1-Old-Generation.time");
-    registerGauge("solr.jvm", "memory.heap.committed");
-    registerGauge("solr.jvm", "memory.pools.CodeHeap-'non-nmethods'.committed");
-    registerGauge("solr.jvm", "threads.count");
-    registerGauge("solr.jvm", "os.availableProcessors");
-    registerGauge("solr.jvm", "buffers.direct.Count");
-    registerGauge("solr.jvm", "buffers.direct.MemoryUsed");
     h.getCoreContainer()
         .getMetricManager()
         .meter(null, "solr.jetty", "org.eclipse.jetty.server.handler.DefaultHandler.2xx-responses");
@@ -706,439 +693,6 @@ public class MetricsHandlerTest extends SolrTestCaseJ4 {
     assertTrue(map.toString(), map.containsKey("count"));
   }
 
-  @Test
-  @SuppressWarnings("unchecked")
-  public void testPrometheusMetricsCore() throws Exception {
-    MetricsHandler handler = new MetricsHandler(h.getCoreContainer());
-
-    SolrQueryResponse resp = new SolrQueryResponse();
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            MetricsHandler.COMPACT_PARAM,
-            "false",
-            CommonParams.WT,
-            "prometheus"),
-        resp);
-
-    NamedList<?> values = resp.getValues();
-    assertNotNull(values.get("metrics"));
-    values = (NamedList<?>) values.get("metrics");
-    SolrPrometheusFormatter formatter = (SolrPrometheusFormatter) values.get("solr.core");
-    assertNotNull(formatter);
-    MetricSnapshots actualSnapshots = formatter.collect();
-    assertNotNull(actualSnapshots);
-
-    MetricSnapshot actualSnapshot =
-        getMetricSnapshot(actualSnapshots, "solr_metrics_core_request_time_ms");
-    SummarySnapshot.SummaryDataPointSnapshot actualSummaryDataPoint =
-        getSummaryDataPointSnapshot(
-            actualSnapshot,
-            Labels.of("category", "QUERY", "core", "collection1", "handler", "/select[shard]"));
-    assertEquals(0, (float) actualSummaryDataPoint.getCount(), 0);
-
-    actualSnapshot = getMetricSnapshot(actualSnapshots, "solr_metrics_core_requests");
-    CounterSnapshot.CounterDataPointSnapshot actualCounterDataPoint =
-        getCounterDatapointSnapshot(
-            actualSnapshot,
-            Labels.of(
-                "category",
-                "QUERY",
-                "core",
-                "collection1",
-                "handler",
-                "/select[shard]",
-                "type",
-                "requests"));
-    assertEquals(0, actualCounterDataPoint.getValue(), 0);
-
-    actualSnapshot = getMetricSnapshot(actualSnapshots, "solr_metrics_core_cache");
-    GaugeSnapshot.GaugeDataPointSnapshot actualGaugeDataPoint =
-        getGaugeDatapointSnapshot(
-            actualSnapshot,
-            Labels.of("cacheType", "fieldValueCache", "core", "collection1", "item", "hits"));
-    assertEquals(0, actualGaugeDataPoint.getValue(), 0);
-
-    actualSnapshot = getMetricSnapshot(actualSnapshots, "solr_metrics_core_highlighter_requests");
-    actualCounterDataPoint =
-        getCounterDatapointSnapshot(
-            actualSnapshot,
-            Labels.of("item", "default", "core", "collection1", "type", "SolrFragmenter"));
-    assertEquals(0, actualCounterDataPoint.getValue(), 0);
-
-    actualSnapshot = getMetricSnapshot(actualSnapshots, "solr_metrics_core_searcher_documents");
-    actualGaugeDataPoint =
-        getGaugeDatapointSnapshot(
-            actualSnapshot, Labels.of("core", "collection1", "type", "numDocs"));
-    assertEquals(0, actualGaugeDataPoint.getValue(), 0);
-
-    actualSnapshot = getMetricSnapshot(actualSnapshots, "solr_metrics_core_update_handler");
-    actualGaugeDataPoint =
-        getGaugeDatapointSnapshot(
-            actualSnapshot,
-            Labels.of(
-                "category",
-                "UPDATE",
-                "core",
-                "collection1",
-                "type",
-                "adds",
-                "handler",
-                "updateHandler"));
-    assertEquals(0, actualGaugeDataPoint.getValue(), 0);
-
-    actualSnapshot =
-        getMetricSnapshot(actualSnapshots, "solr_metrics_core_searcher_warmup_time_ms");
-    actualSummaryDataPoint =
-        getSummaryDataPointSnapshot(
-            actualSnapshot, Labels.of("core", "collection1", "type", "warmup"));
-    assertEquals(0, (float) actualSummaryDataPoint.getCount(), 0);
-
-    handler.close();
-  }
-
-  @Test
-  @SuppressWarnings("unchecked")
-  public void testPrometheusMetricsNode() throws Exception {
-    MetricsHandler handler = new MetricsHandler(h.getCoreContainer());
-
-    SolrQueryResponse resp = new SolrQueryResponse();
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            MetricsHandler.COMPACT_PARAM,
-            "false",
-            CommonParams.WT,
-            "prometheus"),
-        resp);
-
-    NamedList<?> values = resp.getValues();
-    assertNotNull(values.get("metrics"));
-    values = (NamedList<?>) values.get("metrics");
-    SolrPrometheusFormatter formatter = (SolrPrometheusFormatter) values.get("solr.node");
-    assertNotNull(formatter);
-    MetricSnapshots actualSnapshots = formatter.collect();
-    assertNotNull(actualSnapshots);
-
-    MetricSnapshot actualSnapshot =
-        getMetricSnapshot(actualSnapshots, "solr_metrics_node_requests");
-
-    CounterSnapshot.CounterDataPointSnapshot actualCounterDataPoint =
-        getCounterDatapointSnapshot(
-            actualSnapshot,
-            Labels.of("category", "ADMIN", "handler", "/admin/info", "type", "requests"));
-    assertEquals(0, actualCounterDataPoint.getValue(), 0);
-
-    actualSnapshot = getMetricSnapshot(actualSnapshots, "solr_metrics_node_requests_time");
-    actualCounterDataPoint =
-        getCounterDatapointSnapshot(
-            actualSnapshot, Labels.of("category", "ADMIN", "handler", "/admin/info"));
-    assertEquals(0, actualCounterDataPoint.getValue(), 0);
-
-    actualSnapshot = getMetricSnapshot(actualSnapshots, "solr_metrics_node_thread_pool");
-    actualCounterDataPoint =
-        getCounterDatapointSnapshot(
-            actualSnapshot,
-            Labels.of(
-                "category",
-                "ADMIN",
-                "executer",
-                "parallelCoreAdminExecutor",
-                "handler",
-                "/admin/cores",
-                "task",
-                "completed"));
-    assertEquals(0, actualCounterDataPoint.getValue(), 0);
-
-    actualSnapshot = getMetricSnapshot(actualSnapshots, "solr_metrics_node_core_root_fs_bytes");
-    var actualGaugeDataPoint =
-        getGaugeDatapointSnapshot(
-            actualSnapshot, Labels.of("category", "CONTAINER", "item", "totalSpace"));
-    assertNotNull(actualGaugeDataPoint);
-
-    actualSnapshot = getMetricSnapshot(actualSnapshots, "solr_metrics_node_cores");
-    actualGaugeDataPoint =
-        getGaugeDatapointSnapshot(
-            actualSnapshot, Labels.of("category", "CONTAINER", "item", "lazy"));
-    assertEquals(0, actualGaugeDataPoint.getValue(), 0);
-
-    handler.close();
-  }
-
-  @Test
-  @SuppressWarnings("unchecked")
-  public void testPrometheusMetricsJvm() throws Exception {
-    // Some JVM metrics are non-deterministic due to testing environment such as
-    // availableProcessors. We confirm snapshot exists and is nonNull instead.
-    MetricsHandler handler = new MetricsHandler(h.getCoreContainer());
-
-    SolrQueryResponse resp = new SolrQueryResponse();
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            MetricsHandler.COMPACT_PARAM,
-            "false",
-            CommonParams.WT,
-            "prometheus"),
-        resp);
-
-    NamedList<?> values = resp.getValues();
-    assertNotNull(values.get("metrics"));
-    values = (NamedList<?>) values.get("metrics");
-    SolrPrometheusFormatter formatter = (SolrPrometheusFormatter) values.get("solr.jvm");
-    assertNotNull(formatter);
-    MetricSnapshots actualSnapshots = formatter.collect();
-    assertNotNull(actualSnapshots);
-
-    MetricSnapshot actualSnapshot = getMetricSnapshot(actualSnapshots, "solr_metrics_jvm_threads");
-    GaugeSnapshot.GaugeDataPointSnapshot actualGaugeDataPoint =
-        getGaugeDatapointSnapshot(actualSnapshot, Labels.of("item", "count"));
-    assertNotNull(actualGaugeDataPoint);
-
-    actualSnapshot = getMetricSnapshot(actualSnapshots, "solr_metrics_jvm_memory_pools_bytes");
-    actualGaugeDataPoint =
-        getGaugeDatapointSnapshot(
-            actualSnapshot, Labels.of("item", "committed", "space", "CodeHeap-'non-nmethods'"));
-    assertNotNull(actualGaugeDataPoint);
-
-    actualSnapshot = getMetricSnapshot(actualSnapshots, "solr_metrics_jvm_buffers");
-    actualGaugeDataPoint =
-        getGaugeDatapointSnapshot(actualSnapshot, Labels.of("item", "Count", "pool", "direct"));
-    assertNotNull(actualGaugeDataPoint);
-
-    actualSnapshot = getMetricSnapshot(actualSnapshots, "solr_metrics_jvm_heap");
-    actualGaugeDataPoint =
-        getGaugeDatapointSnapshot(actualSnapshot, Labels.of("item", "committed", "memory", "heap"));
-    assertNotNull(actualGaugeDataPoint);
-
-    actualSnapshot = getMetricSnapshot(actualSnapshots, "solr_metrics_jvm_buffers_bytes");
-    actualGaugeDataPoint =
-        getGaugeDatapointSnapshot(
-            actualSnapshot, Labels.of("item", "MemoryUsed", "pool", "direct"));
-    assertNotNull(actualGaugeDataPoint);
-
-    handler.close();
-  }
-
-  @Test
-  @SuppressWarnings("unchecked")
-  public void testPrometheusMetricsJetty() throws Exception {
-    MetricsHandler handler = new MetricsHandler(h.getCoreContainer());
-
-    SolrQueryResponse resp = new SolrQueryResponse();
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            MetricsHandler.COMPACT_PARAM,
-            "false",
-            CommonParams.WT,
-            "prometheus"),
-        resp);
-
-    NamedList<?> values = resp.getValues();
-    assertNotNull(values.get("metrics"));
-    values = (NamedList<?>) values.get("metrics");
-    SolrPrometheusFormatter formatter = (SolrPrometheusFormatter) values.get("solr.jetty");
-    assertNotNull(formatter);
-    MetricSnapshots actualSnapshots = formatter.collect();
-    assertNotNull(actualSnapshots);
-
-    MetricSnapshot actualSnapshot =
-        getMetricSnapshot(actualSnapshots, "solr_metrics_jetty_response");
-    CounterSnapshot.CounterDataPointSnapshot actualCounterDatapoint =
-        getCounterDatapointSnapshot(actualSnapshot, Labels.of("status", "2xx"));
-    assertEquals(0, actualCounterDatapoint.getValue(), 0);
-
-    actualSnapshot = getMetricSnapshot(actualSnapshots, "solr_metrics_jetty_requests");
-    actualCounterDatapoint =
-        getCounterDatapointSnapshot(actualSnapshot, Labels.of("method", "active"));
-    assertEquals(0, actualCounterDatapoint.getValue(), 0);
-
-    actualSnapshot = getMetricSnapshot(actualSnapshots, "solr_metrics_jetty_dispatches");
-    actualCounterDatapoint = getCounterDatapointSnapshot(actualSnapshot, Labels.of());
-    assertEquals(0, actualCounterDatapoint.getValue(), 0);
-
-    handler.close();
-  }
-
-  @Test
-  @SuppressWarnings("unchecked")
-  public void testPrometheusMetricsFilter() throws Exception {
-    MetricsHandler handler = new MetricsHandler(h.getCoreContainer());
-
-    SolrQueryResponse resp = new SolrQueryResponse();
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            CommonParams.WT,
-            "prometheus",
-            "group",
-            "core",
-            "type",
-            "counter",
-            "prefix",
-            "QUERY"),
-        resp);
-
-    NamedList<?> values = resp.getValues();
-    assertNotNull(values.get("metrics"));
-    values = (NamedList<?>) values.get("metrics");
-    SolrPrometheusFormatter formatter = (SolrPrometheusFormatter) values.get("solr.core");
-    assertNotNull(formatter);
-    MetricSnapshots actualSnapshots = formatter.collect();
-    assertNotNull(actualSnapshots);
-
-    actualSnapshots.forEach(
-        (k) -> {
-          k.getDataPoints()
-              .forEach(
-                  (datapoint) -> {
-                    assertTrue(datapoint instanceof CounterSnapshot.CounterDataPointSnapshot);
-                    assertEquals("QUERY", datapoint.getLabels().get("category"));
-                  });
-        });
-
-    handler.close();
-  }
-
-  @Test
-  public void testMetricsUnload() throws Exception {
-
-    SolrCore core = h.getCoreContainer().getCore("collection1");
-    // .getRequestHandlers().put("/dumphandler", new DumpRequestHandler());
-    RefreshablePluginHolder pluginHolder = null;
-    try {
-      PluginInfo info =
-          new PluginInfo(
-              SolrRequestHandler.TYPE,
-              Map.of("name", "/dumphandler", "class", DumpRequestHandler.class.getName()));
-      DumpRequestHandler requestHandler = new DumpRequestHandler();
-      requestHandler.gaugevals = Map.of("d_k1", "v1", "d_k2", "v2");
-      pluginHolder = new RefreshablePluginHolder(info, requestHandler);
-      core.getRequestHandlers().put("/dumphandler", pluginHolder);
-
-    } finally {
-      core.close();
-    }
-
-    MetricsHandler handler = new MetricsHandler(h.getCoreContainer());
-
-    SolrQueryResponse resp = new SolrQueryResponse();
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            CommonParams.WT,
-            "json",
-            MetricsHandler.COMPACT_PARAM,
-            "true",
-            "key",
-            "solr.core.collection1:QUERY./dumphandler.dumphandlergauge"),
-        resp);
-
-    assertEquals(
-        "v1",
-        resp.getValues()
-            ._getStr(
-                Arrays.asList(
-                    "metrics", "solr.core.collection1:QUERY./dumphandler.dumphandlergauge", "d_k1"),
-                null));
-    assertEquals(
-        "v2",
-        resp.getValues()
-            ._getStr(
-                Arrays.asList(
-                    "metrics", "solr.core.collection1:QUERY./dumphandler.dumphandlergauge", "d_k2"),
-                null));
-    pluginHolder.closeHandler();
-    resp = new SolrQueryResponse();
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            CommonParams.WT,
-            "json",
-            MetricsHandler.COMPACT_PARAM,
-            "true",
-            "key",
-            "solr.core.collection1:QUERY./dumphandler.dumphandlergauge"),
-        resp);
-
-    assertNull(
-        resp.getValues()
-            ._getStr(
-                Arrays.asList(
-                    "metrics", "solr.core.collection1:QUERY./dumphandler.dumphandlergauge", "d_k1"),
-                null));
-    assertNull(
-        resp.getValues()
-            ._getStr(
-                Arrays.asList(
-                    "metrics", "solr.core.collection1:QUERY./dumphandler.dumphandlergauge", "d_k2"),
-                null));
-
-    DumpRequestHandler requestHandler = new DumpRequestHandler();
-    requestHandler.gaugevals = Map.of("d_k1", "v1.1", "d_k2", "v2.1");
-    pluginHolder.reset(requestHandler);
-    resp = new SolrQueryResponse();
-    handler.handleRequestBody(
-        req(
-            CommonParams.QT,
-            "/admin/metrics",
-            CommonParams.WT,
-            "json",
-            MetricsHandler.COMPACT_PARAM,
-            "true",
-            "key",
-            "solr.core.collection1:QUERY./dumphandler.dumphandlergauge"),
-        resp);
-
-    assertEquals(
-        "v1.1",
-        resp.getValues()
-            ._getStr(
-                Arrays.asList(
-                    "metrics", "solr.core.collection1:QUERY./dumphandler.dumphandlergauge", "d_k1"),
-                null));
-    assertEquals(
-        "v2.1",
-        resp.getValues()
-            ._getStr(
-                Arrays.asList(
-                    "metrics", "solr.core.collection1:QUERY./dumphandler.dumphandlergauge", "d_k2"),
-                null));
-
-    handler.close();
-  }
-
-  public static void registerGauge(String registry, String metricName) {
-    Gauge<Number> metric =
-        new SettableGauge<>() {
-          @Override
-          public void setValue(Number value) {}
-
-          @Override
-          public Number getValue() {
-            return 0;
-          }
-        };
-    h.getCoreContainer()
-        .getMetricManager()
-        .registerGauge(
-            null,
-            registry,
-            metric,
-            "",
-            SolrMetricManager.ResolutionStrategy.IGNORE,
-            metricName,
-            "");
-  }
-
   private MetricSnapshot getMetricSnapshot(MetricSnapshots snapshots, String metricName) {
     return snapshots.stream()
         .filter(ss -> ss.getMetadata().getPrometheusName().equals(metricName))
@@ -1199,7 +753,8 @@ public class MetricsHandlerTest extends SolrTestCaseJ4 {
 
     void reset(DumpRequestHandler rh) {
       this.rh = rh;
-      if (metricsInfo != null) this.rh.initializeMetrics(metricsInfo, "/dumphandler");
+      if (metricsInfo != null)
+        this.rh.initializeMetrics(metricsInfo, Attributes.empty(), "/dumphandler");
     }
 
     @Override
@@ -1224,8 +779,9 @@ public class MetricsHandlerTest extends SolrTestCaseJ4 {
     }
 
     @Override
-    public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
-      super.initializeMetrics(parentContext, scope);
+    public void initializeMetrics(
+        SolrMetricsContext parentContext, Attributes attributes, String scope) {
+      super.initializeMetrics(parentContext, attributes, scope);
       MetricsMap metrics = new MetricsMap(map -> gaugevals.forEach((k, v) -> map.putNoEx(k, v)));
       solrMetricsContext.gauge(metrics, true, "dumphandlergauge", getCategory().toString(), scope);
     }
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrCoreMetricTest.java b/solr/core/src/test/org/apache/solr/metrics/SolrCoreMetricTest.java
deleted file mode 100644
index 9ee34056e5e..00000000000
--- a/solr/core/src/test/org/apache/solr/metrics/SolrCoreMetricTest.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.metrics;
-
-import com.codahale.metrics.Metric;
-import io.prometheus.metrics.model.snapshots.Labels;
-import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.metrics.prometheus.SolrPrometheusFormatter;
-import org.apache.solr.metrics.prometheus.core.SolrCoreMetric;
-import org.junit.Test;
-
-public class SolrCoreMetricTest extends SolrTestCaseJ4 {
-
-  @Test
-  public void testStandaloneDefaultLabels() {
-    String dropwizardMetricName = "core_Core_Name.test_core_metric";
-
-    String expectedCoreName = "Core_Name";
-    String expectedMetricName = "test_core_metric";
-    Labels expectedLabels = Labels.of("core", expectedCoreName);
-    TestSolrCoreMetric testSolrCoreMetric = new TestSolrCoreMetric(null, dropwizardMetricName);
-
-    assertEquals(expectedCoreName, testSolrCoreMetric.coreName);
-    assertEquals(expectedMetricName, testSolrCoreMetric.metricName);
-    assertEquals(expectedLabels, testSolrCoreMetric.getLabels());
-  }
-
-  @Test
-  public void testCloudDefaultLabels() {
-    String dropwizardMetricName = "core_Collection_Name_shard1_replica_n1.test_core_metric";
-
-    String expectedCoreName = "core_Collection_Name_shard1_replica_n1";
-    String expectedMetricName = "test_core_metric";
-    String expectedCollectionName = "Collection_Name";
-    String expectedShardName = "shard1";
-    String expectedReplicaName = "replica_n1";
-
-    Labels expectedLabels =
-        Labels.of(
-            "core",
-            expectedCoreName,
-            "collection",
-            expectedCollectionName,
-            "shard",
-            expectedShardName,
-            "replica",
-            expectedReplicaName);
-    TestSolrCoreMetric testSolrCoreMetric = new TestSolrCoreMetric(null, dropwizardMetricName);
-
-    assertEquals(expectedCoreName, testSolrCoreMetric.coreName);
-    assertEquals(expectedMetricName, testSolrCoreMetric.metricName);
-    assertEquals(expectedLabels, testSolrCoreMetric.getLabels());
-  }
-
-  static class TestSolrCoreMetric extends SolrCoreMetric {
-    public TestSolrCoreMetric(Metric dropwizardMetric, String metricName) {
-      super(dropwizardMetric, metricName);
-    }
-
-    @Override
-    public SolrCoreMetric parseLabels() {
-      return null;
-    }
-
-    @Override
-    public void toPrometheus(SolrPrometheusFormatter formatter) {}
-  }
-}
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java b/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java
index e1cd960eb15..d22e520d207 100644
--- a/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java
@@ -17,6 +17,7 @@
 package org.apache.solr.metrics;
 
 import com.codahale.metrics.Counter;
+import io.opentelemetry.api.common.Attributes;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
@@ -88,7 +89,8 @@ public final class SolrMetricTestUtils {
       SolrMetricsContext solrMetricsContext;
 
       @Override
-      public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
+      public void initializeMetrics(
+          SolrMetricsContext parentContext, Attributes attributes, String scope) {
         this.solrMetricsContext = parentContext.getChildContext(this);
         if (category == null) {
           throw new IllegalArgumentException("null category");
diff --git a/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriter.java b/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriter.java
index f52b76d5706..4792be1021c 100644
--- a/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriter.java
+++ b/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriter.java
@@ -114,6 +114,10 @@ public class TestPrometheusResponseWriter extends SolrTestCaseJ4 {
             } else {
               actualValue = actualMetric.split(" ")[1];
             }
+            if (actualMetric.startsWith("target_info")) {
+              // Skip standard OTEL metric
+              return;
+            }
             assertTrue(
                 "All metrics should start with 'solr_metrics_'",
                 actualMetric.startsWith("solr_metrics_"));
@@ -127,54 +131,6 @@ public class TestPrometheusResponseWriter extends SolrTestCaseJ4 {
     }
   }
 
-  public void testPrometheusDummyOutput() throws Exception {
-    String expectedCore =
-        "solr_metrics_core_requests_total{category=\"QUERY\",core=\"collection1\",handler=\"/dummy/metrics\",type=\"requests\"} 10.0";
-    String expectedNode =
-        "solr_metrics_node_requests_total{category=\"ADMIN\",handler=\"/dummy/metrics\",type=\"requests\"} 20.0";
-    String expectedJetty = "solr_metrics_jetty_response_total{status=\"2xx\"} 30.0";
-    String expectedJvm = "solr_metrics_jvm_gc{item=\"dummyMetrics\"} 0.0";
-
-    ModifiableSolrParams params = new ModifiableSolrParams();
-    params.set("wt", "prometheus");
-    var req = new GenericSolrRequest(METHOD.GET, "/admin/metrics", SolrRequestType.ADMIN, params);
-    req.setResponseParser(new NoOpResponseParser("prometheus"));
-
-    try (SolrClient adminClient = getHttpSolrClient(solrClientTestRule.getBaseUrl())) {
-      NamedList<Object> res = adminClient.request(req);
-      assertNotNull("null response from server", res);
-      String output = (String) res.get("response");
-      assertEquals(
-          expectedCore,
-          output
-              .lines()
-              .filter(line -> line.contains(expectedCore))
-              .collect(Collectors.toList())
-              .get(0));
-      assertEquals(
-          expectedNode,
-          output
-              .lines()
-              .filter(line -> line.contains(expectedNode))
-              .collect(Collectors.toList())
-              .get(0));
-      assertEquals(
-          expectedJetty,
-          output
-              .lines()
-              .filter(line -> line.contains(expectedJetty))
-              .collect(Collectors.toList())
-              .get(0));
-      assertEquals(
-          expectedJvm,
-          output
-              .lines()
-              .filter(line -> line.contains(expectedJvm))
-              .collect(Collectors.toList())
-              .get(0));
-    }
-  }
-
   private static void registerGauge(
       SolrMetricManager metricManager, String registry, String metricName) {
     Gauge<Number> metric =
diff --git a/solr/core/src/test/org/apache/solr/search/TestCaffeineCache.java b/solr/core/src/test/org/apache/solr/search/TestCaffeineCache.java
index b4c69600fbd..d70ea43a468 100644
--- a/solr/core/src/test/org/apache/solr/search/TestCaffeineCache.java
+++ b/solr/core/src/test/org/apache/solr/search/TestCaffeineCache.java
@@ -19,6 +19,7 @@ package org.apache.solr.search;
 import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.Caffeine;
 import com.github.benmanes.caffeine.cache.RemovalCause;
+import io.opentelemetry.api.common.Attributes;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -47,10 +48,12 @@ public class TestCaffeineCache extends SolrTestCase {
   public void testSimple() {
     CaffeineCache<Integer, String> lfuCache = new CaffeineCache<>();
     SolrMetricsContext solrMetricsContext = new SolrMetricsContext(metricManager, registry, "foo");
-    lfuCache.initializeMetrics(solrMetricsContext, scope + "-1");
+    // TODO SOLR-17458: Fix tests for OTEL
+    lfuCache.initializeMetrics(solrMetricsContext, Attributes.empty(), scope + "-1");
 
     CaffeineCache<Integer, String> newLFUCache = new CaffeineCache<>();
-    newLFUCache.initializeMetrics(solrMetricsContext, scope + "-2");
+    // TODO SOLR-17458: Fix tests for OTEL
+    newLFUCache.initializeMetrics(solrMetricsContext, Attributes.empty(), scope + "-2");
 
     Map<String, String> params = new HashMap<>();
     params.put("size", "100");
diff --git a/solr/core/src/test/org/apache/solr/search/TestSolrCachePerf.java b/solr/core/src/test/org/apache/solr/search/TestSolrCachePerf.java
index 84d49d22f3b..989140b28f0 100644
--- a/solr/core/src/test/org/apache/solr/search/TestSolrCachePerf.java
+++ b/solr/core/src/test/org/apache/solr/search/TestSolrCachePerf.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr.search;
 
+import io.opentelemetry.api.common.Attributes;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -105,7 +106,9 @@ public class TestSolrCachePerf extends SolrTestCaseJ4 {
       CacheRegenerator cr = new NoOpRegenerator();
       Object o = cache.init(params, null, cr);
       cache.setState(SolrCache.State.LIVE);
-      cache.initializeMetrics(new SolrMetricsContext(metricManager, "foo", "bar"), "foo");
+      // TODO SOLR-17458: Fix test later
+      cache.initializeMetrics(
+          new SolrMetricsContext(metricManager, "foo", "bar"), Attributes.empty(), "foo");
       AtomicBoolean stop = new AtomicBoolean();
       SummaryStatistics perImplRatio =
           ratioStats.computeIfAbsent(clazz.getSimpleName(), c -> new SummaryStatistics());
diff --git a/solr/core/src/test/org/apache/solr/search/TestSolrFieldCacheBean.java b/solr/core/src/test/org/apache/solr/search/TestSolrFieldCacheBean.java
index 4aeb4da7c36..a69730afc4b 100644
--- a/solr/core/src/test/org/apache/solr/search/TestSolrFieldCacheBean.java
+++ b/solr/core/src/test/org/apache/solr/search/TestSolrFieldCacheBean.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr.search;
 
+import io.opentelemetry.api.common.Attributes;
 import java.util.Map;
 import java.util.Random;
 import org.apache.lucene.tests.util.TestUtil;
@@ -70,7 +71,7 @@ public class TestSolrFieldCacheBean extends SolrTestCaseJ4 {
     SolrMetricManager metricManager = h.getCoreContainer().getMetricManager();
     SolrMetricsContext solrMetricsContext =
         new SolrMetricsContext(metricManager, registryName, "foo");
-    mbean.initializeMetrics(solrMetricsContext, null);
+    mbean.initializeMetrics(solrMetricsContext, Attributes.empty(), null);
     MetricsMap metricsMap =
         (MetricsMap)
             ((SolrMetricManager.GaugeWrapper)
@@ -89,7 +90,7 @@ public class TestSolrFieldCacheBean extends SolrTestCaseJ4 {
     SolrMetricManager metricManager = h.getCoreContainer().getMetricManager();
     SolrMetricsContext solrMetricsContext =
         new SolrMetricsContext(metricManager, registryName, "foo");
-    mbean.initializeMetrics(solrMetricsContext, null);
+    mbean.initializeMetrics(solrMetricsContext, Attributes.empty(), null);
     MetricsMap metricsMap =
         (MetricsMap)
             ((SolrMetricManager.GaugeWrapper)
diff --git a/solr/core/src/test/org/apache/solr/search/TestThinCache.java b/solr/core/src/test/org/apache/solr/search/TestThinCache.java
index 6a23b82568f..de14da997ea 100644
--- a/solr/core/src/test/org/apache/solr/search/TestThinCache.java
+++ b/solr/core/src/test/org/apache/solr/search/TestThinCache.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr.search;
 
+import io.opentelemetry.api.common.Attributes;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Collections;
@@ -94,12 +95,14 @@ public class TestThinCache extends SolrTestCaseJ4 {
     ThinCache<Object, Integer, String> lfuCache = new ThinCache<>();
     lfuCache.setBacking(cacheScope, backing);
     SolrMetricsContext solrMetricsContext = new SolrMetricsContext(metricManager, registry, "foo");
-    lfuCache.initializeMetrics(solrMetricsContext, scope + "-1");
+    // TODO SOLR-17458: Fix test later
+    lfuCache.initializeMetrics(solrMetricsContext, Attributes.empty(), scope + "-1");
 
     Object cacheScope2 = new Object();
     ThinCache<Object, Integer, String> newLFUCache = new ThinCache<>();
     newLFUCache.setBacking(cacheScope2, backing);
-    newLFUCache.initializeMetrics(solrMetricsContext, scope + "-2");
+    // TODO SOLR-17458: Fix test later
+    newLFUCache.initializeMetrics(solrMetricsContext, Attributes.empty(), scope + "-2");
 
     Map<String, String> params = new HashMap<>();
     params.put("size", "100");
diff --git a/solr/core/src/test/org/apache/solr/search/ThinCache.java b/solr/core/src/test/org/apache/solr/search/ThinCache.java
index ea7a155922e..c39ae8b30cc 100644
--- a/solr/core/src/test/org/apache/solr/search/ThinCache.java
+++ b/solr/core/src/test/org/apache/solr/search/ThinCache.java
@@ -19,6 +19,7 @@ package org.apache.solr.search;
 import com.github.benmanes.caffeine.cache.RemovalCause;
 import com.github.benmanes.caffeine.cache.RemovalListener;
 import com.google.common.annotations.VisibleForTesting;
+import io.opentelemetry.api.common.Attributes;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.lang.ref.WeakReference;
@@ -291,7 +292,8 @@ public class ThinCache<S, K, V> extends SolrCacheBase
   private long priorEvictions;
 
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
     solrMetricsContext = parentContext.getChildContext(this);
     cacheMap =
         new MetricsMap(
diff --git a/solr/core/src/test/org/apache/solr/util/tracing/TestSimplePropagatorDistributedTracing.java b/solr/core/src/test/org/apache/solr/util/tracing/TestSimplePropagatorDistributedTracing.java
index 270c53c0471..80940b31da6 100644
--- a/solr/core/src/test/org/apache/solr/util/tracing/TestSimplePropagatorDistributedTracing.java
+++ b/solr/core/src/test/org/apache/solr/util/tracing/TestSimplePropagatorDistributedTracing.java
@@ -35,6 +35,7 @@ import org.apache.solr.client.solrj.request.UpdateRequest;
 import org.apache.solr.client.solrj.response.CollectionAdminResponse;
 import org.apache.solr.cloud.SolrCloudTestCase;
 import org.apache.solr.common.util.SuppressForbidden;
+import org.apache.solr.core.OpenTelemetryConfigurator;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.handler.admin.api.CreateCore;
 import org.apache.solr.logging.MDCLoggingContext;
@@ -50,6 +51,8 @@ public class TestSimplePropagatorDistributedTracing extends SolrCloudTestCase {
 
   @BeforeClass
   public static void setupCluster() throws Exception {
+    OpenTelemetryConfigurator.resetForTest();
+
     configureCluster(4).addConfig("conf", configset("cloud-minimal")).configure();
 
     Tracer tracer = GlobalOpenTelemetry.get().getTracer("solr");
diff --git a/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/handler/MirroringCollectionsHandlerTest.java b/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/handler/MirroringCollectionsHandlerTest.java
index a4cb9a97cc3..ed914dfaeb1 100644
--- a/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/handler/MirroringCollectionsHandlerTest.java
+++ b/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/handler/MirroringCollectionsHandlerTest.java
@@ -31,6 +31,7 @@ import org.apache.solr.common.cloud.Replica;
 import org.apache.solr.common.cloud.SolrZkClient;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.OpenTelemetryConfigurator;
 import org.apache.solr.core.SolrXmlConfig;
 import org.apache.solr.crossdc.common.KafkaCrossDcConf;
 import org.apache.solr.crossdc.common.KafkaMirroringSink;
@@ -160,6 +161,8 @@ public class MirroringCollectionsHandlerTest extends SolrTestCaseJ4 {
 
   @Test
   public void testCoreContainerInit() throws Exception {
+    OpenTelemetryConfigurator.resetForTest();
+
     Path home = createTempDir();
     String solrXml = IOUtils.resourceToString("/mirroring-solr.xml", StandardCharsets.UTF_8);
     CoreContainer cores = new CoreContainer(SolrXmlConfig.fromString(home, solrXml));
diff --git a/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/CustomTestOtelTracerConfigurator.java b/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/CustomTestOtelTracerConfigurator.java
index 16c2d20a41e..e208d6d0034 100644
--- a/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/CustomTestOtelTracerConfigurator.java
+++ b/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/CustomTestOtelTracerConfigurator.java
@@ -16,7 +16,6 @@
  */
 package org.apache.solr.opentelemetry;
 
-import io.opentelemetry.api.GlobalOpenTelemetry;
 import io.opentelemetry.api.trace.Tracer;
 import io.opentelemetry.sdk.OpenTelemetrySdk;
 import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
@@ -99,7 +98,6 @@ public class CustomTestOtelTracerConfigurator extends OtelTracerConfigurator {
         exporter = null;
       }
       System.clearProperty("otel.traces.exporter");
-      GlobalOpenTelemetry.resetForTest();
     }
   }
 }
diff --git a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
index c49c6843923..c475f2b23d3 100644
--- a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
+++ b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
@@ -25,6 +25,7 @@ import static org.hamcrest.core.StringContains.containsString;
 import com.carrotsearch.randomizedtesting.RandomizedContext;
 import com.carrotsearch.randomizedtesting.RandomizedTest;
 import com.carrotsearch.randomizedtesting.rules.SystemPropertiesRestoreRule;
+import io.opentelemetry.api.GlobalOpenTelemetry;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.io.Reader;
@@ -249,6 +250,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
 
   @BeforeClass
   public static void setupTestCases() {
+    GlobalOpenTelemetry.resetForTest();
     resetExceptionIgnores();
 
     testExecutor =
diff --git a/solr/test-framework/src/java/org/apache/solr/core/MockQuerySenderListenerReqHandler.java b/solr/test-framework/src/java/org/apache/solr/core/MockQuerySenderListenerReqHandler.java
index f70cdc59cbd..622a0c25a14 100644
--- a/solr/test-framework/src/java/org/apache/solr/core/MockQuerySenderListenerReqHandler.java
+++ b/solr/test-framework/src/java/org/apache/solr/core/MockQuerySenderListenerReqHandler.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr.core;
 
+import io.opentelemetry.api.common.Attributes;
 import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.handler.RequestHandlerBase;
@@ -38,8 +39,9 @@ public class MockQuerySenderListenerReqHandler extends RequestHandlerBase {
   }
 
   @Override
-  public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
-    super.initializeMetrics(parentContext, scope);
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
+    super.initializeMetrics(parentContext, attributes, scope);
     solrMetricsContext.gauge(
         () -> initCounter.intValue(), true, "initCount", getCategory().toString(), scope);
   }


(solr) 30/33: SOLR-17806: Migrate CaffeineCache to OTEL (#3671)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 06775b0deb80757d0e66ec2ed33f7f932fac825a
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Fri Sep 26 11:09:57 2025 -0400

    SOLR-17806: Migrate CaffeineCache to OTEL (#3671)
    
    * Migrate base metrics to OTEL
    
    * Fix broken tests
    
    * Cleanup utility methods
    
    * Clean up more tests
    
    * Change ram used bytes name
    
    * Change based on feedback
    
    * Address feedback
    
    * Make cumulative cache metrics the default
    
    * Minor feedback
    
    * Pass in metric name for caffeine cache
    
    ---------
    
    Co-authored-by: Matthew Biscocho <mb...@bloomberg.net>
---
 .../java/org/apache/solr/blockcache/Metrics.java   |   9 +-
 .../java/org/apache/solr/core/CoreContainer.java   |  10 +-
 .../src/java/org/apache/solr/core/SolrCore.java    |  35 +++--
 .../apache/solr/handler/ReplicationHandler.java    |  23 ++--
 .../apache/solr/metrics/SolrCoreMetricManager.java |  20 +--
 .../org/apache/solr/metrics/SolrMetricManager.java |  39 +++++-
 .../apache/solr/metrics/SolrMetricProducer.java    |   2 +
 .../apache/solr/metrics/SolrMetricsContext.java    |  35 +++--
 .../java/org/apache/solr/search/CaffeineCache.java | 117 +++++++++-------
 .../org/apache/solr/search/SolrFieldCacheBean.java |   4 +-
 .../org/apache/solr/search/SolrIndexSearcher.java  |   6 +-
 .../org/apache/solr/update/SolrIndexWriter.java    |  21 +--
 .../src/test/org/apache/solr/CursorPagingTest.java |  63 +++++----
 .../apache/solr/core/TestSolrConfigHandler.java    |  38 +++---
 .../test/org/apache/solr/core/TimeAllowedTest.java |  92 ++++++-------
 .../org/apache/solr/search/TestCaffeineCache.java  | 129 ++++++++++++------
 .../solr/search/TestFiltersQueryCaching.java       |  49 +++----
 .../apache/solr/search/TestMainQueryCaching.java   |  20 +--
 .../solr/search/TestReRankQParserPlugin.java       |  35 +++--
 .../org/apache/solr/search/TestSolr4Spatial2.java  |  49 ++++---
 .../org/apache/solr/search/TestSolrCachePerf.java  |  25 +++-
 .../apache/solr/search/TestSolrQueryParser.java    | 114 ++++++++--------
 .../test/org/apache/solr/search/TestThinCache.java |  82 ++++++++----
 .../org/apache/solr/search/join/BJQParserTest.java |  66 +++++----
 .../solr/search/join/TestNestedDocsSort.java       |  22 ++-
 .../solr/search/join/TestScoreJoinQPScore.java     | 147 +++++++++++++++------
 .../org/apache/solr/util/SolrMetricTestUtils.java  |  52 ++++++++
 27 files changed, 779 insertions(+), 525 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/blockcache/Metrics.java b/solr/core/src/java/org/apache/solr/blockcache/Metrics.java
index 98c9b2dbef6..4de589a1103 100644
--- a/solr/core/src/java/org/apache/solr/blockcache/Metrics.java
+++ b/solr/core/src/java/org/apache/solr/blockcache/Metrics.java
@@ -60,14 +60,15 @@ public class Metrics extends SolrCacheBase implements SolrInfoBean {
     var baseAttributes =
         attributes.toBuilder().put(CATEGORY_ATTR, getCategory().toString()).build();
     var blockcacheStats =
-        solrMetricsContext.longMeasurement("solr_block_cache_stats", "Block cache stats");
+        solrMetricsContext.longGaugeMeasurement("solr_block_cache_stats", "Block cache stats");
     var hitRatio =
-        solrMetricsContext.doubleMeasurement("solr_block_cache_hit_ratio", "Block cache hit ratio");
+        solrMetricsContext.doubleGaugeMeasurement(
+            "solr_block_cache_hit_ratio", "Block cache hit ratio");
     var perSecStats =
-        solrMetricsContext.doubleMeasurement(
+        solrMetricsContext.doubleGaugeMeasurement(
             "solr_block_cache_stats_per_second", "Block cache per second stats");
     var bufferCacheStats =
-        solrMetricsContext.doubleMeasurement(
+        solrMetricsContext.doubleGaugeMeasurement(
             "solr_buffer_cache_stats", "Buffer cache per second stats");
 
     this.toClose =
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index cfcdbd451c1..6590349ad87 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -28,6 +28,7 @@ import static org.apache.solr.common.params.CommonParams.ZK_PATH;
 import static org.apache.solr.common.params.CommonParams.ZK_STATUS_PATH;
 import static org.apache.solr.metrics.SolrMetricProducer.CATEGORY_ATTR;
 import static org.apache.solr.metrics.SolrMetricProducer.HANDLER_ATTR;
+import static org.apache.solr.metrics.SolrMetricProducer.NAME_ATTR;
 import static org.apache.solr.search.SolrIndexSearcher.EXECUTOR_MAX_CPU_THREADS;
 import static org.apache.solr.security.AuthenticationPlugin.AUTHENTICATION_PLUGIN_PROP;
 
@@ -135,6 +136,7 @@ import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestHandler;
 import org.apache.solr.request.SolrRequestInfo;
 import org.apache.solr.search.CacheConfig;
+import org.apache.solr.search.CaffeineCache;
 import org.apache.solr.search.SolrCache;
 import org.apache.solr.search.SolrFieldCacheBean;
 import org.apache.solr.search.SolrIndexSearcher;
@@ -802,8 +804,12 @@ public class CoreContainer {
       for (Map.Entry<String, CacheConfig> e : cachesConfig.entrySet()) {
         SolrCache<?, ?> c = e.getValue().newInstance();
         String cacheName = e.getKey();
-        // NOCOMMIT SOLR-17458: Add Otel
-        c.initializeMetrics(solrMetricsContext, Attributes.empty(), "nodeLevelCache/" + cacheName);
+        if (c instanceof CaffeineCache<?, ?> caffeineCache) {
+          caffeineCache.initializeMetrics(
+              solrMetricsContext,
+              Attributes.builder().put(NAME_ATTR, cacheName).build(),
+              "solr_node_cache");
+        }
         m.put(cacheName, c);
       }
       this.caches = Collections.unmodifiableMap(m);
diff --git a/solr/core/src/java/org/apache/solr/core/SolrCore.java b/solr/core/src/java/org/apache/solr/core/SolrCore.java
index 8118ab6c824..d42096c6b6e 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrCore.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java
@@ -21,6 +21,7 @@ import static org.apache.solr.handler.admin.MetricsHandler.OPEN_METRICS_WT;
 import static org.apache.solr.handler.admin.MetricsHandler.PROMETHEUS_METRICS_WT;
 import static org.apache.solr.metrics.SolrCoreMetricManager.COLLECTION_ATTR;
 import static org.apache.solr.metrics.SolrCoreMetricManager.CORE_ATTR;
+import static org.apache.solr.metrics.SolrCoreMetricManager.REPLICA_ATTR;
 import static org.apache.solr.metrics.SolrCoreMetricManager.SHARD_ATTR;
 
 import com.github.benmanes.caffeine.cache.Cache;
@@ -259,7 +260,7 @@ public class SolrCore implements SolrInfoBean, Closeable {
 
   private volatile boolean newSearcherReady = false;
 
-  private final Attributes coreAttributes;
+  private Attributes coreAttributes;
   private AttributedLongCounter newSearcherCounter;
   private AttributedLongCounter newSearcherMaxReachedCounter;
   private AttributedLongCounter newSearcherOtherErrorsCounter;
@@ -556,6 +557,25 @@ public class SolrCore implements SolrInfoBean, Closeable {
     assert this.name != null;
     assert coreDescriptor.getCloudDescriptor() == null : "Cores are not renamed in SolrCloud";
     this.name = Objects.requireNonNull(v);
+    initCoreAttributes();
+  }
+
+  public Attributes getCoreAttributes() {
+    return coreAttributes;
+  }
+
+  private void initCoreAttributes() {
+    this.coreAttributes =
+        (coreContainer.isZooKeeperAware())
+            ? Attributes.builder()
+                .put(COLLECTION_ATTR, coreDescriptor.getCollectionName())
+                .put(CORE_ATTR, getName())
+                .put(SHARD_ATTR, coreDescriptor.getCloudDescriptor().getShardId())
+                .put(
+                    REPLICA_ATTR,
+                    Utils.parseMetricsReplicaName(coreDescriptor.getCollectionName(), getName()))
+                .build()
+            : Attributes.builder().put(CORE_ATTR, getName()).build();
   }
 
   /**
@@ -1089,17 +1109,6 @@ public class SolrCore implements SolrInfoBean, Closeable {
       this.solrMetricsContext = coreMetricManager.getSolrMetricsContext();
       this.coreMetricManager.loadReporters();
 
-      if (coreContainer.isZooKeeperAware()) {
-        this.coreAttributes =
-            Attributes.builder()
-                .put(COLLECTION_ATTR, coreDescriptor.getCollectionName())
-                .put(CORE_ATTR, coreDescriptor.getName())
-                .put(SHARD_ATTR, coreDescriptor.getCloudDescriptor().getShardId())
-                .build();
-      } else {
-        this.coreAttributes = Attributes.builder().put(CORE_ATTR, coreDescriptor.getName()).build();
-      }
-
       if (updateHandler == null) {
         directoryFactory = initDirectoryFactory();
         recoveryStrategyBuilder = initRecoveryStrategyBuilder();
@@ -1120,6 +1129,8 @@ public class SolrCore implements SolrInfoBean, Closeable {
       checkVersionFieldExistsInSchema(schema, coreDescriptor);
       setLatestSchema(schema);
 
+      initCoreAttributes();
+
       // initialize core metrics
       initializeMetrics(solrMetricsContext, coreAttributes, "");
 
diff --git a/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java b/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
index b900dc22ddb..010d1090e48 100644
--- a/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
@@ -841,7 +841,6 @@ public class ReplicationHandler extends RequestHandlerBase
     }
   }
 
-  // NOCOMMIT SOLR-17458: Update with OTEL
   @Override
   public void initializeMetrics(
       SolrMetricsContext parentContext, Attributes attributes, String scope) {
@@ -853,51 +852,51 @@ public class ReplicationHandler extends RequestHandlerBase
     super.initializeMetrics(parentContext, replicationAttributes, scope);
 
     ObservableLongMeasurement indexSizeMetric =
-        solrMetricsContext.longMeasurement(
+        solrMetricsContext.longGaugeMeasurement(
             "solr_replication_index_size", "Size of the index in bytes", OtelUnit.BYTES);
 
     ObservableLongMeasurement indexVersionMetric =
-        solrMetricsContext.longMeasurement(
+        solrMetricsContext.longGaugeMeasurement(
             "solr_replication_index_version", "Current index version");
 
     ObservableLongMeasurement indexGenerationMetric =
-        solrMetricsContext.longMeasurement(
+        solrMetricsContext.longGaugeMeasurement(
             "solr_replication_index_generation", "Current index generation");
 
     ObservableLongMeasurement isLeaderMetric =
-        solrMetricsContext.longMeasurement(
+        solrMetricsContext.longGaugeMeasurement(
             "solr_replication_is_leader", "Whether this node is a leader (1) or not (0)");
 
     ObservableLongMeasurement isFollowerMetric =
-        solrMetricsContext.longMeasurement(
+        solrMetricsContext.longGaugeMeasurement(
             "solr_replication_is_follower", "Whether this node is a follower (1) or not (0)");
 
     ObservableLongMeasurement replicationEnabledMetric =
-        solrMetricsContext.longMeasurement(
+        solrMetricsContext.longGaugeMeasurement(
             "solr_replication_is_enabled", "Whether replication is enabled (1) or not (0)");
 
     ObservableLongMeasurement isPollingDisabledMetric =
-        solrMetricsContext.longMeasurement(
+        solrMetricsContext.longGaugeMeasurement(
             "solr_replication_is_polling_disabled", "Whether polling is disabled (1) or not (0)");
 
     ObservableLongMeasurement isReplicatingMetric =
-        solrMetricsContext.longMeasurement(
+        solrMetricsContext.longGaugeMeasurement(
             "solr_replication_is_replicating", "Whether replication is in progress (1) or not (0)");
 
     ObservableLongMeasurement timeElapsedMetric =
-        solrMetricsContext.longMeasurement(
+        solrMetricsContext.longGaugeMeasurement(
             "solr_replication_time_elapsed",
             "Time elapsed during replication in seconds",
             OtelUnit.SECONDS);
 
     ObservableLongMeasurement bytesDownloadedMetric =
-        solrMetricsContext.longMeasurement(
+        solrMetricsContext.longGaugeMeasurement(
             "solr_replication_downloaded_size",
             "Total bytes downloaded during replication",
             OtelUnit.BYTES);
 
     ObservableLongMeasurement downloadSpeedMetric =
-        solrMetricsContext.longMeasurement(
+        solrMetricsContext.longGaugeMeasurement(
             "solr_replication_download_speed", "Download speed in bytes per second");
 
     metricsCallback =
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
index 6af631d6ebf..86c81c83bd8 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
@@ -20,7 +20,6 @@ import static org.apache.solr.metrics.SolrMetricProducer.HANDLER_ATTR;
 
 import com.codahale.metrics.MetricRegistry;
 import io.opentelemetry.api.common.AttributeKey;
-import io.opentelemetry.api.common.Attributes;
 import java.io.Closeable;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -128,19 +127,11 @@ public class SolrCoreMetricManager implements Closeable {
     // tracked producers.
     // There is some possible improvement that can be done here to not have to duplicate code in
     // registerMetricProducer
-    var attributes =
-        Attributes.builder()
-            .put(CORE_ATTR, core.getName())
-            .put(COLLECTION_ATTR, collectionName)
-            .put(SHARD_ATTR, shardName)
-            .put(REPLICA_ATTR, replicaName)
-            .build();
-
-    core.initializeMetrics(solrMetricsContext, attributes, core.getName());
+    core.initializeMetrics(solrMetricsContext, core.getCoreAttributes(), core.getName());
 
     registeredProducers.forEach(
         metricProducer -> {
-          var producerAttributes = attributes.toBuilder();
+          var producerAttributes = core.getCoreAttributes().toBuilder();
           if (metricProducer.scope().startsWith("/"))
             producerAttributes.put(HANDLER_ATTR, metricProducer.scope);
           metricProducer.producer.initializeMetrics(
@@ -173,12 +164,7 @@ public class SolrCoreMetricManager implements Closeable {
     // reregisterCoreMetrics
     // There is some possible improvement that can be done here to not have to duplicate code in
     // reregisterCoreMetrics
-    var attributesBuilder =
-        Attributes.builder()
-            .put(CORE_ATTR, core.getName())
-            .put(COLLECTION_ATTR, collectionName)
-            .put(SHARD_ATTR, shardName)
-            .put(REPLICA_ATTR, replicaName);
+    var attributesBuilder = core.getCoreAttributes().toBuilder();
     if (scope.startsWith("/")) attributesBuilder.put(HANDLER_ATTR, scope);
     producer.initializeMetrics(solrMetricsContext, attributesBuilder.build(), scope);
   }
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
index 98d18cea32f..b4e1e708d86 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
@@ -389,16 +389,26 @@ public class SolrMetricManager {
         .batchCallback(callback, measurement, additionalMeasurements);
   }
 
-  ObservableLongMeasurement longMeasurement(
+  ObservableLongMeasurement longGaugeMeasurement(
       String registry, String gaugeName, String description, OtelUnit unit) {
     return longGaugeBuilder(registry, gaugeName, description, unit).buildObserver();
   }
 
-  ObservableDoubleMeasurement doubleMeasurement(
+  ObservableDoubleMeasurement doubleGaugeMeasurement(
       String registry, String gaugeName, String description, OtelUnit unit) {
     return doubleGaugeBuilder(registry, gaugeName, description, unit).buildObserver();
   }
 
+  ObservableLongMeasurement longCounterMeasurement(
+      String registry, String counterName, String description, OtelUnit unit) {
+    return longCounterBuilder(registry, counterName, description, unit).buildObserver();
+  }
+
+  ObservableDoubleMeasurement doubleCounterMeasurement(
+      String registry, String counterName, String description, OtelUnit unit) {
+    return doubleCounterBuilder(registry, counterName, description, unit).buildObserver();
+  }
+
   private LongGaugeBuilder longGaugeBuilder(
       String registry, String gaugeName, String description, OtelUnit unit) {
     LongGaugeBuilder builder =
@@ -424,6 +434,31 @@ public class SolrMetricManager {
     return builder;
   }
 
+  private LongCounterBuilder longCounterBuilder(
+      String registry, String counterName, String description, OtelUnit unit) {
+    LongCounterBuilder builder =
+        meterProvider(registry)
+            .get(OTEL_SCOPE_NAME)
+            .counterBuilder(counterName)
+            .setDescription(description);
+    if (unit != null) builder.setUnit(unit.getSymbol());
+
+    return builder;
+  }
+
+  private DoubleCounterBuilder doubleCounterBuilder(
+      String registry, String counterName, String description, OtelUnit unit) {
+    DoubleCounterBuilder builder =
+        meterProvider(registry)
+            .get(OTEL_SCOPE_NAME)
+            .counterBuilder(counterName)
+            .setDescription(description)
+            .ofDoubles();
+    if (unit != null) builder.setUnit(unit.getSymbol());
+
+    return builder;
+  }
+
   // for unit tests
   public MetricRegistry.MetricSupplier<Counter> getCounterSupplier() {
     return counterSupplier;
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java
index 435b7c0051c..ea046b8fda5 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java
@@ -27,6 +27,8 @@ public interface SolrMetricProducer extends AutoCloseable {
   public static final AttributeKey<String> CATEGORY_ATTR = AttributeKey.stringKey("category");
   public static final AttributeKey<String> HANDLER_ATTR = AttributeKey.stringKey("handler");
   public static final AttributeKey<String> OPERATION_ATTR = AttributeKey.stringKey("ops");
+  public static final AttributeKey<String> RESULT_ATTR = AttributeKey.stringKey("result");
+  public static final AttributeKey<String> NAME_ATTR = AttributeKey.stringKey("name");
   public static final AttributeKey<String> PLUGIN_NAME_ATTR = AttributeKey.stringKey("plugin_name");
 
   /**
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java
index 696ace6aa0d..05df56eba88 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java
@@ -276,22 +276,41 @@ public class SolrMetricsContext {
         registryName, metricName, description, callback, unit);
   }
 
-  public ObservableLongMeasurement longMeasurement(String metricName, String description) {
-    return longMeasurement(metricName, description, null);
+  public ObservableLongMeasurement longGaugeMeasurement(String metricName, String description) {
+    return longGaugeMeasurement(metricName, description, null);
   }
 
-  public ObservableLongMeasurement longMeasurement(
+  public ObservableLongMeasurement longGaugeMeasurement(
       String metricName, String description, OtelUnit unit) {
-    return metricManager.longMeasurement(registryName, metricName, description, unit);
+    return metricManager.longGaugeMeasurement(registryName, metricName, description, unit);
   }
 
-  public ObservableDoubleMeasurement doubleMeasurement(String metricName, String description) {
-    return doubleMeasurement(metricName, description, null);
+  public ObservableDoubleMeasurement doubleGaugeMeasurement(String metricName, String description) {
+    return doubleGaugeMeasurement(metricName, description, null);
   }
 
-  public ObservableDoubleMeasurement doubleMeasurement(
+  public ObservableDoubleMeasurement doubleGaugeMeasurement(
       String metricName, String description, OtelUnit unit) {
-    return metricManager.doubleMeasurement(registryName, metricName, description, unit);
+    return metricManager.doubleGaugeMeasurement(registryName, metricName, description, unit);
+  }
+
+  public ObservableLongMeasurement longCounterMeasurement(String metricName, String description) {
+    return longCounterMeasurement(metricName, description, null);
+  }
+
+  public ObservableLongMeasurement longCounterMeasurement(
+      String metricName, String description, OtelUnit unit) {
+    return metricManager.longCounterMeasurement(registryName, metricName, description, unit);
+  }
+
+  public ObservableDoubleMeasurement doubleCounterMeasurement(
+      String metricName, String description) {
+    return doubleCounterMeasurement(metricName, description, null);
+  }
+
+  public ObservableDoubleMeasurement doubleCounterMeasurement(
+      String metricName, String description, OtelUnit unit) {
+    return metricManager.doubleCounterMeasurement(registryName, metricName, description, unit);
   }
 
   public BatchCallback batchCallback(
diff --git a/solr/core/src/java/org/apache/solr/search/CaffeineCache.java b/solr/core/src/java/org/apache/solr/search/CaffeineCache.java
index 8251cd0be96..601bdf80e11 100644
--- a/solr/core/src/java/org/apache/solr/search/CaffeineCache.java
+++ b/solr/core/src/java/org/apache/solr/search/CaffeineCache.java
@@ -16,6 +16,10 @@
  */
 package org.apache.solr.search;
 
+import static org.apache.solr.metrics.SolrMetricProducer.CATEGORY_ATTR;
+import static org.apache.solr.metrics.SolrMetricProducer.OPERATION_ATTR;
+import static org.apache.solr.metrics.SolrMetricProducer.RESULT_ATTR;
+
 import com.github.benmanes.caffeine.cache.AsyncCache;
 import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.Caffeine;
@@ -23,8 +27,8 @@ import com.github.benmanes.caffeine.cache.Policy.Eviction;
 import com.github.benmanes.caffeine.cache.RemovalCause;
 import com.github.benmanes.caffeine.cache.RemovalListener;
 import com.github.benmanes.caffeine.cache.stats.CacheStats;
-import com.google.common.annotations.VisibleForTesting;
 import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.ObservableLongMeasurement;
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.lang.invoke.MethodHandles;
@@ -43,8 +47,9 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.LongAdder;
 import org.apache.lucene.util.Accountable;
 import org.apache.lucene.util.RamUsageEstimator;
-import org.apache.solr.metrics.MetricsMap;
+import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.OtelUnit;
 import org.apache.solr.util.IOFunction;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -99,8 +104,8 @@ public class CaffeineCache<K, V> extends SolrCacheBase
   private boolean cleanupThread;
   private boolean async;
 
-  private MetricsMap cacheMap;
   private SolrMetricsContext solrMetricsContext;
+  private AutoCloseable toClose;
 
   private long initialRamBytes = 0;
   private final LongAdder ramBytes = new LongAdder();
@@ -325,13 +330,14 @@ public class CaffeineCache<K, V> extends SolrCacheBase
 
   @Override
   public void close() throws IOException {
-    SolrCache.super.close();
     cache.invalidateAll();
     cache.cleanUp();
     if (executor instanceof ExecutorService) {
       ((ExecutorService) executor).shutdownNow();
     }
     ramBytes.reset();
+    IOUtils.closeQuietly(toClose);
+    SolrCache.super.close();
   }
 
   @Override
@@ -458,12 +464,6 @@ public class CaffeineCache<K, V> extends SolrCacheBase
     return description;
   }
 
-  // for unit tests only
-  @VisibleForTesting
-  MetricsMap getMetricsMap() {
-    return cacheMap;
-  }
-
   @Override
   public SolrMetricsContext getSolrMetricsContext() {
     return solrMetricsContext;
@@ -471,47 +471,72 @@ public class CaffeineCache<K, V> extends SolrCacheBase
 
   @Override
   public String toString() {
-    return name() + (cacheMap != null ? cacheMap.getValue().toString() : "");
+    return name();
   }
 
-  // TODO SOLR-17458: Migrate to Otel
   @Override
   public void initializeMetrics(
-      SolrMetricsContext parentContext, Attributes attributes, String scope) {
+      SolrMetricsContext parentContext, Attributes attributes, String metricName) {
+    Attributes cacheAttributes =
+        attributes.toBuilder().put(CATEGORY_ATTR, getCategory().toString()).build();
     solrMetricsContext = parentContext.getChildContext(this);
-    cacheMap =
-        new MetricsMap(
-            map -> {
-              if (cache != null) {
-                CacheStats stats = cache.stats();
-                long hitCount = stats.hitCount() + hits.sum();
-                long insertCount = inserts.sum();
-                long lookupCount = stats.requestCount() + lookups.sum();
-
-                map.put(LOOKUPS_PARAM, lookupCount);
-                map.put(HITS_PARAM, hitCount);
-                map.put(HIT_RATIO_PARAM, hitRate(hitCount, lookupCount));
-                map.put(INSERTS_PARAM, insertCount);
-                map.put(EVICTIONS_PARAM, stats.evictionCount());
-                map.put(SIZE_PARAM, cache.asMap().size());
-                map.put("warmupTime", warmupTime);
-                map.put(RAM_BYTES_USED_PARAM, ramBytesUsed());
-                map.put(MAX_RAM_MB_PARAM, getMaxRamMB());
-
-                CacheStats cumulativeStats = priorStats.plus(stats);
-                long cumLookups = priorLookups + lookupCount;
-                long cumHits = priorHits + hitCount;
-                map.put("cumulative_lookups", cumLookups);
-                map.put("cumulative_hits", cumHits);
-                map.put("cumulative_hitratio", hitRate(cumHits, cumLookups));
-                map.put("cumulative_inserts", priorInserts + insertCount);
-                map.put("cumulative_evictions", cumulativeStats.evictionCount());
-              }
-            });
-    solrMetricsContext.gauge(cacheMap, true, scope, getCategory().toString());
-  }
 
-  private static double hitRate(long hitCount, long lookupCount) {
-    return lookupCount == 0 ? 1.0 : (double) hitCount / lookupCount;
+    ObservableLongMeasurement cacheLookupsMetric =
+        solrMetricsContext.longCounterMeasurement(
+            metricName + "_lookups", "Number of cumulative cache lookup results (hits and misses)");
+
+    ObservableLongMeasurement cacheOperationMetric =
+        solrMetricsContext.longCounterMeasurement(
+            metricName + "_ops", "Number of cumulative cache operations (inserts and evictions)");
+
+    ObservableLongMeasurement sizeMetric =
+        solrMetricsContext.longGaugeMeasurement(
+            metricName + "_size", "Current number cache entries");
+
+    ObservableLongMeasurement ramBytesUsedMetric =
+        solrMetricsContext.longGaugeMeasurement(
+            metricName + "_ram_used", "RAM bytes used by cache", OtelUnit.BYTES);
+
+    ObservableLongMeasurement warmupTimeMetric =
+        solrMetricsContext.longGaugeMeasurement(
+            metricName + "_warmup_time", "Cache warmup time (most recent)", OtelUnit.MILLISECONDS);
+
+    this.toClose =
+        solrMetricsContext.batchCallback(
+            () -> {
+              if (cache == null) {
+                return;
+              }
+              CacheStats stats = cache.stats();
+              long hitCount = stats.hitCount() + hits.sum();
+              long lookupCount = stats.requestCount() + lookups.sum();
+              long insertCount = inserts.sum();
+
+              sizeMetric.record(cache.asMap().size(), cacheAttributes);
+              ramBytesUsedMetric.record(ramBytesUsed(), cacheAttributes);
+              warmupTimeMetric.record(warmupTime, cacheAttributes);
+
+              CacheStats cumulativeStats = priorStats.plus(stats);
+              long cumLookups = priorLookups + lookupCount;
+              long cumHits = priorHits + hitCount;
+
+              cacheLookupsMetric.record(
+                  cumHits, cacheAttributes.toBuilder().put(RESULT_ATTR, "hit").build());
+              cacheLookupsMetric.record(
+                  cumLookups - cumHits,
+                  cacheAttributes.toBuilder().put(RESULT_ATTR, "miss").build());
+
+              cacheOperationMetric.record(
+                  priorInserts + insertCount,
+                  cacheAttributes.toBuilder().put(OPERATION_ATTR, "inserts").build());
+              cacheOperationMetric.record(
+                  cumulativeStats.evictionCount(),
+                  cacheAttributes.toBuilder().put(OPERATION_ATTR, "evictions").build());
+            },
+            cacheLookupsMetric,
+            cacheOperationMetric,
+            sizeMetric,
+            ramBytesUsedMetric,
+            warmupTimeMetric);
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/search/SolrFieldCacheBean.java b/solr/core/src/java/org/apache/solr/search/SolrFieldCacheBean.java
index d341ed743c2..3289d42cf25 100644
--- a/solr/core/src/java/org/apache/solr/search/SolrFieldCacheBean.java
+++ b/solr/core/src/java/org/apache/solr/search/SolrFieldCacheBean.java
@@ -60,10 +60,10 @@ public class SolrFieldCacheBean implements SolrInfoBean {
       SolrMetricsContext parentContext, Attributes attributes, String scope) {
     this.solrMetricsContext = parentContext;
     var solrCacheStats =
-        solrMetricsContext.longMeasurement(
+        solrMetricsContext.longGaugeMeasurement(
             "solr_field_cache_entries", "Number of field cache entries");
     var solrCacheSize =
-        solrMetricsContext.longMeasurement(
+        solrMetricsContext.longGaugeMeasurement(
             "solr_field_cache_size", "Size of field cache in bytes", OtelUnit.BYTES);
     this.toClose =
         solrMetricsContext.batchCallback(
diff --git a/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java b/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
index e92b1001b79..235160ec28b 100644
--- a/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
+++ b/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
@@ -106,7 +106,6 @@ import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.index.SlowCompositeReaderWrapper;
 import org.apache.solr.metrics.MetricsMap;
-import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.metrics.SolrMetricsContext;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestInfo;
@@ -603,10 +602,9 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
     this.solrMetricsContext = core.getSolrMetricsContext().getChildContext(this);
     for (SolrCache<?, ?> cache : cacheList) {
       cache.initializeMetrics(
-          // TODO SOLR-17458: Add Otel
           solrMetricsContext,
-          Attributes.empty(),
-          SolrMetricManager.mkName(cache.name(), STATISTICS_KEY));
+          core.getCoreAttributes().toBuilder().put(NAME_ATTR, cache.name()).build(),
+          "solr_searcher_cache");
     }
     // TODO SOLR-17458: Add Otel
     initializeMetrics(solrMetricsContext, Attributes.empty(), STATISTICS_KEY);
diff --git a/solr/core/src/java/org/apache/solr/update/SolrIndexWriter.java b/solr/core/src/java/org/apache/solr/update/SolrIndexWriter.java
index 7fd5e434d6f..75bd8ddc311 100644
--- a/solr/core/src/java/org/apache/solr/update/SolrIndexWriter.java
+++ b/solr/core/src/java/org/apache/solr/update/SolrIndexWriter.java
@@ -16,15 +16,10 @@
  */
 package org.apache.solr.update;
 
-import static org.apache.solr.metrics.SolrCoreMetricManager.COLLECTION_ATTR;
-import static org.apache.solr.metrics.SolrCoreMetricManager.CORE_ATTR;
-import static org.apache.solr.metrics.SolrCoreMetricManager.REPLICA_ATTR;
-import static org.apache.solr.metrics.SolrCoreMetricManager.SHARD_ATTR;
 import static org.apache.solr.metrics.SolrMetricProducer.CATEGORY_ATTR;
 import static org.apache.solr.metrics.SolrMetricProducer.TYPE_ATTR;
 
 import io.opentelemetry.api.common.AttributeKey;
-import io.opentelemetry.api.common.Attributes;
 import io.opentelemetry.api.metrics.ObservableLongGauge;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
@@ -44,7 +39,6 @@ import org.apache.lucene.store.Directory;
 import org.apache.lucene.util.InfoStream;
 import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.common.util.SuppressForbidden;
-import org.apache.solr.common.util.Utils;
 import org.apache.solr.core.DirectoryFactory;
 import org.apache.solr.core.DirectoryFactory.DirContext;
 import org.apache.solr.core.SolrCore;
@@ -191,19 +185,10 @@ public class SolrIndexWriter extends IndexWriter {
       } else {
         mergeTotals = false;
       }
-      String coreName = core.getCoreDescriptor().getName();
-      var baseAttributesBuilder =
-          Attributes.builder()
+      var baseAttributes =
+          core.getCoreAttributes().toBuilder()
               .put(CATEGORY_ATTR, SolrInfoBean.Category.INDEX.toString())
-              .put(CORE_ATTR, coreName);
-      if (core.getCoreContainer().isZooKeeperAware()) {
-        String collectionName = core.getCoreDescriptor().getCollectionName();
-        baseAttributesBuilder
-            .put(COLLECTION_ATTR, collectionName)
-            .put(SHARD_ATTR, core.getCoreDescriptor().getCloudDescriptor().getShardId())
-            .put(REPLICA_ATTR, Utils.parseMetricsReplicaName(collectionName, coreName));
-      }
-      var baseAttributes = baseAttributesBuilder.build();
+              .build();
       if (mergeDetails) {
         mergeTotals = true; // override
         majorMergedDocs =
diff --git a/solr/core/src/test/org/apache/solr/CursorPagingTest.java b/solr/core/src/test/org/apache/solr/CursorPagingTest.java
index 45689e2cf21..0babb49496c 100644
--- a/solr/core/src/test/org/apache/solr/CursorPagingTest.java
+++ b/solr/core/src/test/org/apache/solr/CursorPagingTest.java
@@ -45,11 +45,11 @@ import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.CursorMarkParams;
 import org.apache.solr.common.params.GroupParams;
 import org.apache.solr.common.params.SolrParams;
-import org.apache.solr.metrics.MetricsMap;
-import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.core.SolrCore;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.search.CursorMark;
 import org.apache.solr.util.LogLevel;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.junit.After;
 import org.junit.BeforeClass;
 
@@ -715,30 +715,19 @@ public class CursorPagingTest extends SolrTestCaseJ4 {
 
     final Collection<String> allFieldNames = getAllSortFieldNames();
 
-    final MetricsMap filterCacheStats =
-        (MetricsMap)
-            ((SolrMetricManager.GaugeWrapper)
-                    h.getCore()
-                        .getCoreMetricManager()
-                        .getRegistry()
-                        .getMetrics()
-                        .get("CACHE.searcher.filterCache"))
-                .getGauge();
-    assertNotNull(filterCacheStats);
-    final MetricsMap queryCacheStats =
-        (MetricsMap)
-            ((SolrMetricManager.GaugeWrapper)
-                    h.getCore()
-                        .getCoreMetricManager()
-                        .getRegistry()
-                        .getMetrics()
-                        .get("CACHE.searcher.queryResultCache"))
-                .getGauge();
-    assertNotNull(queryCacheStats);
-
-    final long preQcIn = (Long) queryCacheStats.getValue().get("inserts");
-    final long preFcIn = (Long) filterCacheStats.getValue().get("inserts");
-    final long preFcHits = (Long) filterCacheStats.getValue().get("hits");
+    SolrCore solrCore = h.getCore();
+
+    // Get initial metrics for filter and query cache
+    var preFilterInserts =
+        SolrMetricTestUtils.getCacheSearcherOpsInserts(solrCore, SolrMetricTestUtils.FILTER_CACHE)
+            .getValue();
+    var preFilterHits =
+        SolrMetricTestUtils.getCacheSearcherOpsHits(solrCore, SolrMetricTestUtils.FILTER_CACHE)
+            .getValue();
+    var preQueryInserts =
+        SolrMetricTestUtils.getCacheSearcherOpsInserts(
+                solrCore, SolrMetricTestUtils.QUERY_RESULT_CACHE)
+            .getValue();
 
     SentinelIntSet ids =
         assertFullWalkNoDups(
@@ -753,13 +742,21 @@ public class CursorPagingTest extends SolrTestCaseJ4 {
 
     assertEquals(6, ids.size());
 
-    final long postQcIn = (Long) queryCacheStats.getValue().get("inserts");
-    final long postFcIn = (Long) filterCacheStats.getValue().get("inserts");
-    final long postFcHits = (Long) filterCacheStats.getValue().get("hits");
-
-    assertEquals("query cache inserts changed", preQcIn, postQcIn);
-    assertEquals("filter cache did not grow correctly", 2, postFcIn - preFcIn);
-    assertTrue("filter cache did not have any new cache hits", 0 < postFcHits - preFcHits);
+    var postFilterInserts =
+        SolrMetricTestUtils.getCacheSearcherOpsInserts(solrCore, SolrMetricTestUtils.FILTER_CACHE)
+            .getValue();
+    var postFilterHits =
+        SolrMetricTestUtils.getCacheSearcherOpsHits(solrCore, SolrMetricTestUtils.FILTER_CACHE)
+            .getValue();
+    var postQueryInserts =
+        SolrMetricTestUtils.getCacheSearcherOpsInserts(
+                solrCore, SolrMetricTestUtils.QUERY_RESULT_CACHE)
+            .getValue();
+
+    assertEquals("query cache inserts changed", preQueryInserts, postQueryInserts, 0.0);
+    assertEquals(
+        "filter cache did not grow correctly", 2, postFilterInserts - preFilterInserts, 0.0);
+    assertTrue("filter cache did not have any new cache hits", 0 < postFilterHits - preFilterHits);
   }
 
   /** randomized testing of a non-trivial number of docs using assertFullWalkNoDups */
diff --git a/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java b/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java
index cbadc2afdd9..0eeb3b463db 100644
--- a/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java
+++ b/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java
@@ -1012,14 +1012,15 @@ public class TestSolrConfigHandler extends RestTestBase {
   public void testCacheDisableSolrConfig() throws Exception {
     RESTfulServerProvider oldProvider = restTestHarness.getServerProvider();
     restTestHarness.setServerProvider(() -> getBaseUrl());
-    MapWriter confMap = getRespMap("/admin/metrics", restTestHarness);
-    assertNotNull(
-        confMap._get(
-            asList("metrics", "solr.core.collection1", "CACHE.searcher.fieldValueCache"), null));
+    String prometheusMetrics = restTestHarness.query("/admin/metrics?wt=prometheus");
+    assertTrue(
+        "fieldValueCache metrics should be present",
+        prometheusMetrics.contains("name=\"fieldValueCache\""));
     // Here documentCache is disabled at initialization in SolrConfig
-    assertNull(
-        confMap._get(
-            asList("metrics", "solr.core.collection1", "CACHE.searcher.documentCache"), null));
+    assertFalse(
+        "documentCache metrics should be absent",
+        prometheusMetrics.contains("name=\"documentCache\""));
+
     restTestHarness.setServerProvider(oldProvider);
   }
 
@@ -1032,10 +1033,10 @@ public class TestSolrConfigHandler extends RestTestBase {
     assertEquals("399", overlay._getStr("overlay/props/query/documentCache/size"));
     // Setting size only will not enable the cache
     restTestHarness.setServerProvider(() -> getBaseUrl());
-    MapWriter confMap = getRespMap("/admin/metrics", restTestHarness);
-    assertNull(
-        confMap._get(
-            asList("metrics", "solr.core.collection1", "CACHE.searcher.documentCache"), null));
+
+    String prometheusMetrics = restTestHarness.query("/admin/metrics?wt=prometheus");
+    assertFalse(prometheusMetrics.contains("cache_name=\"documentCache\""));
+
     restTestHarness.setServerProvider(oldProvider);
   }
 
@@ -1045,20 +1046,19 @@ public class TestSolrConfigHandler extends RestTestBase {
     String payload = "{'set-property' : { 'query.documentCache.enabled': true} }";
     runConfigCommand(restTestHarness, "/config", payload);
     restTestHarness.setServerProvider(() -> getBaseUrl());
-    MapWriter confMap = getRespMap("/admin/metrics", restTestHarness);
-    assertNotNull(
-        confMap._get(
-            asList("metrics", "solr.core.collection1", "CACHE.searcher.documentCache"), null));
+
+    String prometheusMetrics = restTestHarness.query("/admin/metrics?wt=prometheus");
+    assertTrue(prometheusMetrics.contains("name=\"documentCache\""));
 
     // Disabling Cache
     payload = "{ 'set-property' : { 'query.documentCache.enabled': false } }";
     restTestHarness.setServerProvider(oldProvider);
+
     runConfigCommand(restTestHarness, "/config", payload);
     restTestHarness.setServerProvider(() -> getBaseUrl());
-    confMap = getRespMap("/admin/metrics", restTestHarness);
-    assertNull(
-        confMap._get(
-            asList("metrics", "solr.core.collection1", "CACHE.searcher.documentCache"), null));
+
+    prometheusMetrics = restTestHarness.query("/admin/metrics?wt=prometheus");
+    assertFalse(prometheusMetrics.contains("name=\"documentCache\""));
 
     restTestHarness.setServerProvider(oldProvider);
   }
diff --git a/solr/core/src/test/org/apache/solr/core/TimeAllowedTest.java b/solr/core/src/test/org/apache/solr/core/TimeAllowedTest.java
index 32521802d13..7a88a470a90 100644
--- a/solr/core/src/test/org/apache/solr/core/TimeAllowedTest.java
+++ b/solr/core/src/test/org/apache/solr/core/TimeAllowedTest.java
@@ -18,11 +18,11 @@ package org.apache.solr.core;
 
 import static org.apache.solr.common.util.Utils.fromJSONString;
 
+import io.prometheus.metrics.model.snapshots.CounterSnapshot;
 import java.util.Map;
 import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.metrics.MetricsMap;
-import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -110,25 +110,15 @@ public class TimeAllowedTest extends SolrTestCaseJ4 {
   public void testCacheAssumptions() throws Exception {
     String fq = "name:d*";
     SolrCore core = h.getCore();
-    MetricsMap filterCacheStats =
-        (MetricsMap)
-            ((SolrMetricManager.GaugeWrapper<?>)
-                    core.getCoreMetricManager()
-                        .getRegistry()
-                        .getMetrics()
-                        .get("CACHE.searcher.filterCache"))
-                .getGauge();
-    long fqInserts = (long) filterCacheStats.getValue().get("inserts");
-
-    MetricsMap queryCacheStats =
-        (MetricsMap)
-            ((SolrMetricManager.GaugeWrapper<?>)
-                    core.getCoreMetricManager()
-                        .getRegistry()
-                        .getMetrics()
-                        .get("CACHE.searcher.queryResultCache"))
-                .getGauge();
-    long qrInserts = (long) queryCacheStats.getValue().get("inserts");
+
+    CounterSnapshot.CounterDataPointSnapshot fqInsertsPre =
+        SolrMetricTestUtils.getCacheSearcherOpsInserts(core, SolrMetricTestUtils.FILTER_CACHE);
+    long fqInserts = (long) fqInsertsPre.getValue();
+
+    CounterSnapshot.CounterDataPointSnapshot qrInsertsPre =
+        SolrMetricTestUtils.getCacheSearcherOpsInserts(
+            core, SolrMetricTestUtils.QUERY_RESULT_CACHE);
+    long qrInserts = (long) qrInsertsPre.getValue();
 
     // This gets 0 docs back. Use 10000 instead of 1 for timeAllowed, and it gets 100 back and the
     // for loop below succeeds.
@@ -143,25 +133,29 @@ public class TimeAllowedTest extends SolrTestCaseJ4 {
         "Should have partial results",
         (Boolean) (header.get(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY)));
 
+    CounterSnapshot.CounterDataPointSnapshot qrInsertsPost =
+        SolrMetricTestUtils.getCacheSearcherOpsInserts(
+            core, SolrMetricTestUtils.QUERY_RESULT_CACHE);
     assertEquals(
         "Should NOT have inserted partial results in the cache!",
-        (long) queryCacheStats.getValue().get("inserts"),
+        (long) qrInsertsPost.getValue(),
         qrInserts);
 
-    assertEquals(
-        "Should NOT have another insert",
-        fqInserts,
-        (long) filterCacheStats.getValue().get("inserts"));
+    CounterSnapshot.CounterDataPointSnapshot fqInsertsPost =
+        SolrMetricTestUtils.getCacheSearcherOpsInserts(core, SolrMetricTestUtils.FILTER_CACHE);
+    assertEquals("Should NOT have another insert", fqInserts, (long) fqInsertsPost.getValue());
 
     // At the end of all this, we should have no hits in the queryResultCache.
     response = JQ(req("q", "*:*", "fq", fq, "indent", "true", "timeAllowed", longTimeout));
 
     // Check that we did insert this one.
-    assertEquals("Hits should still be 0", (long) filterCacheStats.getValue().get("hits"), 0L);
-    assertEquals(
-        "Inserts should be bumped",
-        (long) filterCacheStats.getValue().get("inserts"),
-        fqInserts + 1);
+    CounterSnapshot.CounterDataPointSnapshot fqHitsPost =
+        SolrMetricTestUtils.getCacheSearcherOpsHits(core, SolrMetricTestUtils.FILTER_CACHE);
+    assertEquals("Hits should still be 0", (long) fqHitsPost.getValue(), 0L);
+
+    CounterSnapshot.CounterDataPointSnapshot fqInsertsPostSecond =
+        SolrMetricTestUtils.getCacheSearcherOpsInserts(core, SolrMetricTestUtils.FILTER_CACHE);
+    assertEquals("Inserts should be bumped", (long) fqInsertsPostSecond.getValue(), fqInserts + 1);
 
     res = (Map<?, ?>) fromJSONString(response);
     body = (Map<?, ?>) (res.get("response"));
@@ -179,22 +173,24 @@ public class TimeAllowedTest extends SolrTestCaseJ4 {
   public void testQueryResults() throws Exception {
     String q = "name:e*";
     SolrCore core = h.getCore();
-    MetricsMap queryCacheStats =
-        (MetricsMap)
-            ((SolrMetricManager.GaugeWrapper<?>)
-                    core.getCoreMetricManager()
-                        .getRegistry()
-                        .getMetrics()
-                        .get("CACHE.searcher.queryResultCache"))
-                .getGauge();
-    Map<String, Object> nl = queryCacheStats.getValue();
-    long inserts = (long) nl.get("inserts");
+
+    CounterSnapshot.CounterDataPointSnapshot insertsPre =
+        SolrMetricTestUtils.getCacheSearcherOpsInserts(
+            core, SolrMetricTestUtils.QUERY_RESULT_CACHE);
+    long inserts = (long) insertsPre.getValue();
+
+    CounterSnapshot.CounterDataPointSnapshot hitsPre =
+        SolrMetricTestUtils.getCacheSearcherOpsHits(core, SolrMetricTestUtils.QUERY_RESULT_CACHE);
+    long hits = (long) hitsPre.getValue();
 
     String response = JQ(req("q", q, "indent", "true", "timeAllowed", "1", "sleep", sleep));
 
     // The queryResultCache should NOT get an entry here.
-    nl = queryCacheStats.getValue();
-    assertEquals("Should NOT have inserted partial results!", inserts, (long) nl.get("inserts"));
+    CounterSnapshot.CounterDataPointSnapshot insertsPost =
+        SolrMetricTestUtils.getCacheSearcherOpsInserts(
+            core, SolrMetricTestUtils.QUERY_RESULT_CACHE);
+    assertEquals(
+        "Should NOT have inserted partial results!", inserts, (long) insertsPost.getValue());
 
     Map<?, ?> res = (Map<?, ?>) fromJSONString(response);
     Map<?, ?> body = (Map<?, ?>) (res.get("response"));
@@ -208,9 +204,13 @@ public class TimeAllowedTest extends SolrTestCaseJ4 {
     response = JQ(req("q", q, "indent", "true", "timeAllowed", longTimeout));
 
     // Check that we did insert this one.
-    Map<String, Object> nl2 = queryCacheStats.getValue();
-    assertEquals("Hits should still be 0", (long) nl.get("hits"), (long) nl2.get("hits"));
-    assertTrue("Inserts should be bumped", inserts < (long) nl2.get("inserts"));
+    CounterSnapshot.CounterDataPointSnapshot hitsPost =
+        SolrMetricTestUtils.getCacheSearcherOpsHits(core, SolrMetricTestUtils.QUERY_RESULT_CACHE);
+    CounterSnapshot.CounterDataPointSnapshot insertsPost2 =
+        SolrMetricTestUtils.getCacheSearcherOpsInserts(
+            core, SolrMetricTestUtils.QUERY_RESULT_CACHE);
+    assertEquals("Hits should still be 0", hits, (long) hitsPost.getValue());
+    assertTrue("Inserts should be bumped", inserts < (long) insertsPost2.getValue());
 
     res = (Map<?, ?>) fromJSONString(response);
     body = (Map<?, ?>) (res.get("response"));
diff --git a/solr/core/src/test/org/apache/solr/search/TestCaffeineCache.java b/solr/core/src/test/org/apache/solr/search/TestCaffeineCache.java
index d70ea43a468..12d7813ec2d 100644
--- a/solr/core/src/test/org/apache/solr/search/TestCaffeineCache.java
+++ b/solr/core/src/test/org/apache/solr/search/TestCaffeineCache.java
@@ -16,10 +16,14 @@
  */
 package org.apache.solr.search;
 
+import static org.apache.solr.metrics.SolrMetricProducer.NAME_ATTR;
+
 import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.Caffeine;
 import com.github.benmanes.caffeine.cache.RemovalCause;
 import io.opentelemetry.api.common.Attributes;
+import io.prometheus.metrics.model.snapshots.CounterSnapshot;
+import io.prometheus.metrics.model.snapshots.Labels;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -35,6 +39,7 @@ import org.apache.lucene.util.Accountable;
 import org.apache.solr.SolrTestCase;
 import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.junit.Test;
 
 /** Test for {@link CaffeineCache}. */
@@ -45,15 +50,18 @@ public class TestCaffeineCache extends SolrTestCase {
   String scope = TestUtil.randomSimpleString(random(), 2, 10);
 
   @Test
-  public void testSimple() {
+  public void testSimple() throws IOException {
     CaffeineCache<Integer, String> lfuCache = new CaffeineCache<>();
+    CaffeineCache<Integer, String> newLFUCache = new CaffeineCache<>();
+    String lfuCacheName = scope + "-1";
+    String newLfuCacheName = scope + "-2";
+
     SolrMetricsContext solrMetricsContext = new SolrMetricsContext(metricManager, registry, "foo");
-    // TODO SOLR-17458: Fix tests for OTEL
-    lfuCache.initializeMetrics(solrMetricsContext, Attributes.empty(), scope + "-1");
 
-    CaffeineCache<Integer, String> newLFUCache = new CaffeineCache<>();
-    // TODO SOLR-17458: Fix tests for OTEL
-    newLFUCache.initializeMetrics(solrMetricsContext, Attributes.empty(), scope + "-2");
+    lfuCache.initializeMetrics(
+        solrMetricsContext, Attributes.of(NAME_ATTR, lfuCacheName), "solr_cache");
+    newLFUCache.initializeMetrics(
+        solrMetricsContext, Attributes.of(NAME_ATTR, newLfuCacheName), "solr_cache");
 
     Map<String, String> params = new HashMap<>();
     params.put("size", "100");
@@ -69,31 +77,46 @@ public class TestCaffeineCache extends SolrTestCase {
     assertEquals("15", lfuCache.get(15));
     assertEquals("75", lfuCache.get(75));
     assertNull(lfuCache.get(110));
-    Map<String, Object> nl = lfuCache.getMetricsMap().getValue();
-    assertEquals(3L, nl.get("lookups"));
-    assertEquals(2L, nl.get("hits"));
-    assertEquals(101L, nl.get("inserts"));
 
-    assertNull(lfuCache.get(1)); // first item put in should be the first out
+    var prometheusReader = metricManager.getPrometheusMetricReader(registry);
+
+    var hitDatapoint = getCacheLookup(prometheusReader, lfuCacheName, "hit").getValue();
+    var missDatapoint = getCacheLookup(prometheusReader, lfuCacheName, "miss").getValue();
+    var insertDatapoint = getCacheOperation(prometheusReader, lfuCacheName, "inserts").getValue();
+    var evictionsDatapoint =
+        getCacheOperation(prometheusReader, lfuCacheName, "evictions").getValue();
+    assertEquals(3.0, hitDatapoint + missDatapoint, 0.001); // total lookups
+    assertEquals(2.0, hitDatapoint, 0.001);
+    assertEquals(101.0, insertDatapoint, 0.001);
+    assertNull(lfuCache.get(1));
 
     // Test autowarming
     newLFUCache.init(params, initObj, regenerator);
     newLFUCache.warm(null, lfuCache);
     newLFUCache.setState(SolrCache.State.LIVE);
-
     newLFUCache.put(103, "103");
     assertEquals("15", newLFUCache.get(15));
     assertEquals("75", newLFUCache.get(75));
     assertNull(newLFUCache.get(50));
-    nl = newLFUCache.getMetricsMap().getValue();
-    assertEquals(3L, nl.get("lookups"));
-    assertEquals(2L, nl.get("hits"));
-    assertEquals(1L, nl.get("inserts"));
-    assertEquals(0L, nl.get("evictions"));
-
-    assertEquals(7L, nl.get("cumulative_lookups"));
-    assertEquals(4L, nl.get("cumulative_hits"));
-    assertEquals(102L, nl.get("cumulative_inserts"));
+
+    var cumHitDatapoint = getCacheLookup(prometheusReader, newLfuCacheName, "hit").getValue();
+    var cumMissDatapoint = getCacheLookup(prometheusReader, newLfuCacheName, "miss").getValue();
+    var cumInsertDatapoint =
+        getCacheOperation(prometheusReader, newLfuCacheName, "inserts").getValue();
+    var cumEvictionsDatapoint =
+        getCacheOperation(prometheusReader, newLfuCacheName, "evictions").getValue();
+    var newHitDatapoint = cumHitDatapoint - hitDatapoint;
+    var newMissDatapoint = cumMissDatapoint - missDatapoint;
+    var newInsertDatapoint = cumInsertDatapoint - insertDatapoint;
+    var newEvictionDatapoint = cumEvictionsDatapoint - evictionsDatapoint;
+    assertEquals(3.0, newHitDatapoint + missDatapoint, 0.001); // total lookups
+    assertEquals(2.0, newMissDatapoint, 0.001);
+    assertEquals(1.0, newInsertDatapoint, 0.001);
+    assertEquals(0.0, newEvictionDatapoint, 0.001);
+
+    assertEquals(7.0, cumHitDatapoint + cumMissDatapoint, 0.001); // total cumulative lookups
+    assertEquals(4.0, cumHitDatapoint, 0.001);
+    assertEquals(102.0, cumInsertDatapoint, 0.001);
   }
 
   @Test
@@ -148,7 +171,7 @@ public class TestCaffeineCache extends SolrTestCase {
     int IDLE_TIME_SEC = 5;
     CountDownLatch removed = new CountDownLatch(1);
     AtomicReference<RemovalCause> removalCause = new AtomicReference<>();
-    CaffeineCache<String, String> cache =
+    try (CaffeineCache<String, String> cache =
         new CaffeineCache<>() {
           @Override
           public void onRemoval(String key, String value, RemovalCause cause) {
@@ -156,22 +179,23 @@ public class TestCaffeineCache extends SolrTestCase {
             removalCause.set(cause);
             removed.countDown();
           }
-        };
-    Map<String, String> params = new HashMap<>();
-    params.put("size", "6");
-    params.put("maxIdleTime", "" + IDLE_TIME_SEC);
-    cache.init(params, null, new NoOpRegenerator());
-
-    cache.put("foo", "bar");
-    assertEquals("bar", cache.get("foo"));
-    // sleep for at least the idle time before inserting other entries
-    // the eviction is piggy-backed on put()
-    Thread.sleep(TimeUnit.SECONDS.toMillis(IDLE_TIME_SEC * 2));
-    cache.put("abc", "xyz");
-    boolean await = removed.await(30, TimeUnit.SECONDS);
-    assertTrue("did not expire entry in in time", await);
-    assertEquals(RemovalCause.EXPIRED, removalCause.get());
-    assertNull(cache.get("foo"));
+        }) {
+      Map<String, String> params = new HashMap<>();
+      params.put("size", "6");
+      params.put("maxIdleTime", "" + IDLE_TIME_SEC);
+      cache.init(params, null, new NoOpRegenerator());
+
+      cache.put("foo", "bar");
+      assertEquals("bar", cache.get("foo"));
+      // sleep for at least the idle time before inserting other entries
+      // the eviction is piggy-backed on put()
+      Thread.sleep(TimeUnit.SECONDS.toMillis(IDLE_TIME_SEC * 2));
+      cache.put("abc", "xyz");
+      boolean await = removed.await(30, TimeUnit.SECONDS);
+      assertTrue("did not expire entry in in time", await);
+      assertEquals(RemovalCause.EXPIRED, removalCause.get());
+      assertNull(cache.get("foo"));
+    }
   }
 
   @Test
@@ -358,4 +382,33 @@ public class TestCaffeineCache extends SolrTestCase {
     cache.close();
     assertEquals(emptySize, cache.ramBytesUsed());
   }
+
+  private CounterSnapshot.CounterDataPointSnapshot getCacheOperation(
+      org.apache.solr.metrics.otel.FilterablePrometheusMetricReader prometheusReader,
+      String cacheName,
+      String operation) {
+    return SolrMetricTestUtils.getCounterDatapoint(
+        prometheusReader,
+        "solr_cache_ops",
+        Labels.builder()
+            .label("category", "CACHE")
+            .label("ops", operation)
+            .label("name", cacheName)
+            .label("otel_scope_name", "org.apache.solr")
+            .build());
+  }
+
+  private CounterSnapshot.CounterDataPointSnapshot getCacheLookup(
+      org.apache.solr.metrics.otel.FilterablePrometheusMetricReader prometheusReader,
+      String cacheName,
+      String result) {
+    var builder =
+        Labels.builder()
+            .label("category", "CACHE")
+            .label("name", cacheName)
+            .label("result", result)
+            .label("otel_scope_name", "org.apache.solr");
+    return SolrMetricTestUtils.getCounterDatapoint(
+        prometheusReader, "solr_cache_lookups", builder.build());
+  }
 }
diff --git a/solr/core/src/test/org/apache/solr/search/TestFiltersQueryCaching.java b/solr/core/src/test/org/apache/solr/search/TestFiltersQueryCaching.java
index 48733cca532..e2f47693ec4 100644
--- a/solr/core/src/test/org/apache/solr/search/TestFiltersQueryCaching.java
+++ b/solr/core/src/test/org/apache/solr/search/TestFiltersQueryCaching.java
@@ -16,17 +16,16 @@
  */
 package org.apache.solr.search;
 
+import io.prometheus.metrics.model.snapshots.CounterSnapshot;
 import java.lang.reflect.Array;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 import java.util.concurrent.Future;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.core.SolrCore;
-import org.apache.solr.metrics.MetricsMap;
-import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -51,6 +50,14 @@ public class TestFiltersQueryCaching extends SolrTestCaseJ4 {
     assertU(commit());
   }
 
+  private static CounterSnapshot.CounterDataPointSnapshot getFilterCacheInserts(SolrCore core) {
+    return SolrMetricTestUtils.getCacheSearcherOpsInserts(core, SolrMetricTestUtils.FILTER_CACHE);
+  }
+
+  private static CounterSnapshot.CounterDataPointSnapshot getFilterCacheHits(SolrCore core) {
+    return SolrMetricTestUtils.getCacheSearcherOpsHits(core, SolrMetricTestUtils.FILTER_CACHE);
+  }
+
   /** Reload the core to reset non-cumulative cache metrics. */
   private void reloadAndWait() throws Exception {
     h.reload();
@@ -64,19 +71,12 @@ public class TestFiltersQueryCaching extends SolrTestCaseJ4 {
     waitSearcher[0].get();
   }
 
-  private static Map<String, Object> lookupFilterCacheMetrics(SolrCore core) {
-    return ((MetricsMap)
-            ((SolrMetricManager.GaugeWrapper<?>)
-                    core.getCoreMetricManager()
-                        .getRegistry()
-                        .getMetrics()
-                        .get("CACHE.searcher.filterCache"))
-                .getGauge())
-        .getValue();
+  private static long lookupFilterCacheInserts(SolrCore core) {
+    return (long) getFilterCacheInserts(core).getValue();
   }
 
-  private static long lookupFilterCacheInserts(SolrCore core) {
-    return (long) lookupFilterCacheMetrics(core).get("inserts");
+  private static long lookupFilterCacheHits(SolrCore core) {
+    return (long) getFilterCacheHits(core).getValue();
   }
 
   @Test
@@ -126,7 +126,6 @@ public class TestFiltersQueryCaching extends SolrTestCaseJ4 {
 
     reloadAndWait();
     SolrCore core = h.getCore();
-    Map<String, Object> filterCacheMetrics;
     final String termQuery2 = "{!term f=field_s v='d1'}";
     final String filterTermQuery2 = "filter(" + termQuery2 + ")";
     assertJQ(
@@ -151,9 +150,8 @@ public class TestFiltersQueryCaching extends SolrTestCaseJ4 {
             "true",
             "fq",
             random().nextBoolean() ? termQuery : filterTermQuery));
-    filterCacheMetrics = lookupFilterCacheMetrics(core);
-    assertEquals(2, (long) filterCacheMetrics.get("inserts")); // unchanged
-    assertEquals(1, (long) filterCacheMetrics.get("hits"));
+    assertEquals(2, lookupFilterCacheInserts(core)); // unchanged
+    assertEquals(1, lookupFilterCacheHits(core));
     JQ(
         req(
             "q",
@@ -162,9 +160,8 @@ public class TestFiltersQueryCaching extends SolrTestCaseJ4 {
             "true",
             "fq",
             random().nextBoolean() ? termQuery2 : filterTermQuery2));
-    filterCacheMetrics = lookupFilterCacheMetrics(core);
-    assertEquals(2, (long) filterCacheMetrics.get("inserts")); // unchanged
-    assertEquals(2, (long) filterCacheMetrics.get("hits"));
+    assertEquals(2, lookupFilterCacheInserts(core)); // unchanged
+    assertEquals(2, lookupFilterCacheHits(core));
     JQ(
         req(
             "q",
@@ -181,9 +178,8 @@ public class TestFiltersQueryCaching extends SolrTestCaseJ4 {
             "*",
             "sort",
             "id asc"));
-    filterCacheMetrics = lookupFilterCacheMetrics(core);
-    assertEquals(2, (long) filterCacheMetrics.get("inserts")); // unchanged
-    assertEquals(4, (long) filterCacheMetrics.get("hits"));
+    assertEquals(2, lookupFilterCacheInserts(core)); // unchanged
+    assertEquals(4, lookupFilterCacheHits(core));
     JQ(
         req(
             "q",
@@ -200,9 +196,8 @@ public class TestFiltersQueryCaching extends SolrTestCaseJ4 {
             "*",
             "sort",
             "id asc"));
-    filterCacheMetrics = lookupFilterCacheMetrics(core);
-    assertEquals(3, (long) filterCacheMetrics.get("inserts")); // added top-level
-    assertEquals(6, (long) filterCacheMetrics.get("hits"));
+    assertEquals(3, lookupFilterCacheInserts(core)); // added top-level
+    assertEquals(6, lookupFilterCacheHits(core));
   }
 
   @Test
diff --git a/solr/core/src/test/org/apache/solr/search/TestMainQueryCaching.java b/solr/core/src/test/org/apache/solr/search/TestMainQueryCaching.java
index 2cebca1ffe1..60cd7b9ab90 100644
--- a/solr/core/src/test/org/apache/solr/search/TestMainQueryCaching.java
+++ b/solr/core/src/test/org/apache/solr/search/TestMainQueryCaching.java
@@ -30,6 +30,7 @@ import org.apache.solr.common.util.SolrNamedThreadFactory;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.metrics.MetricsMap;
 import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -96,16 +97,7 @@ public class TestMainQueryCaching extends SolrTestCaseJ4 {
   }
 
   private static long coreToInserts(SolrCore core, String cacheName) {
-    return (long)
-        ((MetricsMap)
-                ((SolrMetricManager.GaugeWrapper<?>)
-                        core.getCoreMetricManager()
-                            .getRegistry()
-                            .getMetrics()
-                            .get("CACHE.searcher.".concat(cacheName)))
-                    .getGauge())
-            .getValue()
-            .get("inserts");
+    return (long) SolrMetricTestUtils.getCacheSearcherOpsInserts(core, cacheName).getValue();
   }
 
   private static long coreToSortCount(SolrCore core, String skipOrFull) {
@@ -119,6 +111,7 @@ public class TestMainQueryCaching extends SolrTestCaseJ4 {
             .getValue();
   }
 
+  // NOCOMMIT: Fix this once SolrIndexSearcher is migrated for OTEL
   private static long coreToMatchAllDocsInsertCount(SolrCore core) {
     return (long) coreToLiveDocsCacheMetrics(core).get("inserts");
   }
@@ -267,10 +260,11 @@ public class TestMainQueryCaching extends SolrTestCaseJ4 {
     Map<?, ?> body = (Map<?, ?>) (res.get("response"));
     SolrCore core = h.getCore();
     assertEquals("Bad matchAllDocs insert count", 1, coreToMatchAllDocsInsertCount(core));
-    assertEquals("Bad filterCache insert count", 0, coreToInserts(core, "filterCache"));
+    assertEquals(
+        "Bad filterCache insert count", 0, coreToInserts(core, SolrMetricTestUtils.FILTER_CACHE));
     assertEquals("Bad full sort count", 0, coreToSortCount(core, "full"));
     assertEquals("Should have exactly " + ALL_DOCS, ALL_DOCS, (long) (body.get("numFound")));
-    long queryCacheInsertCount = coreToInserts(core, "queryResultCache");
+    long queryCacheInsertCount = coreToInserts(core, SolrMetricTestUtils.QUERY_RESULT_CACHE);
     if (queryCacheInsertCount == expectCounters[0]) {
       // should be a hit, so all insert/sort-count metrics remain unchanged.
     } else {
@@ -422,7 +416,7 @@ public class TestMainQueryCaching extends SolrTestCaseJ4 {
     assertEquals(
         "Bad filterCache insert count",
         expectFilterCacheInsertCount,
-        coreToInserts(core, "filterCache"));
+        coreToInserts(core, SolrMetricTestUtils.FILTER_CACHE));
     assertEquals("Bad full sort count", expectFullSortCount, coreToSortCount(core, "full"));
     assertEquals("Bad skip sort count", expectSkipSortCount, coreToSortCount(core, "skip"));
     assertEquals(
diff --git a/solr/core/src/test/org/apache/solr/search/TestReRankQParserPlugin.java b/solr/core/src/test/org/apache/solr/search/TestReRankQParserPlugin.java
index b3e05ae5cbb..e6856a19e93 100644
--- a/solr/core/src/test/org/apache/solr/search/TestReRankQParserPlugin.java
+++ b/solr/core/src/test/org/apache/solr/search/TestReRankQParserPlugin.java
@@ -26,8 +26,7 @@ import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
-import org.apache.solr.metrics.MetricsMap;
-import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -729,18 +728,11 @@ public class TestReRankQParserPlugin extends SolrTestCaseJ4 {
         "//result/doc[4]/str[@name='id'][.='3']",
         "//result/doc[5]/str[@name='id'][.='2']");
 
-    MetricsMap metrics =
-        (MetricsMap)
-            ((SolrMetricManager.GaugeWrapper)
-                    h.getCore()
-                        .getCoreMetricManager()
-                        .getRegistry()
-                        .getMetrics()
-                        .get("CACHE.searcher.queryResultCache"))
-                .getGauge();
-    Map<String, Object> stats = metrics.getValue();
-
-    long inserts = (Long) stats.get("inserts");
+    long inserts =
+        (long)
+            SolrMetricTestUtils.getCacheSearcherOpsInserts(
+                    h.getCore(), SolrMetricTestUtils.QUERY_RESULT_CACHE)
+                .getValue();
 
     assertTrue(inserts > 0);
 
@@ -770,9 +762,11 @@ public class TestReRankQParserPlugin extends SolrTestCaseJ4 {
         "//result/doc[4]/str[@name='id'][.='2']",
         "//result/doc[5]/str[@name='id'][.='1']");
 
-    stats = metrics.getValue();
-
-    long inserts1 = (Long) stats.get("inserts");
+    long inserts1 =
+        (long)
+            SolrMetricTestUtils.getCacheSearcherOpsInserts(
+                    h.getCore(), SolrMetricTestUtils.QUERY_RESULT_CACHE)
+                .getValue();
 
     // Last query was added to the cache
     assertTrue(inserts1 > inserts);
@@ -804,8 +798,11 @@ public class TestReRankQParserPlugin extends SolrTestCaseJ4 {
         "//result/doc[4]/str[@name='id'][.='2']",
         "//result/doc[5]/str[@name='id'][.='1']");
 
-    stats = metrics.getValue();
-    long inserts2 = (Long) stats.get("inserts");
+    long inserts2 =
+        (long)
+            SolrMetricTestUtils.getCacheSearcherOpsInserts(
+                    h.getCore(), SolrMetricTestUtils.QUERY_RESULT_CACHE)
+                .getValue();
     // Last query was NOT added to the cache
     assertEquals(inserts1, inserts2);
 
diff --git a/solr/core/src/test/org/apache/solr/search/TestSolr4Spatial2.java b/solr/core/src/test/org/apache/solr/search/TestSolr4Spatial2.java
index 4456934937c..166d17e8e94 100644
--- a/solr/core/src/test/org/apache/solr/search/TestSolr4Spatial2.java
+++ b/solr/core/src/test/org/apache/solr/search/TestSolr4Spatial2.java
@@ -32,9 +32,8 @@ import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.common.params.FacetParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
-import org.apache.solr.metrics.MetricsMap;
-import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.apache.solr.util.SpatialUtils;
 import org.apache.solr.util.TestUtils;
 import org.junit.Before;
@@ -369,21 +368,15 @@ public class TestSolr4Spatial2 extends SolrTestCaseJ4 {
     if (testCache) {
       // The tricky thing is verifying the cache works correctly...
 
-      MetricsMap cacheMetrics =
-          (MetricsMap)
-              ((SolrMetricManager.GaugeWrapper)
-                      h.getCore()
-                          .getCoreMetricManager()
-                          .getRegistry()
-                          .getMetrics()
-                          .get("CACHE.searcher.perSegSpatialFieldCache_" + fieldName))
-                  .getGauge();
-      assertEquals("1", cacheMetrics.getValue().get("cumulative_inserts").toString());
-      assertEquals("0", cacheMetrics.getValue().get("cumulative_hits").toString());
+      long inserts = getSpatialFieldCacheInserts(fieldName);
+      long hits = getSpatialFieldCacheHits(fieldName);
+      assertEquals(1, inserts);
+      assertEquals(0, hits);
 
       // Repeat the query earlier
       assertJQ(sameReq, "/response/numFound==1", "/response/docs/[0]/id=='1'");
-      assertEquals("1", cacheMetrics.getValue().get("cumulative_hits").toString());
+      hits = getSpatialFieldCacheHits(fieldName);
+      assertEquals(1, hits);
 
       assertEquals("1 segment", 1, getSearcher().getRawReader().leaves().size());
       // Get key of first leaf reader -- this one contains the match for sure.
@@ -404,18 +397,8 @@ public class TestSolr4Spatial2 extends SolrTestCaseJ4 {
       Object leafKey2 = getFirstLeafReaderKey();
       // get the current instance of metrics - the old one may not represent the current cache
       // instance
-      cacheMetrics =
-          (MetricsMap)
-              ((SolrMetricManager.GaugeWrapper)
-                      h.getCore()
-                          .getCoreMetricManager()
-                          .getRegistry()
-                          .getMetrics()
-                          .get("CACHE.searcher.perSegSpatialFieldCache_" + fieldName))
-                  .getGauge();
-      assertEquals(
-          leafKey1.equals(leafKey2) ? "2" : "1",
-          cacheMetrics.getValue().get("cumulative_hits").toString());
+      hits = getSpatialFieldCacheHits(fieldName);
+      assertEquals(leafKey1.equals(leafKey2) ? 2 : 1, hits);
     }
 
     if (testHeatmap) {
@@ -456,6 +439,20 @@ public class TestSolr4Spatial2 extends SolrTestCaseJ4 {
     }
   }
 
+  private long getSpatialFieldCacheInserts(String fieldName) {
+    return (long)
+        SolrMetricTestUtils.getCacheSearcherOps(
+                h.getCore(), "perSegSpatialFieldCache_" + fieldName, "inserts")
+            .getValue();
+  }
+
+  private long getSpatialFieldCacheHits(String fieldName) {
+    return (long)
+        SolrMetricTestUtils.getCacheSearcherLookups(
+                h.getCore(), "perSegSpatialFieldCache_" + fieldName, "hit")
+            .getValue();
+  }
+
   protected SolrIndexSearcher getSearcher() {
     // neat trick; needn't deal with the hassle RefCounted
     return (SolrIndexSearcher) h.getCore().getInfoRegistry().get("searcher");
diff --git a/solr/core/src/test/org/apache/solr/search/TestSolrCachePerf.java b/solr/core/src/test/org/apache/solr/search/TestSolrCachePerf.java
index 989140b28f0..8e9e4717676 100644
--- a/solr/core/src/test/org/apache/solr/search/TestSolrCachePerf.java
+++ b/solr/core/src/test/org/apache/solr/search/TestSolrCachePerf.java
@@ -16,7 +16,10 @@
  */
 package org.apache.solr.search;
 
+import static org.apache.solr.metrics.SolrMetricProducer.NAME_ATTR;
+
 import io.opentelemetry.api.common.Attributes;
+import io.prometheus.metrics.model.snapshots.Labels;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -30,6 +33,7 @@ import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runners.model.MultipleFailureException;
@@ -106,9 +110,10 @@ public class TestSolrCachePerf extends SolrTestCaseJ4 {
       CacheRegenerator cr = new NoOpRegenerator();
       Object o = cache.init(params, null, cr);
       cache.setState(SolrCache.State.LIVE);
-      // TODO SOLR-17458: Fix test later
       cache.initializeMetrics(
-          new SolrMetricsContext(metricManager, "foo", "bar"), Attributes.empty(), "foo");
+          new SolrMetricsContext(metricManager, "foo", "bar"),
+          Attributes.of(NAME_ATTR, "foo"),
+          "foo");
       AtomicBoolean stop = new AtomicBoolean();
       SummaryStatistics perImplRatio =
           ratioStats.computeIfAbsent(clazz.getSimpleName(), c -> new SummaryStatistics());
@@ -161,8 +166,20 @@ public class TestSolrCachePerf extends SolrTestCaseJ4 {
         throw new MultipleFailureException(new ArrayList<>(exceptions));
       }
       long stopTime = System.nanoTime();
-      Map<String, Object> metrics = cache.getSolrMetricsContext().getMetricsSnapshot();
-      perImplRatio.addValue(Double.parseDouble(String.valueOf(metrics.get("CACHE.foo.hitratio"))));
+
+      var hitRatioDatapoint =
+          SolrMetricTestUtils.getGaugeDatapoint(
+              metricManager.getPrometheusMetricReader(
+                  cache.getSolrMetricsContext().getRegistryName()),
+              "solr_cache_hit_ratio",
+              Labels.builder()
+                  .label("category", "CACHE")
+                  .label("name", "foo")
+                  .label("otel_scope_name", "org.apache.solr")
+                  .build());
+
+      double hitRatio = hitRatioDatapoint != null ? hitRatioDatapoint.getValue() : 0.0;
+      perImplRatio.addValue(hitRatio);
       perImplTime.addValue((double) (stopTime - startTime));
       cache.close();
     }
diff --git a/solr/core/src/test/org/apache/solr/search/TestSolrQueryParser.java b/solr/core/src/test/org/apache/solr/search/TestSolrQueryParser.java
index 0eef541f39f..e8a8b3562f9 100644
--- a/solr/core/src/test/org/apache/solr/search/TestSolrQueryParser.java
+++ b/solr/core/src/test/org/apache/solr/search/TestSolrQueryParser.java
@@ -23,6 +23,7 @@ import static org.apache.solr.util.QueryMatchers.phraseQuery;
 import static org.apache.solr.util.QueryMatchers.termQuery;
 import static org.hamcrest.core.StringContains.containsString;
 
+import io.prometheus.metrics.model.snapshots.CounterSnapshot;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -49,14 +50,13 @@ import org.apache.solr.common.params.MapSolrParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.Utils;
-import org.apache.solr.metrics.MetricsMap;
-import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.parser.QueryParser;
 import org.apache.solr.query.FilterQuery;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.schema.NumberType;
 import org.apache.solr.schema.SchemaField;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -579,54 +579,40 @@ public class TestSolrQueryParser extends SolrTestCaseJ4 {
     delI("777");
     assertU(commit()); // arg... commit no longer "commits" unless there has been a change.
 
-    final MetricsMap filterCacheStats =
-        (MetricsMap)
-            ((SolrMetricManager.GaugeWrapper)
-                    h.getCore()
-                        .getCoreMetricManager()
-                        .getRegistry()
-                        .getMetrics()
-                        .get("CACHE.searcher.filterCache"))
-                .getGauge();
-    assertNotNull(filterCacheStats);
-    final MetricsMap queryCacheStats =
-        (MetricsMap)
-            ((SolrMetricManager.GaugeWrapper)
-                    h.getCore()
-                        .getCoreMetricManager()
-                        .getRegistry()
-                        .getMetrics()
-                        .get("CACHE.searcher.queryResultCache"))
-                .getGauge();
-
-    assertNotNull(queryCacheStats);
-
-    long inserts = (Long) filterCacheStats.getValue().get("inserts");
-    long hits = (Long) filterCacheStats.getValue().get("hits");
+    CounterSnapshot.CounterDataPointSnapshot filterInsertsInitial =
+        SolrMetricTestUtils.getCacheSearcherOpsInserts(
+            h.getCore(), SolrMetricTestUtils.FILTER_CACHE);
+    CounterSnapshot.CounterDataPointSnapshot filterHitsInitial =
+        SolrMetricTestUtils.getCacheSearcherOpsHits(h.getCore(), SolrMetricTestUtils.FILTER_CACHE);
+
+    long inserts = (long) filterInsertsInitial.getValue();
+    long hits = (long) filterHitsInitial.getValue();
 
     assertJQ(
         req("q", "doesnotexist filter(id:1) filter(qqq_s:X) filter(abcdefg)"),
         "/response/numFound==2");
 
     inserts += 3;
-    assertEquals(
-        "wrong number of inserts",
-        inserts,
-        ((Long) filterCacheStats.getValue().get("inserts")).longValue());
-    assertEquals(
-        "wrong number of hits", hits, ((Long) filterCacheStats.getValue().get("hits")).longValue());
+    CounterSnapshot.CounterDataPointSnapshot filterInsertsAfter1 =
+        SolrMetricTestUtils.getCacheSearcherOpsInserts(
+            h.getCore(), SolrMetricTestUtils.FILTER_CACHE);
+    CounterSnapshot.CounterDataPointSnapshot filterHitsAfter1 =
+        SolrMetricTestUtils.getCacheSearcherOpsHits(h.getCore(), SolrMetricTestUtils.FILTER_CACHE);
+    assertEquals("wrong number of inserts", inserts, (long) filterInsertsAfter1.getValue());
+    assertEquals("wrong number of hits", hits, (long) filterHitsAfter1.getValue());
 
     assertJQ(
         req("q", "doesnotexist2 filter(id:1) filter(qqq_s:X) filter(abcdefg)"),
         "/response/numFound==2");
 
     hits += 3;
-    assertEquals(
-        "wrong number of inserts",
-        inserts,
-        ((Long) filterCacheStats.getValue().get("inserts")).longValue());
-    assertEquals(
-        "wrong number of hits", hits, ((Long) filterCacheStats.getValue().get("hits")).longValue());
+    CounterSnapshot.CounterDataPointSnapshot filterInsertsAfter2 =
+        SolrMetricTestUtils.getCacheSearcherOpsInserts(
+            h.getCore(), SolrMetricTestUtils.FILTER_CACHE);
+    CounterSnapshot.CounterDataPointSnapshot filterHitsAfter2 =
+        SolrMetricTestUtils.getCacheSearcherOpsHits(h.getCore(), SolrMetricTestUtils.FILTER_CACHE);
+    assertEquals("wrong number of inserts", inserts, (long) filterInsertsAfter2.getValue());
+    assertEquals("wrong number of hits", hits, (long) filterHitsAfter2.getValue());
 
     // make sure normal "fq" parameters also hit the cache the same way
     assertJQ(
@@ -634,12 +620,13 @@ public class TestSolrQueryParser extends SolrTestCaseJ4 {
         "/response/numFound==0");
 
     hits += 3;
-    assertEquals(
-        "wrong number of inserts",
-        inserts,
-        ((Long) filterCacheStats.getValue().get("inserts")).longValue());
-    assertEquals(
-        "wrong number of hits", hits, ((Long) filterCacheStats.getValue().get("hits")).longValue());
+    CounterSnapshot.CounterDataPointSnapshot filterInsertsAfter3 =
+        SolrMetricTestUtils.getCacheSearcherOpsInserts(
+            h.getCore(), SolrMetricTestUtils.FILTER_CACHE);
+    CounterSnapshot.CounterDataPointSnapshot filterHitsAfter3 =
+        SolrMetricTestUtils.getCacheSearcherOpsHits(h.getCore(), SolrMetricTestUtils.FILTER_CACHE);
+    assertEquals("wrong number of inserts", inserts, (long) filterInsertsAfter3.getValue());
+    assertEquals("wrong number of hits", hits, (long) filterHitsAfter3.getValue());
 
     // try a query deeply nested in a FQ
     assertJQ(
@@ -652,12 +639,13 @@ public class TestSolrQueryParser extends SolrTestCaseJ4 {
 
     inserts += 1; // +1 for top level fq
     hits += 3;
-    assertEquals(
-        "wrong number of inserts",
-        inserts,
-        ((Long) filterCacheStats.getValue().get("inserts")).longValue());
-    assertEquals(
-        "wrong number of hits", hits, ((Long) filterCacheStats.getValue().get("hits")).longValue());
+    CounterSnapshot.CounterDataPointSnapshot filterInsertsAfter4 =
+        SolrMetricTestUtils.getCacheSearcherOpsInserts(
+            h.getCore(), SolrMetricTestUtils.FILTER_CACHE);
+    CounterSnapshot.CounterDataPointSnapshot filterHitsAfter4 =
+        SolrMetricTestUtils.getCacheSearcherOpsHits(h.getCore(), SolrMetricTestUtils.FILTER_CACHE);
+    assertEquals("wrong number of inserts", inserts, (long) filterInsertsAfter4.getValue());
+    assertEquals("wrong number of hits", hits, (long) filterHitsAfter4.getValue());
 
     // retry the complex FQ and make sure hashCode/equals works as expected w/ filter queries
     assertJQ(
@@ -669,24 +657,26 @@ public class TestSolrQueryParser extends SolrTestCaseJ4 {
         "/response/numFound==2");
 
     hits += 1; // top-level fq should have been found.
-    assertEquals(
-        "wrong number of inserts",
-        inserts,
-        ((Long) filterCacheStats.getValue().get("inserts")).longValue());
-    assertEquals(
-        "wrong number of hits", hits, ((Long) filterCacheStats.getValue().get("hits")).longValue());
+    CounterSnapshot.CounterDataPointSnapshot filterInsertsAfter5 =
+        SolrMetricTestUtils.getCacheSearcherOpsInserts(
+            h.getCore(), SolrMetricTestUtils.FILTER_CACHE);
+    CounterSnapshot.CounterDataPointSnapshot filterHitsAfter5 =
+        SolrMetricTestUtils.getCacheSearcherOpsHits(h.getCore(), SolrMetricTestUtils.FILTER_CACHE);
+    assertEquals("wrong number of inserts", inserts, (long) filterInsertsAfter5.getValue());
+    assertEquals("wrong number of hits", hits, (long) filterHitsAfter5.getValue());
 
     // try nested filter with multiple top-level args (i.e. a boolean query)
     assertJQ(req("q", "*:* +filter(id:1 filter(qqq_s:X) abcdefg)"), "/response/numFound==2");
 
     hits += 1; // the inner filter
     inserts += 1; // the outer filter
-    assertEquals(
-        "wrong number of inserts",
-        inserts,
-        ((Long) filterCacheStats.getValue().get("inserts")).longValue());
-    assertEquals(
-        "wrong number of hits", hits, ((Long) filterCacheStats.getValue().get("hits")).longValue());
+    CounterSnapshot.CounterDataPointSnapshot filterInsertsAfter6 =
+        SolrMetricTestUtils.getCacheSearcherOpsInserts(
+            h.getCore(), SolrMetricTestUtils.FILTER_CACHE);
+    CounterSnapshot.CounterDataPointSnapshot filterHitsAfter6 =
+        SolrMetricTestUtils.getCacheSearcherOpsHits(h.getCore(), SolrMetricTestUtils.FILTER_CACHE);
+    assertEquals("wrong number of inserts", inserts, (long) filterInsertsAfter6.getValue());
+    assertEquals("wrong number of hits", hits, (long) filterHitsAfter6.getValue());
 
     // test the score for a filter, and that default score is 0
     assertJQ(
diff --git a/solr/core/src/test/org/apache/solr/search/TestThinCache.java b/solr/core/src/test/org/apache/solr/search/TestThinCache.java
index de14da997ea..00c7d7a02a1 100644
--- a/solr/core/src/test/org/apache/solr/search/TestThinCache.java
+++ b/solr/core/src/test/org/apache/solr/search/TestThinCache.java
@@ -17,19 +17,19 @@
 package org.apache.solr.search;
 
 import io.opentelemetry.api.common.Attributes;
+import io.prometheus.metrics.model.snapshots.Labels;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.metrics.SolrMetricsContext;
 import org.apache.solr.util.EmbeddedSolrServerTestRule;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.apache.solr.util.TestHarness;
-import org.apache.solr.util.stats.MetricUtils;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Test;
@@ -95,13 +95,11 @@ public class TestThinCache extends SolrTestCaseJ4 {
     ThinCache<Object, Integer, String> lfuCache = new ThinCache<>();
     lfuCache.setBacking(cacheScope, backing);
     SolrMetricsContext solrMetricsContext = new SolrMetricsContext(metricManager, registry, "foo");
-    // TODO SOLR-17458: Fix test later
     lfuCache.initializeMetrics(solrMetricsContext, Attributes.empty(), scope + "-1");
 
     Object cacheScope2 = new Object();
     ThinCache<Object, Integer, String> newLFUCache = new ThinCache<>();
     newLFUCache.setBacking(cacheScope2, backing);
-    // TODO SOLR-17458: Fix test later
     newLFUCache.initializeMetrics(solrMetricsContext, Attributes.empty(), scope + "-2");
 
     Map<String, String> params = new HashMap<>();
@@ -148,6 +146,8 @@ public class TestThinCache extends SolrTestCaseJ4 {
 
   @Test
   public void testInitCore() throws Exception {
+    String thinCacheName = "myNodeLevelCacheThin";
+    String nodeCacheName = "myNodeLevelCache";
     for (int i = 0; i < 20; i++) {
       assertU(adoc("id", Integer.toString(i)));
     }
@@ -155,28 +155,60 @@ public class TestThinCache extends SolrTestCaseJ4 {
     assertQ(req("q", "*:*", "fq", "id:0"));
     assertQ(req("q", "*:*", "fq", "id:0"));
     assertQ(req("q", "*:*", "fq", "id:1"));
-    Map<String, Object> nodeMetricsSnapshot =
-        MetricUtils.convertMetrics(
-            h.getCoreContainer().getMetricManager().registry("solr.node"),
-            List.of(
-                "CACHE.nodeLevelCache/myNodeLevelCacheThin",
-                "CACHE.nodeLevelCache/myNodeLevelCache"));
-    Map<String, Object> coreMetricsSnapshot =
-        MetricUtils.convertMetrics(
-            h.getCore().getCoreMetricManager().getRegistry(),
-            List.of("CACHE.searcher.filterCache"));
-
-    // check that metrics are accessible, and the core cache writes through to the node-level cache
-    Map<String, Number> assertions = Map.of("lookups", 3L, "hits", 1L, "inserts", 2L, "size", 2);
-    for (Map.Entry<String, Number> e : assertions.entrySet()) {
-      String key = e.getKey();
-      Number val = e.getValue();
-      assertEquals(
-          val, nodeMetricsSnapshot.get("CACHE.nodeLevelCache/myNodeLevelCacheThin.".concat(key)));
-      assertEquals(val, coreMetricsSnapshot.get("CACHE.searcher.filterCache.".concat(key)));
-    }
+
+    assertEquals(
+        3L,
+        getNodeCacheLookups(thinCacheName, "hit")
+            + getNodeCacheLookups(thinCacheName, "miss")); // total lookups
+    assertEquals(1L, getNodeCacheLookups(thinCacheName, "hit"));
+    assertEquals(2L, getNodeCacheOp(thinCacheName, "inserts"));
+
+    assertEquals(2, getNodeCacheSize(thinCacheName));
 
     // for the other node-level cache, simply check that metrics are accessible
-    assertEquals(0, nodeMetricsSnapshot.get("CACHE.nodeLevelCache/myNodeLevelCache.size"));
+    assertEquals(0, getNodeCacheSize(nodeCacheName));
+  }
+
+  private long getNodeCacheOp(String cacheName, String operation) {
+    var reader = h.getCoreContainer().getMetricManager().getPrometheusMetricReader("solr.node");
+    return (long)
+        SolrMetricTestUtils.getCounterDatapoint(
+                reader,
+                "solr_node_cache_ops",
+                Labels.builder()
+                    .label("category", "CACHE")
+                    .label("ops", operation)
+                    .label("name", cacheName)
+                    .label("otel_scope_name", "org.apache.solr")
+                    .build())
+            .getValue();
+  }
+
+  private long getNodeCacheLookups(String cacheName, String result) {
+    var reader = h.getCoreContainer().getMetricManager().getPrometheusMetricReader("solr.node");
+    var builder =
+        Labels.builder()
+            .label("category", "CACHE")
+            .label("name", cacheName)
+            .label("otel_scope_name", "org.apache.solr");
+    if (result != null) builder.label("result", result);
+
+    return (long)
+        SolrMetricTestUtils.getCounterDatapoint(reader, "solr_node_cache_lookups", builder.build())
+            .getValue();
+  }
+
+  private long getNodeCacheSize(String cacheName) {
+    var reader = h.getCoreContainer().getMetricManager().getPrometheusMetricReader("solr.node");
+    return (long)
+        SolrMetricTestUtils.getGaugeDatapoint(
+                reader,
+                "solr_node_cache_size",
+                Labels.builder()
+                    .label("category", "CACHE")
+                    .label("name", cacheName)
+                    .label("otel_scope_name", "org.apache.solr")
+                    .build())
+            .getValue();
   }
 }
diff --git a/solr/core/src/test/org/apache/solr/search/join/BJQParserTest.java b/solr/core/src/test/org/apache/solr/search/join/BJQParserTest.java
index 66826e34ae8..bed4212bbca 100644
--- a/solr/core/src/test/org/apache/solr/search/join/BJQParserTest.java
+++ b/solr/core/src/test/org/apache/solr/search/join/BJQParserTest.java
@@ -23,7 +23,6 @@ import java.util.Collections;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Locale;
-import java.util.Map;
 import java.util.Objects;
 import javax.xml.xpath.XPathConstants;
 import org.apache.lucene.search.Query;
@@ -31,13 +30,12 @@ import org.apache.lucene.search.TopDocs;
 import org.apache.lucene.search.join.ScoreMode;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.SolrException;
-import org.apache.solr.metrics.MetricsMap;
-import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.search.QParser;
 import org.apache.solr.search.SyntaxError;
 import org.apache.solr.util.BaseTestHarness;
 import org.apache.solr.util.RandomNoReverseMergePolicyFactory;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.junit.After;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
@@ -49,6 +47,8 @@ public class BJQParserTest extends SolrTestCaseJ4 {
   private static final String[] klm = new String[] {"k", "l", "m"};
   private static final List<String> xyz = Arrays.asList("x", "y", "z");
   private static final String[] abcdef = new String[] {"a", "b", "c", "d", "e", "f"};
+  private static final String PER_SEG_FILTER_CACHE_NAME = "perSegFilter";
+  private static final String FILTER_CACHE_NAME = "filterCache";
 
   @ClassRule
   public static final TestRule noReverseMerge = RandomNoReverseMergePolicyFactory.createRule();
@@ -397,28 +397,15 @@ public class BJQParserTest extends SolrTestCaseJ4 {
   @Test
   public void testCacheHit() {
 
-    MetricsMap parentFilterCache =
-        (MetricsMap)
-            ((SolrMetricManager.GaugeWrapper<?>)
-                    h.getCore()
-                        .getCoreMetricManager()
-                        .getRegistry()
-                        .getMetrics()
-                        .get("CACHE.searcher.perSegFilter"))
-                .getGauge();
-    MetricsMap filterCache =
-        (MetricsMap)
-            ((SolrMetricManager.GaugeWrapper<?>)
-                    h.getCore()
-                        .getCoreMetricManager()
-                        .getRegistry()
-                        .getMetrics()
-                        .get("CACHE.searcher.filterCache"))
-                .getGauge();
-
-    Map<String, Object> parentsBefore = parentFilterCache.getValue();
-
-    Map<String, Object> filtersBefore = filterCache.getValue();
+    // Get initial values
+    long parentLookupsBefore = getCacheLookups(PER_SEG_FILTER_CACHE_NAME);
+    ;
+    long parentHitsBefore = getCacheHits(PER_SEG_FILTER_CACHE_NAME);
+    long parentInsertsBefore = getCacheInserts(PER_SEG_FILTER_CACHE_NAME);
+    ;
+
+    long filterHitsBefore = getCacheHits(FILTER_CACHE_NAME);
+    long filterInsertsBefore = getCacheInserts(FILTER_CACHE_NAME);
 
     // it should be weird enough to be uniq
     String parentFilter = "parent_s:([a TO c] [d TO f])";
@@ -433,8 +420,7 @@ public class BJQParserTest extends SolrTestCaseJ4 {
         req("q", "*:*", "fq", "{!parent which=\"" + parentFilter + "\"}"),
         "//*[@numFound='6']");
 
-    assertEquals(
-        "didn't hit fqCache yet ", 0L, delta("hits", filterCache.getValue(), filtersBefore));
+    assertEquals("didn't hit fqCache yet ", 0L, getCacheHits(FILTER_CACHE_NAME) - filterHitsBefore);
 
     assertQ(
         "filter by join",
@@ -444,28 +430,24 @@ public class BJQParserTest extends SolrTestCaseJ4 {
     assertEquals(
         "in cache mode every request lookups",
         3,
-        delta("lookups", parentFilterCache.getValue(), parentsBefore));
+        getCacheLookups(PER_SEG_FILTER_CACHE_NAME) - parentLookupsBefore);
     assertEquals(
         "last two lookups causes hits",
         2,
-        delta("hits", parentFilterCache.getValue(), parentsBefore));
+        getCacheHits(PER_SEG_FILTER_CACHE_NAME) - parentHitsBefore);
     assertEquals(
         "the first lookup gets insert",
         1,
-        delta("inserts", parentFilterCache.getValue(), parentsBefore));
+        getCacheInserts(PER_SEG_FILTER_CACHE_NAME) - parentInsertsBefore);
 
     assertEquals(
         "true join query was not in fqCache",
         0L,
-        delta("hits", filterCache.getValue(), filtersBefore));
+        getCacheHits(FILTER_CACHE_NAME) - filterHitsBefore);
     assertEquals(
         "true join query is cached in fqCache",
         1L,
-        delta("inserts", filterCache.getValue(), filtersBefore));
-  }
-
-  private long delta(String key, Map<String, Object> a, Map<String, Object> b) {
-    return (Long) a.get(key) - (Long) b.get(key);
+        getCacheInserts(FILTER_CACHE_NAME) - filterInsertsBefore);
   }
 
   @Test
@@ -662,4 +644,16 @@ public class BJQParserTest extends SolrTestCaseJ4 {
     assertU("should be noop", delI("12275"));
     assertU("most of the time", commit());
   }
+
+  private long getCacheHits(String cacheName) {
+    return (long) SolrMetricTestUtils.getCacheSearcherOpsHits(h.getCore(), cacheName).getValue();
+  }
+
+  private long getCacheLookups(String cacheName) {
+    return (long) SolrMetricTestUtils.getCacheSearcherTotalLookups(h.getCore(), cacheName);
+  }
+
+  private long getCacheInserts(String cacheName) {
+    return (long) SolrMetricTestUtils.getCacheSearcherOpsInserts(h.getCore(), cacheName).getValue();
+  }
 }
diff --git a/solr/core/src/test/org/apache/solr/search/join/TestNestedDocsSort.java b/solr/core/src/test/org/apache/solr/search/join/TestNestedDocsSort.java
index 00c58558527..710bdf75975 100644
--- a/solr/core/src/test/org/apache/solr/search/join/TestNestedDocsSort.java
+++ b/solr/core/src/test/org/apache/solr/search/join/TestNestedDocsSort.java
@@ -16,7 +16,6 @@
  */
 package org.apache.solr.search.join;
 
-import java.util.Map;
 import org.apache.lucene.search.Sort;
 import org.apache.lucene.search.SortField;
 import org.apache.solr.SolrTestCaseJ4;
@@ -26,6 +25,7 @@ import org.apache.solr.search.SolrCache;
 import org.apache.solr.search.SortSpec;
 import org.apache.solr.search.SortSpecParsing;
 import org.apache.solr.util.RandomNoReverseMergePolicyFactory;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Test;
@@ -136,23 +136,21 @@ public class TestNestedDocsSort extends SolrTestCaseJ4 {
       @SuppressWarnings({"rawtypes"})
       final SolrCache cache = req.getSearcher().getCache("perSegFilter");
       assertNotNull(cache);
-      final Map<String, Object> state = cache.getSolrMetricsContext().getMetricsSnapshot();
-      String lookupsKey = null;
-      for (String key : state.keySet()) {
-        if (key.endsWith(".lookups")) {
-          lookupsKey = key;
-          break;
-        }
-      }
-      Number before = (Number) state.get(lookupsKey);
+      var core = req.getSearcher().getCore();
+      double before =
+          SolrMetricTestUtils.getCacheSearcherTotalLookups(
+              core, SolrMetricTestUtils.PER_SEG_FILTER_CACHE);
+
       parse("childfield(name_s1,$q) asc");
-      Number after = (Number) cache.getSolrMetricsContext().getMetricsSnapshot().get(lookupsKey);
+      double after =
+          SolrMetricTestUtils.getCacheSearcherTotalLookups(
+              core, SolrMetricTestUtils.PER_SEG_FILTER_CACHE);
       assertEquals(
           "parsing bjq lookups parent filter,"
               + "parsing sort spec lookups parent and child filters, "
               + "hopefully for the purpose",
           3,
-          after.intValue() - before.intValue());
+          (int) (after - before));
     } finally {
       req.close();
     }
diff --git a/solr/core/src/test/org/apache/solr/search/join/TestScoreJoinQPScore.java b/solr/core/src/test/org/apache/solr/search/join/TestScoreJoinQPScore.java
index e3433329d06..662cda05439 100644
--- a/solr/core/src/test/org/apache/solr/search/join/TestScoreJoinQPScore.java
+++ b/solr/core/src/test/org/apache/solr/search/join/TestScoreJoinQPScore.java
@@ -16,25 +16,23 @@
  */
 package org.apache.solr.search.join;
 
-import com.codahale.metrics.Metric;
+import io.prometheus.metrics.model.snapshots.CounterSnapshot;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
-import java.util.Map;
 import java.util.Random;
 import org.apache.lucene.search.BoostQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.join.ScoreMode;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.SolrException;
-import org.apache.solr.metrics.MetricsMap;
-import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestInfo;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.search.QParser;
 import org.apache.solr.search.SolrCache;
+import org.apache.solr.util.SolrMetricTestUtils;
 import org.junit.BeforeClass;
 import org.junit.Ignore;
 
@@ -228,19 +226,17 @@ public class TestScoreJoinQPScore extends SolrTestCaseJ4 {
   public void testCacheHit() throws Exception {
     indexDataForScoring();
 
-    Map<String, Metric> metrics =
-        h.getCoreContainer()
-            .getMetricManager()
-            .registry(h.getCore().getCoreMetricManager().getRegistryName())
-            .getMetrics();
-
-    @SuppressWarnings("rawtypes")
-    MetricsMap mm =
-        (MetricsMap)
-            ((SolrMetricManager.GaugeWrapper) metrics.get("CACHE.searcher.queryResultCache"))
-                .getGauge();
     {
-      Map<String, Object> statPre = mm.getValue();
+      double lookupsPre =
+          SolrMetricTestUtils.getCacheSearcherTotalLookups(
+              h.getCore(), SolrMetricTestUtils.QUERY_RESULT_CACHE);
+      CounterSnapshot.CounterDataPointSnapshot hitsPre =
+          SolrMetricTestUtils.getCacheSearcherOpsHits(
+              h.getCore(), SolrMetricTestUtils.QUERY_RESULT_CACHE);
+      CounterSnapshot.CounterDataPointSnapshot insertsPre =
+          SolrMetricTestUtils.getCacheSearcherOpsInserts(
+              h.getCore(), SolrMetricTestUtils.QUERY_RESULT_CACHE);
+
       h.query(
           req(
               "q",
@@ -249,11 +245,21 @@ public class TestScoreJoinQPScore extends SolrTestCaseJ4 {
               "id",
               "omitHeader",
               "true"));
-      assertHitOrInsert(mm.getValue(), statPre);
+
+      assertHitOrInsert(lookupsPre, hitsPre, insertsPre);
     }
 
     {
-      Map<String, Object> statPre = mm.getValue();
+      double lookupsPre =
+          SolrMetricTestUtils.getCacheSearcherTotalLookups(
+              h.getCore(), SolrMetricTestUtils.QUERY_RESULT_CACHE);
+      CounterSnapshot.CounterDataPointSnapshot hitsPre =
+          SolrMetricTestUtils.getCacheSearcherOpsHits(
+              h.getCore(), SolrMetricTestUtils.QUERY_RESULT_CACHE);
+      CounterSnapshot.CounterDataPointSnapshot insertsPre =
+          SolrMetricTestUtils.getCacheSearcherOpsInserts(
+              h.getCore(), SolrMetricTestUtils.QUERY_RESULT_CACHE);
+
       h.query(
           req(
               "q",
@@ -262,11 +268,20 @@ public class TestScoreJoinQPScore extends SolrTestCaseJ4 {
               "id",
               "omitHeader",
               "true"));
-      assertHit(mm.getValue(), statPre);
+
+      assertHit(lookupsPre, hitsPre, insertsPre);
     }
 
     {
-      Map<String, Object> statPre = mm.getValue();
+      double lookupsPre =
+          SolrMetricTestUtils.getCacheSearcherTotalLookups(
+              h.getCore(), SolrMetricTestUtils.QUERY_RESULT_CACHE);
+      CounterSnapshot.CounterDataPointSnapshot hitsPre =
+          SolrMetricTestUtils.getCacheSearcherOpsHits(
+              h.getCore(), SolrMetricTestUtils.QUERY_RESULT_CACHE);
+      CounterSnapshot.CounterDataPointSnapshot insertsPre =
+          SolrMetricTestUtils.getCacheSearcherOpsInserts(
+              h.getCore(), SolrMetricTestUtils.QUERY_RESULT_CACHE);
 
       Random r = random();
       boolean changed = false;
@@ -300,9 +315,19 @@ public class TestScoreJoinQPScore extends SolrTestCaseJ4 {
                   "id",
                   "omitHeader",
                   "true"));
-      assertInsert(mm.getValue(), statPre);
 
-      statPre = mm.getValue();
+      assertInsert(lookupsPre, hitsPre, insertsPre);
+
+      double lookupsPreRepeat =
+          SolrMetricTestUtils.getCacheSearcherTotalLookups(
+              h.getCore(), SolrMetricTestUtils.QUERY_RESULT_CACHE);
+      CounterSnapshot.CounterDataPointSnapshot hitsPreRepeat =
+          SolrMetricTestUtils.getCacheSearcherOpsHits(
+              h.getCore(), SolrMetricTestUtils.QUERY_RESULT_CACHE);
+      CounterSnapshot.CounterDataPointSnapshot insertsPreRepeat =
+          SolrMetricTestUtils.getCacheSearcherOpsInserts(
+              h.getCore(), SolrMetricTestUtils.QUERY_RESULT_CACHE);
+
       final String repeat =
           h.query(
               req(
@@ -321,7 +346,8 @@ public class TestScoreJoinQPScore extends SolrTestCaseJ4 {
                   "id",
                   "omitHeader",
                   "true"));
-      assertHit(mm.getValue(), statPre);
+
+      assertHit(lookupsPreRepeat, hitsPreRepeat, insertsPreRepeat);
 
       assertEquals("lowercase shouldn't change anything", resp, repeat);
 
@@ -342,7 +368,8 @@ public class TestScoreJoinQPScore extends SolrTestCaseJ4 {
     // however, it might be better to extract this method into a separate suite
     // for a while let's nuke a cache content, in case of repetitions
     @SuppressWarnings("rawtypes")
-    SolrCache cache = (SolrCache) h.getCore().getInfoRegistry().get("queryResultCache");
+    SolrCache cache =
+        (SolrCache) h.getCore().getInfoRegistry().get(SolrMetricTestUtils.QUERY_RESULT_CACHE);
     cache.clear();
   }
 
@@ -353,27 +380,71 @@ public class TestScoreJoinQPScore extends SolrTestCaseJ4 {
     return l.get(r.nextInt(l.size()));
   }
 
-  private void assertInsert(Map<String, Object> current, final Map<String, Object> statPre) {
-    assertEquals("it lookups", 1, delta("lookups", current, statPre));
-    assertEquals("it doesn't hit", 0, delta("hits", current, statPre));
-    assertEquals("it inserts", 1, delta("inserts", current, statPre));
+  private void assertInsert(
+      double lookupsPre,
+      CounterSnapshot.CounterDataPointSnapshot hitsPre,
+      CounterSnapshot.CounterDataPointSnapshot insertsPre) {
+    double lookupsPost =
+        SolrMetricTestUtils.getCacheSearcherTotalLookups(
+            h.getCore(), SolrMetricTestUtils.QUERY_RESULT_CACHE);
+    CounterSnapshot.CounterDataPointSnapshot hitsPost =
+        SolrMetricTestUtils.getCacheSearcherOpsHits(
+            h.getCore(), SolrMetricTestUtils.QUERY_RESULT_CACHE);
+    CounterSnapshot.CounterDataPointSnapshot insertsPost =
+        SolrMetricTestUtils.getCacheSearcherOpsInserts(
+            h.getCore(), SolrMetricTestUtils.QUERY_RESULT_CACHE);
+
+    assertEquals("it lookups", 1, delta(lookupsPost, lookupsPre));
+    assertEquals("it doesn't hit", 0, delta(hitsPost, hitsPre));
+    assertEquals("it inserts", 1, delta(insertsPost, insertsPre));
   }
 
-  private void assertHit(Map<String, Object> current, final Map<String, Object> statPre) {
-    assertEquals("it lookups", 1, delta("lookups", current, statPre));
-    assertEquals("it hits", 1, delta("hits", current, statPre));
-    assertEquals("it doesn't insert", 0, delta("inserts", current, statPre));
+  private void assertHit(
+      double lookupsPre,
+      CounterSnapshot.CounterDataPointSnapshot hitsPre,
+      CounterSnapshot.CounterDataPointSnapshot insertsPre) {
+    double lookupsPost =
+        SolrMetricTestUtils.getCacheSearcherTotalLookups(
+            h.getCore(), SolrMetricTestUtils.QUERY_RESULT_CACHE);
+    CounterSnapshot.CounterDataPointSnapshot hitsPost =
+        SolrMetricTestUtils.getCacheSearcherOpsHits(
+            h.getCore(), SolrMetricTestUtils.QUERY_RESULT_CACHE);
+    CounterSnapshot.CounterDataPointSnapshot insertsPost =
+        SolrMetricTestUtils.getCacheSearcherOpsInserts(
+            h.getCore(), SolrMetricTestUtils.QUERY_RESULT_CACHE);
+
+    assertEquals("it lookups", 1, delta(lookupsPost, lookupsPre));
+    assertEquals("it hits", 1, delta(hitsPost, hitsPre));
+    assertEquals("it doesn't insert", 0, delta(insertsPost, insertsPre));
   }
 
-  private void assertHitOrInsert(Map<String, Object> current, final Map<String, Object> statPre) {
-    assertEquals("it lookups", 1, delta("lookups", current, statPre));
-    final long mayHit = delta("hits", current, statPre);
+  private void assertHitOrInsert(
+      double lookupsPre,
+      CounterSnapshot.CounterDataPointSnapshot hitsPre,
+      CounterSnapshot.CounterDataPointSnapshot insertsPre) {
+    double lookupsPost =
+        SolrMetricTestUtils.getCacheSearcherTotalLookups(
+            h.getCore(), SolrMetricTestUtils.QUERY_RESULT_CACHE);
+    CounterSnapshot.CounterDataPointSnapshot hitsPost =
+        SolrMetricTestUtils.getCacheSearcherOpsHits(
+            h.getCore(), SolrMetricTestUtils.QUERY_RESULT_CACHE);
+    CounterSnapshot.CounterDataPointSnapshot insertsPost =
+        SolrMetricTestUtils.getCacheSearcherOpsInserts(
+            h.getCore(), SolrMetricTestUtils.QUERY_RESULT_CACHE);
+
+    assertEquals("it lookups", 1, delta(lookupsPost, lookupsPre));
+    final long mayHit = delta(hitsPost, hitsPre);
     assertTrue("it may hit", 0 == mayHit || 1 == mayHit);
-    assertEquals("or insert on cold", 1, delta("inserts", current, statPre) + mayHit);
+    assertEquals("or insert on cold", 1, delta(insertsPost, insertsPre) + mayHit);
+  }
+
+  private long delta(
+      CounterSnapshot.CounterDataPointSnapshot post, CounterSnapshot.CounterDataPointSnapshot pre) {
+    return delta(post.getValue(), pre.getValue());
   }
 
-  private long delta(String key, Map<String, Object> a, Map<String, Object> b) {
-    return (Long) a.get(key) - (Long) b.get(key);
+  private long delta(double post, double pre) {
+    return (long) post - (long) pre;
   }
 
   private void indexDataForScoring() {
diff --git a/solr/test-framework/src/java/org/apache/solr/util/SolrMetricTestUtils.java b/solr/test-framework/src/java/org/apache/solr/util/SolrMetricTestUtils.java
index 3531094cfc3..1fb3b0082cf 100644
--- a/solr/test-framework/src/java/org/apache/solr/util/SolrMetricTestUtils.java
+++ b/solr/test-framework/src/java/org/apache/solr/util/SolrMetricTestUtils.java
@@ -45,6 +45,12 @@ public final class SolrMetricTestUtils {
   private static final int MAX_ITERATIONS = 100;
   private static final SolrInfoBean.Category CATEGORIES[] = SolrInfoBean.Category.values();
 
+  // Cache name constants
+  public static final String QUERY_RESULT_CACHE = "queryResultCache";
+  public static final String FILTER_CACHE = "filterCache";
+  public static final String DOCUMENT_CACHE = "documentCache";
+  public static final String PER_SEG_FILTER_CACHE = "perSegFilter";
+
   public static String getRandomScope(Random random) {
     return getRandomScope(random, random.nextBoolean());
   }
@@ -246,6 +252,11 @@ public final class SolrMetricTestUtils {
     return getDataPoint(reader, metricName, labels, CounterSnapshot.CounterDataPointSnapshot.class);
   }
 
+  public static GaugeSnapshot.GaugeDataPointSnapshot getGaugeDatapoint(
+      PrometheusMetricReader reader, String metricName, Labels labels) {
+    return getDataPoint(reader, metricName, labels, GaugeSnapshot.GaugeDataPointSnapshot.class);
+  }
+
   public static HistogramSnapshot.HistogramDataPointSnapshot getHistogramDatapoint(
       PrometheusMetricReader reader, String metricName, Labels labels) {
     return getDataPoint(
@@ -304,6 +315,47 @@ public final class SolrMetricTestUtils {
             .build());
   }
 
+  public static CounterSnapshot.CounterDataPointSnapshot getCacheSearcherOps(
+      SolrCore core, String cacheName, String operation) {
+    return SolrMetricTestUtils.getCounterDatapoint(
+        core,
+        "solr_searcher_cache_ops",
+        SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+            .label("category", "CACHE")
+            .label("ops", operation)
+            .label("name", cacheName)
+            .build());
+  }
+
+  public static CounterSnapshot.CounterDataPointSnapshot getCacheSearcherLookups(
+      SolrCore core, String cacheName, String result) {
+    var builder =
+        SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+            .label("category", "CACHE")
+            .label("name", cacheName)
+            .label("result", result);
+    return SolrMetricTestUtils.getCounterDatapoint(
+        core, "solr_searcher_cache_lookups", builder.build());
+  }
+
+  public static CounterSnapshot.CounterDataPointSnapshot getCacheSearcherOpsHits(
+      SolrCore core, String cacheName) {
+    return SolrMetricTestUtils.getCacheSearcherLookups(core, cacheName, "hit");
+  }
+
+  public static double getCacheSearcherTotalLookups(SolrCore core, String cacheName) {
+    // Calculate lookup total as hits + misses
+    var hitDatapoint = SolrMetricTestUtils.getCacheSearcherOpsHits(core, cacheName);
+    var missDatapoint = SolrMetricTestUtils.getCacheSearcherLookups(core, cacheName, "miss");
+
+    return hitDatapoint.getValue() + missDatapoint.getValue();
+  }
+
+  public static CounterSnapshot.CounterDataPointSnapshot getCacheSearcherOpsInserts(
+      SolrCore core, String cacheName) {
+    return SolrMetricTestUtils.getCacheSearcherOps(core, cacheName, "inserts");
+  }
+
   public static class TestSolrMetricProducer implements SolrMetricProducer {
     SolrMetricsContext solrMetricsContext;
     private final Map<String, io.opentelemetry.api.metrics.LongCounter> counters = new HashMap<>();


(solr) 16/33: SOLR-17806: Recreate OTEL metric registries on core rename/swapping (#3458)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 09125e74c3641c0b0a4ade332d0351dc4efa20a8
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Wed Aug 27 12:28:26 2025 -0400

    SOLR-17806: Recreate OTEL metric registries on core rename/swapping (#3458)
    
    * Migrate core swapping and renaming
    
    * Broken tests
    
    * Cleanup from comments
    
    * Changes from review
    
    * Cleanup
    
    * Add comments
---
 .../java/org/apache/solr/core/CoreContainer.java   |   9 +-
 .../src/java/org/apache/solr/core/SolrCore.java    |   1 -
 .../src/java/org/apache/solr/core/SolrCores.java   |   8 +-
 .../apache/solr/handler/admin/MetricsHandler.java  |   2 +-
 .../apache/solr/metrics/SolrCoreMetricManager.java |  82 ++++++++++-----
 .../org/apache/solr/metrics/SolrMetricManager.java |  66 ++----------
 .../solr/metrics/SolrCoreMetricManagerTest.java    |  48 +++++++++
 .../apache/solr/metrics/SolrMetricManagerTest.java |  42 --------
 .../apache/solr/metrics/SolrMetricTestUtils.java   | 114 +++++++++++++++++++++
 .../solr/metrics/SolrMetricsIntegrationTest.java   |  99 ++++++------------
 .../pages/major-changes-in-solr-10.adoc            |   8 +-
 .../solr/common/cloud/NodesSysPropsCacher.java     |   3 +
 .../solr/client/solrj/SolrJMetricTestUtils.java    |   3 +-
 .../solr/client/solrj/request/TestCoreAdmin.java   |  68 ++++++++----
 14 files changed, 319 insertions(+), 234 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index cd6bf740b36..808ee59f6bb 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -126,7 +126,6 @@ import org.apache.solr.jersey.InjectionFactories;
 import org.apache.solr.jersey.JerseyAppHandlerCache;
 import org.apache.solr.logging.LogWatcher;
 import org.apache.solr.logging.MDCLoggingContext;
-import org.apache.solr.metrics.SolrCoreMetricManager;
 import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.metrics.SolrMetricProducer;
 import org.apache.solr.metrics.SolrMetricsContext;
@@ -2224,18 +2223,18 @@ public class CoreContainer {
     SolrIdentifierValidator.validateCoreName(toName);
     try (SolrCore core = getCore(name)) {
       if (core != null) {
-        String oldRegistryName = core.getCoreMetricManager().getRegistryName();
-        String newRegistryName = SolrCoreMetricManager.createRegistryName(core, toName);
-        metricManager.swapRegistries(oldRegistryName, newRegistryName);
         // The old coreDescriptor is obsolete, so remove it. registerCore will put it back.
         CoreDescriptor cd = core.getCoreDescriptor();
         solrCores.removeCoreDescriptor(cd);
         cd.setProperty("name", toName);
         solrCores.addCoreDescriptor(cd);
+        // Before setting the new name, delete the old metrics registry then reregister metrics
+        // under the new core name
+        metricManager.removeRegistry(core.getCoreMetricManager().getRegistryName());
         core.setName(toName);
+        core.getCoreMetricManager().reregisterCoreMetrics();
         registerCore(cd, core, true, false);
         SolrCore old = solrCores.remove(name);
-
         coresLocator.rename(this, old.getCoreDescriptor(), core.getCoreDescriptor());
       }
     }
diff --git a/solr/core/src/java/org/apache/solr/core/SolrCore.java b/solr/core/src/java/org/apache/solr/core/SolrCore.java
index 50c40530937..5f1760673ab 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrCore.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java
@@ -554,7 +554,6 @@ public class SolrCore implements SolrInfoBean, Closeable {
     assert this.name != null;
     assert coreDescriptor.getCloudDescriptor() == null : "Cores are not renamed in SolrCloud";
     this.name = Objects.requireNonNull(v);
-    coreMetricManager.afterCoreRename();
   }
 
   /**
diff --git a/solr/core/src/java/org/apache/solr/core/SolrCores.java b/solr/core/src/java/org/apache/solr/core/SolrCores.java
index 39b1f68b155..8aef23b0555 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrCores.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrCores.java
@@ -257,12 +257,8 @@ public class SolrCores {
       cores.put(n1, c0);
       c0.setName(n1);
       c1.setName(n0);
-
-      container
-          .getMetricManager()
-          .swapRegistries(
-              c0.getCoreMetricManager().getRegistryName(),
-              c1.getCoreMetricManager().getRegistryName());
+      c0.getCoreMetricManager().reregisterCoreMetrics();
+      c1.getCoreMetricManager().reregisterCoreMetrics();
     }
   }
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java
index f55c0c4b177..633ccc7bb00 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java
@@ -294,7 +294,7 @@ public class MetricsHandler extends RequestHandlerBase implements PermissionName
       final String registryName = unescape(parts[0]);
       final String metricName = unescape(parts[1]);
       final String propertyName = parts.length > 2 ? unescape(parts[2]) : null;
-      if (!metricManager.hasRegistry(registryName)) {
+      if (!metricManager.hasDropwizardRegistry(registryName)) {
         errors.add(key, "registry '" + registryName + "' not found");
         continue;
       }
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
index 01122a779e0..6af631d6ebf 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
@@ -23,6 +23,8 @@ import io.opentelemetry.api.common.AttributeKey;
 import io.opentelemetry.api.common.Attributes;
 import java.io.Closeable;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 import org.apache.solr.cloud.CloudDescriptor;
 import org.apache.solr.common.util.Utils;
 import org.apache.solr.core.CoreContainer;
@@ -41,7 +43,6 @@ public class SolrCoreMetricManager implements Closeable {
   public static final AttributeKey<String> CORE_ATTR = AttributeKey.stringKey("core");
   public static final AttributeKey<String> SHARD_ATTR = AttributeKey.stringKey("shard");
   public static final AttributeKey<String> REPLICA_ATTR = AttributeKey.stringKey("replica");
-  public static final AttributeKey<String> SCOPE_ATTR = AttributeKey.stringKey("scope");
 
   private final SolrCore core;
   private SolrMetricsContext solrMetricsContext;
@@ -52,6 +53,12 @@ public class SolrCoreMetricManager implements Closeable {
   private String leaderRegistryName;
   private boolean cloudMode;
 
+  // Track all metric producers registered for this core so we can re-initialize them during core
+  // rename
+  private final List<MetricProducerInfo> registeredProducers = new ArrayList<>();
+
+  private record MetricProducerInfo(SolrMetricProducer producer, String scope) {}
+
   /**
    * Constructs a metric manager.
    *
@@ -105,34 +112,46 @@ public class SolrCoreMetricManager implements Closeable {
   }
 
   /**
-   * Make sure that metrics already collected that correspond to the old core name are carried over
-   * and will be used under the new core name. This method also reloads reporters so that they use
-   * the new core name.
+   * Re-register all metric producers associated with this core. This recreates the metric registry
+   * resetting its state and recreating its attributes for all tracked registered producers.
    */
-  // NOCOMMIT SOLR-17458: Update for core renaming
-  public void afterCoreRename() {
-    assert core.getCoreDescriptor().getCloudDescriptor() == null;
-    String oldRegistryName = solrMetricsContext.getRegistryName();
-    String oldLeaderRegistryName = leaderRegistryName;
-    String newRegistryName =
-        createRegistryName(cloudMode, collectionName, shardName, replicaName, core.getName());
-    leaderRegistryName = createLeaderRegistryName(cloudMode, collectionName, shardName);
-    if (oldRegistryName.equals(newRegistryName)) {
-      return;
-    }
-    // close old reporters
-    metricManager.closeReporters(oldRegistryName, solrMetricsContext.getTag());
-    if (oldLeaderRegistryName != null) {
-      metricManager.closeReporters(oldLeaderRegistryName, solrMetricsContext.getTag());
-    }
-    solrMetricsContext =
-        new SolrMetricsContext(metricManager, newRegistryName, solrMetricsContext.getTag());
-    // load reporters again, using the new core name
-    loadReporters();
+  public void reregisterCoreMetrics() {
+    this.solrMetricsContext =
+        new SolrMetricsContext(
+            metricManager,
+            createRegistryName(cloudMode, collectionName, shardName, replicaName, core.getName()),
+            solrMetricsContext.getTag());
+    metricManager.removeRegistry(solrMetricsContext.getRegistryName());
+    if (leaderRegistryName != null) metricManager.removeRegistry(leaderRegistryName);
+
+    // TODO: We are going to recreate the attributes and re-initialize/reregister metrics from
+    // tracked producers.
+    // There is some possible improvement that can be done here to not have to duplicate code in
+    // registerMetricProducer
+    var attributes =
+        Attributes.builder()
+            .put(CORE_ATTR, core.getName())
+            .put(COLLECTION_ATTR, collectionName)
+            .put(SHARD_ATTR, shardName)
+            .put(REPLICA_ATTR, replicaName)
+            .build();
+
+    core.initializeMetrics(solrMetricsContext, attributes, core.getName());
+
+    registeredProducers.forEach(
+        metricProducer -> {
+          var producerAttributes = attributes.toBuilder();
+          if (metricProducer.scope().startsWith("/"))
+            producerAttributes.put(HANDLER_ATTR, metricProducer.scope);
+          metricProducer.producer.initializeMetrics(
+              solrMetricsContext, producerAttributes.build(), metricProducer.scope);
+        });
   }
 
   /**
-   * Registers a mapping of name/metric's with the manager's metric registry.
+   * Registers a mapping of name/metric's with the manager's metric registry and creates the base
+   * set of attributes for core level metrics. All metric producers are tracked for re-registering
+   * in the case of core swapping/renaming
    *
    * @param scope the scope of the metrics to be registered (e.g. `/admin/ping`)
    * @param producer producer of metrics to be registered
@@ -147,11 +166,16 @@ public class SolrCoreMetricManager implements Closeable {
               + producer);
     }
 
-    // NOCOMMIT SOLR-17458: These attributes may not work for standalone mode and maybe make the
-    // scope attribute optional
+    // Track this producer for potential re-initialization during core rename
+    registeredProducers.add(new MetricProducerInfo(producer, scope));
+
+    // TODO: We initialize metrics with attributes of the core. This happens again in
+    // reregisterCoreMetrics
+    // There is some possible improvement that can be done here to not have to duplicate code in
+    // reregisterCoreMetrics
     var attributesBuilder =
         Attributes.builder()
-            .put(CORE_ATTR, core.getCoreDescriptor().getName())
+            .put(CORE_ATTR, core.getName())
             .put(COLLECTION_ATTR, collectionName)
             .put(SHARD_ATTR, shardName)
             .put(REPLICA_ATTR, replicaName);
@@ -179,6 +203,8 @@ public class SolrCoreMetricManager implements Closeable {
     }
     metricManager.unregisterGauges(
         solrMetricsContext.getRegistryName(), solrMetricsContext.getTag());
+
+    registeredProducers.clear();
   }
 
   public SolrMetricsContext getSolrMetricsContext() {
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
index 367bc9da886..a950e1520fb 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
@@ -630,17 +630,17 @@ public class SolrMetricManager {
    * @param name registry name
    * @return true if this name points to a registry that already exists, false otherwise
    */
-  // TODO SOLR-17458: We may not need
   public boolean hasRegistry(String name) {
+    return meterProviderAndReaders.containsKey(enforcePrefix(name));
+  }
+
+  // NOCOMMIT: Used in filtering. Will remove later
+  public boolean hasDropwizardRegistry(String name) {
     Set<String> names = registryNames();
     name = enforcePrefix(name);
     return names.contains(name);
   }
 
-  public boolean hasMeterProvider(String name) {
-    return meterProviderAndReaders.containsKey(enforcePrefix(name));
-  }
-
   /**
    * Return set of existing registry names that match a regex pattern
    *
@@ -772,68 +772,15 @@ public class SolrMetricManager {
    *
    * @param registry name of the registry to remove
    */
-  // TODO SOLR-17458: You can't delete OTEL meters
   public void removeRegistry(String registry) {
-    // NOCOMMIT Remove all closing Dropwizard registries
-    // close any reporters for this registry first
-    closeReporters(registry, null);
-    // make sure we use a name with prefix
-    registry = enforcePrefix(registry);
-    if (isSharedRegistry(registry)) {
-      SharedMetricRegistries.remove(registry);
-    } else {
-      swapLock.lock();
-      try {
-        registries.remove(registry);
-      } finally {
-        swapLock.unlock();
-      }
-    }
     meterProviderAndReaders.computeIfPresent(
-        registry,
+        enforcePrefix(registry),
         (key, meterAndReader) -> {
           meterAndReader.sdkMeterProvider().close();
           return null;
         });
   }
 
-  /**
-   * Swap registries. This is useful eg. during {@link org.apache.solr.core.SolrCore} rename or swap
-   * operations. NOTE: this operation is not supported for shared registries.
-   *
-   * @param registry1 source registry
-   * @param registry2 target registry. Note: when used after core rename the target registry doesn't
-   *     exist, so the swap operation will only rename the existing registry without creating an
-   *     empty one under the previous name.
-   */
-  // NOCOMMIT SOLR-17458: Don't think we need
-  public void swapRegistries(String registry1, String registry2) {
-    registry1 = enforcePrefix(registry1);
-    registry2 = enforcePrefix(registry2);
-    if (isSharedRegistry(registry1) || isSharedRegistry(registry2)) {
-      throw new UnsupportedOperationException(
-          "Cannot swap shared registry: " + registry1 + ", " + registry2);
-    }
-    swapLock.lock();
-    try {
-      MetricRegistry from = registries.get(registry1);
-      MetricRegistry to = registries.get(registry2);
-      if (from == to) {
-        return;
-      }
-      MetricRegistry reg1 = registries.remove(registry1);
-      MetricRegistry reg2 = registries.remove(registry2);
-      if (reg2 != null) {
-        registries.put(registry1, reg2);
-      }
-      if (reg1 != null) {
-        registries.put(registry2, reg1);
-      }
-    } finally {
-      swapLock.unlock();
-    }
-  }
-
   /**
    * Potential conflict resolution strategies when attempting to register a new metric that already
    * exists
@@ -1254,6 +1201,7 @@ public class SolrMetricManager {
    * @param group selected group, not null
    * @param registryNames optional child registry name elements
    */
+  // NOCOMMIT: Come back and cleanup and remove all reporters code
   public void loadReporters(
       PluginInfo[] pluginInfos,
       SolrResourceLoader loader,
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrCoreMetricManagerTest.java b/solr/core/src/test/org/apache/solr/metrics/SolrCoreMetricManagerTest.java
index 365a997339f..93626397dc9 100644
--- a/solr/core/src/test/org/apache/solr/metrics/SolrCoreMetricManagerTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrCoreMetricManagerTest.java
@@ -19,6 +19,8 @@ package org.apache.solr.metrics;
 import com.codahale.metrics.Counter;
 import com.codahale.metrics.Metric;
 import com.codahale.metrics.MetricRegistry;
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.Attributes;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
@@ -43,6 +45,8 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
+// NOCOMMIT: Need to fix up these tests to use the new SolrMetricTestUtils once we move off of
+// Dropwizard
 public class SolrCoreMetricManagerTest extends SolrTestCaseJ4 {
   private static final int MAX_ITERATIONS = 100;
 
@@ -177,6 +181,50 @@ public class SolrCoreMetricManagerTest extends SolrTestCaseJ4 {
     }
   }
 
+  @Test
+  public void testReregisterMetrics() {
+    Random random = random();
+
+    Map<String, Long> initialMetrics =
+        SolrMetricTestUtils.getRandomPrometheusMetricsWithReplacements(random, new HashMap<>());
+    var initialProducer = new SolrMetricTestUtils.TestSolrMetricProducer(coreName, initialMetrics);
+    coreMetricManager.registerMetricProducer(
+        SolrMetricTestUtils.getRandomScope(random, true), initialProducer);
+
+    var labels = SolrMetricTestUtils.newStandaloneLabelsBuilder(h.getCore()).build();
+
+    String randomMetricName = initialMetrics.entrySet().iterator().next().getKey();
+
+    long actualValue =
+        (long)
+            SolrMetricTestUtils.getCounterDatapoint(h.getCore(), randomMetricName, labels)
+                .getValue();
+    long expectedValue = initialMetrics.get(randomMetricName);
+
+    assertEquals(expectedValue, actualValue);
+
+    // Change the metric value in OTEL
+    initialProducer
+        .getCounters()
+        .get(randomMetricName)
+        .add(10L, Attributes.of(AttributeKey.stringKey("core"), coreName));
+
+    long newActualValue =
+        (long)
+            SolrMetricTestUtils.getCounterDatapoint(h.getCore(), randomMetricName, labels)
+                .getValue();
+    assertEquals(expectedValue + 10L, newActualValue);
+
+    // Reregister the core metrics which should reset the metric value back to the initial value
+    coreMetricManager.reregisterCoreMetrics();
+
+    long reregisteredValue =
+        (long)
+            SolrMetricTestUtils.getCounterDatapoint(h.getCore(), randomMetricName, labels)
+                .getValue();
+    assertEquals(expectedValue, reregisteredValue);
+  }
+
   @Test
   public void testNonCloudRegistryName() {
     String registryName = h.getCore().getCoreMetricManager().getRegistryName();
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java b/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
index d5701d15111..1d324ab7de4 100644
--- a/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
@@ -64,48 +64,6 @@ public class SolrMetricManagerTest extends SolrTestCaseJ4 {
     this.reader = metricManager.getPrometheusMetricReader(METER_PROVIDER_NAME);
   }
 
-  // NOCOMMIT: We might not be supported core swapping in 10. Maybe remove this test
-  @Test
-  public void testSwapRegistries() {
-    Random r = random();
-
-    SolrMetricManager metricManager = new SolrMetricManager();
-
-    Map<String, Counter> metrics1 = SolrMetricTestUtils.getRandomMetrics(r, true);
-    Map<String, Counter> metrics2 = SolrMetricTestUtils.getRandomMetrics(r, true);
-    String fromName = "from-" + TestUtil.randomSimpleString(r, 1, 10);
-    String toName = "to-" + TestUtil.randomSimpleString(r, 1, 10);
-    // register test metrics
-    for (Map.Entry<String, Counter> entry : metrics1.entrySet()) {
-      metricManager.registerMetric(
-          null, fromName, entry.getValue(), false, entry.getKey(), "metrics1");
-    }
-    for (Map.Entry<String, Counter> entry : metrics2.entrySet()) {
-      metricManager.registerMetric(
-          null, toName, entry.getValue(), false, entry.getKey(), "metrics2");
-    }
-    assertEquals(metrics1.size(), metricManager.registry(fromName).getMetrics().size());
-    assertEquals(metrics2.size(), metricManager.registry(toName).getMetrics().size());
-
-    // swap
-    metricManager.swapRegistries(fromName, toName);
-    // check metrics
-    Map<String, Metric> fromMetrics = metricManager.registry(fromName).getMetrics();
-    assertEquals(metrics2.size(), fromMetrics.size());
-    for (Map.Entry<String, Counter> entry : metrics2.entrySet()) {
-      Object value = fromMetrics.get(SolrMetricManager.mkName(entry.getKey(), "metrics2"));
-      assertNotNull(value);
-      assertEquals(entry.getValue(), value);
-    }
-    Map<String, Metric> toMetrics = metricManager.registry(toName).getMetrics();
-    assertEquals(metrics1.size(), toMetrics.size());
-    for (Map.Entry<String, Counter> entry : metrics1.entrySet()) {
-      Object value = toMetrics.get(SolrMetricManager.mkName(entry.getKey(), "metrics1"));
-      assertNotNull(value);
-      assertEquals(entry.getValue(), value);
-    }
-  }
-
   // NOCOMMIT: Migration of this to OTEL isn't possible. You can't register instruments to a
   // meterprovider that the provider itself didn't create
   @Test
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java b/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java
index f7878f19c8e..425aa45468e 100644
--- a/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java
@@ -17,6 +17,7 @@
 package org.apache.solr.metrics;
 
 import com.codahale.metrics.Counter;
+import io.opentelemetry.api.common.AttributeKey;
 import io.opentelemetry.api.common.Attributes;
 import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
 import io.prometheus.metrics.model.snapshots.CounterSnapshot;
@@ -26,6 +27,7 @@ import io.prometheus.metrics.model.snapshots.HistogramSnapshot;
 import io.prometheus.metrics.model.snapshots.Labels;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Random;
 import org.apache.lucene.tests.util.TestUtil;
@@ -68,8 +70,40 @@ public final class SolrMetricTestUtils {
     return shouldDefineMetrics ? getRandomMetricsWithReplacements(random, new HashMap<>()) : null;
   }
 
+  /**
+   * Generate random OpenTelemetry metric names for testing Prometheus metrics. Returns a map of
+   * metric names to their expected increment values.
+   */
+  public static Map<String, Long> getRandomPrometheusMetrics(Random random) {
+    return random.nextBoolean()
+        ? getRandomPrometheusMetricsWithReplacements(random, new HashMap<>())
+        : null;
+  }
+
+  public static Map<String, Long> getRandomPrometheusMetricsWithReplacements(
+      Random random, Map<String, Long> existing) {
+    HashMap<String, Long> metrics = new HashMap<>();
+    List<String> existingKeys = List.copyOf(existing.keySet());
+
+    int numMetrics = TestUtil.nextInt(random, 1, MAX_ITERATIONS);
+    for (int i = 0; i < numMetrics; ++i) {
+      boolean shouldReplaceMetric = !existing.isEmpty() && random.nextBoolean();
+      String name =
+          shouldReplaceMetric
+              ? existingKeys.get(TestUtil.nextInt(random, 0, existingKeys.size() - 1))
+              : TestUtil.randomSimpleString(random, 5, 10)
+                  + SUFFIX; // must be simple string for JMX publishing
+
+      Long incrementValue = TestUtil.nextLong(random, 1L, 1000L);
+      metrics.put(name, incrementValue);
+    }
+
+    return metrics;
+  }
+
   public static final String SUFFIX = "_testing";
 
+  // NOCOMMIT: This will get replaced by getRandomPrometheusMetricsWithReplacements
   public static Map<String, Counter> getRandomMetricsWithReplacements(
       Random random, Map<String, Counter> existing) {
     HashMap<String, Counter> metrics = new HashMap<>();
@@ -193,4 +227,84 @@ public final class SolrMetricTestUtils {
     return getDatapoint(
         core, metricName, labels, HistogramSnapshot.HistogramDataPointSnapshot.class);
   }
+
+  public static CounterSnapshot.CounterDataPointSnapshot newStandaloneSelectRequestsDatapoint(
+      SolrCore core) {
+    return SolrMetricTestUtils.getCounterDatapoint(
+        core,
+        "solr_core_requests",
+        SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+            .label("handler", "/select")
+            .label("category", "QUERY")
+            .label("internal", "false")
+            .build());
+  }
+
+  public static CounterSnapshot.CounterDataPointSnapshot newCloudSelectRequestsDatapoint(
+      SolrCore core) {
+    return SolrMetricTestUtils.getCounterDatapoint(
+        core,
+        "solr_core_requests",
+        SolrMetricTestUtils.newCloudLabelsBuilder(core)
+            .label("handler", "/select")
+            .label("category", "QUERY")
+            .label("internal", "false")
+            .build());
+  }
+
+  public static CounterSnapshot.CounterDataPointSnapshot newStandaloneUpdateRequestsDatapoint(
+      SolrCore core) {
+    return SolrMetricTestUtils.getCounterDatapoint(
+        core,
+        "solr_core_requests",
+        SolrMetricTestUtils.newStandaloneLabelsBuilder(core)
+            .label("handler", "/update")
+            .label("category", "UPDATE")
+            .build());
+  }
+
+  public static CounterSnapshot.CounterDataPointSnapshot newCloudUpdateRequestsDatapoint(
+      SolrCore core) {
+    return SolrMetricTestUtils.getCounterDatapoint(
+        core,
+        "solr_core_requests",
+        SolrMetricTestUtils.newCloudLabelsBuilder(core)
+            .label("handler", "/update")
+            .label("category", "UPDATE")
+            .build());
+  }
+
+  public static class TestSolrMetricProducer implements SolrMetricProducer {
+    SolrMetricsContext solrMetricsContext;
+    private final Map<String, io.opentelemetry.api.metrics.LongCounter> counters = new HashMap<>();
+    private final String coreName;
+    private final Map<String, Long> metrics;
+
+    public TestSolrMetricProducer(String coreName, Map<String, Long> metrics) {
+      this.coreName = coreName;
+      this.metrics = metrics;
+    }
+
+    @Override
+    public void initializeMetrics(
+        SolrMetricsContext parentContext, Attributes attributes, String scope) {
+      this.solrMetricsContext = parentContext.getChildContext(this);
+      for (Map.Entry<String, Long> entry : metrics.entrySet()) {
+        String metricName = entry.getKey();
+        Long incrementValue = entry.getValue();
+        var counter = solrMetricsContext.longCounter(metricName, "testing");
+        counters.put(metricName, counter);
+        counter.add(incrementValue, Attributes.of(AttributeKey.stringKey("core"), coreName));
+      }
+    }
+
+    @Override
+    public SolrMetricsContext getSolrMetricsContext() {
+      return solrMetricsContext;
+    }
+
+    public Map<String, io.opentelemetry.api.metrics.LongCounter> getCounters() {
+      return counters;
+    }
+  }
 }
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java b/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java
index b62f56745a8..117f02936c4 100644
--- a/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java
@@ -27,7 +27,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import org.apache.http.client.HttpClient;
-import org.apache.lucene.tests.util.LuceneTestCase;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.impl.HttpClientUtil;
@@ -36,48 +35,19 @@ import org.apache.solr.cloud.MiniSolrCloudCluster;
 import org.apache.solr.common.util.Utils;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.NodeConfig;
-import org.apache.solr.core.PluginInfo;
+import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.core.SolrXmlConfig;
 import org.apache.solr.embedded.JettySolrRunner;
-import org.apache.solr.util.JmxUtil;
 import org.apache.solr.util.TestHarness;
 import org.hamcrest.number.OrderingComparison;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-// NOCOMMIT: Test fails because of the @After assert on index path. Was going to migrate to just
-// check the registry for the core is deleted but this test does a rename operation which otel has
-// not addressed yet. Need to migrate the rename operation to otel first.
-@LuceneTestCase.BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17458")
 public class SolrMetricsIntegrationTest extends SolrTestCaseJ4 {
-  private static final int MAX_ITERATIONS = 20;
-  private static final String CORE_NAME = "metrics_integration";
-  private static final String METRIC_NAME = "requestTimes";
-  private static final String HANDLER_NAME = "/select";
-  private static final String[] REPORTER_NAMES = {"reporter1", "reporter2"};
-  private static final String UNIVERSAL = "universal";
-  private static final String SPECIFIC = "specific";
-  private static final String MULTIGROUP = "multigroup";
-  private static final String MULTIREGISTRY = "multiregistry";
-  private static final String[] INITIAL_REPORTERS = {
-    REPORTER_NAMES[0], REPORTER_NAMES[1], UNIVERSAL, SPECIFIC, MULTIGROUP, MULTIREGISTRY
-  };
-  private static final String[] RENAMED_REPORTERS = {
-    REPORTER_NAMES[0], REPORTER_NAMES[1], UNIVERSAL, MULTIGROUP
-  };
-  private static final SolrInfoBean.Category HANDLER_CATEGORY = SolrInfoBean.Category.QUERY;
-
   private CoreContainer cc;
   private SolrMetricManager metricManager;
-  private String tag;
-  private int jmxReporter;
-
-  private void assertTagged(Map<String, SolrMetricReporter> reporters, String name) {
-    assertTrue(
-        "Reporter '" + name + "' missing in " + reporters, reporters.containsKey(name + "@" + tag));
-  }
 
   @Before
   public void beforeTest() throws Exception {
@@ -98,36 +68,7 @@ public class SolrMetricsIntegrationTest extends SolrTestCaseJ4 {
                 "schema.xml"));
 
     h.coreName = DEFAULT_TEST_CORENAME;
-    jmxReporter = JmxUtil.findFirstMBeanServer() != null ? 1 : 0;
-    metricManager = cc.getMetricManager();
-    tag = h.getCore().getCoreMetricManager().getTag();
-    // initially there are more reporters, because two of them are added via a matching collection
-    // name
-    Map<String, SolrMetricReporter> reporters =
-        metricManager.getReporters("solr.core." + DEFAULT_TEST_CORENAME);
-    assertEquals(INITIAL_REPORTERS.length + jmxReporter, reporters.size());
-    for (String r : INITIAL_REPORTERS) {
-      assertTagged(reporters, r);
-    }
-    // test rename operation
-    cc.rename(DEFAULT_TEST_CORENAME, CORE_NAME);
-    h.coreName = CORE_NAME;
-    cfg = cc.getConfig();
-    PluginInfo[] plugins = cfg.getMetricsConfig().getMetricReporters();
-    assertNotNull(plugins);
-    assertEquals(10 + jmxReporter, plugins.length);
-    reporters = metricManager.getReporters("solr.node");
-    assertEquals(4 + jmxReporter, reporters.size());
-    assertTrue(
-        "Reporter '" + REPORTER_NAMES[0] + "' missing in solr.node",
-        reporters.containsKey(REPORTER_NAMES[0]));
-    assertTrue(
-        "Reporter '" + UNIVERSAL + "' missing in solr.node", reporters.containsKey(UNIVERSAL));
-    assertTrue(
-        "Reporter '" + MULTIGROUP + "' missing in solr.node", reporters.containsKey(MULTIGROUP));
-    assertTrue(
-        "Reporter '" + MULTIREGISTRY + "' missing in solr.node",
-        reporters.containsKey(MULTIREGISTRY));
+    metricManager = h.getCore().getCoreContainer().getMetricManager();
   }
 
   @After
@@ -135,12 +76,7 @@ public class SolrMetricsIntegrationTest extends SolrTestCaseJ4 {
     if (null == metricManager) {
       return; // test failed to init, nothing to clean up
     }
-
-    SolrCoreMetricManager coreMetricManager = h.getCore().getCoreMetricManager();
-    Gauge<?> gauge = (Gauge<?>) coreMetricManager.getRegistry().getMetrics().get("CORE.indexDir");
-    assertNotNull(gauge.getValue());
     deleteCore(); // closes TestHarness which closes CoreContainer which closes SolrCore
-    assertEquals(metricManager.nullString(), gauge.getValue());
   }
 
   @Test
@@ -166,6 +102,7 @@ public class SolrMetricsIntegrationTest extends SolrTestCaseJ4 {
     assertEquals(g.getValue(), cc.getSolrHome().toString());
   }
 
+  // NOCOMMIT: Comeback and fix this test after merging the SolrZKClient metrics migration
   @Test
   public void testZkMetrics() throws Exception {
     System.setProperty("metricsEnabled", "true");
@@ -231,6 +168,36 @@ public class SolrMetricsIntegrationTest extends SolrTestCaseJ4 {
     }
   }
 
+  @Test
+  public void testCoreRename() {
+    String newCoreName = "renamed_core";
+    String originalRegistryName;
+
+    try (SolrCore core = cc.getCore(DEFAULT_TEST_CORENAME)) {
+      originalRegistryName = core.getCoreMetricManager().getRegistryName();
+      assertTrue("Original registry should exist", metricManager.hasRegistry(originalRegistryName));
+      assertQ(req("q", "*:*"), "//result[@numFound='0']");
+      assertEquals(
+          1.0, SolrMetricTestUtils.newStandaloneSelectRequestsDatapoint(core).getValue(), 0.0);
+    }
+
+    cc.rename(DEFAULT_TEST_CORENAME, newCoreName);
+    h.coreName = newCoreName;
+
+    try (SolrCore core = cc.getCore(newCoreName)) {
+      assertFalse(
+          "Original registry should not exist", metricManager.hasRegistry(originalRegistryName));
+      assertTrue(
+          "Renamed registry should exist",
+          metricManager.hasRegistry(core.getCoreMetricManager().getRegistryName()));
+      assertQ(req("q", "*:*"), "//result[@numFound='0']");
+      assertEquals(
+          1.0,
+          SolrMetricTestUtils.newStandaloneSelectRequestsDatapoint(h.getCore()).getValue(),
+          0.0);
+    }
+  }
+
   private long findDelta(Map<String, Object> m1, Map<String, Object> m2, String k) {
     return ((Number) m2.get(k)).longValue() - ((Number) m1.get(k)).longValue();
   }
diff --git a/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-10.adoc b/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-10.adoc
index 5b3b74e53df..004539aa1aa 100644
--- a/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-10.adoc
+++ b/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-10.adoc
@@ -168,8 +168,6 @@ Nowadays, the HTTP request is available via internal APIs: `SolrQueryRequest.get
 
 * BlobHandler and the `.system` collection have been removed in favour of FileStore API.  (SOLR-17851).
 
-* JMX, SLF4J and Graphite metric reporters have been removed. Users should migrate to using OTLP or the /admin/metrics endpoint with external tools to get metrics to their preferred backend.
-
 === Security
 
 * There is no longer a distinction between trusted and untrusted configSets; all configSets are now considered trusted. To ensure security, Solr should be properly protected using authentication and authorization mechanisms, allowing only authorized users with administrative privileges to publish them.
@@ -177,6 +175,12 @@ Nowadays, the HTTP request is available via internal APIs: `SolrQueryRequest.get
 === Upgrade to Jetty 12.x
 Solr upgraded to Jetty 12.x from 10.x as Jetty 10 and 11 have reached end-of-life support. Jetty 12.x requires Java 17 or newer and is fully compatible with Solr's new minimum requirement of Java 21. This upgrade brings support for modern HTTP protocols and adopts the Jakarta EE 10 namespace. For more details, see https://webtide.com/jetty-12-has-arrived/.
 
+=== Open Telemetry
+
+* JMX, SLF4J and Graphite metric reporters have been removed. Users should migrate to using OTLP or the /admin/metrics endpoint with external tools to get metrics to their preferred backend.
+
+* Core renaming and swapping will reset the state of all the corresponding cores metrics
+
 === Docker
 
 The OS version of the official Docker image and provided Dockerfile has been upgraded to Ubuntu 24 (noble) from Ubuntu 22 (jammy).
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/NodesSysPropsCacher.java b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/NodesSysPropsCacher.java
index 35d6829f7cb..4e79f905972 100644
--- a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/NodesSysPropsCacher.java
+++ b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/NodesSysPropsCacher.java
@@ -74,6 +74,9 @@ public class NodesSysPropsCacher implements NodesSysProps, AutoCloseable {
     return result;
   }
 
+  // NOCOMMIT: These properties were fetched from the /admin/metrics endpoint. These properties were
+  // stored as strings instead of numeric values. This is not possible in OTEL metrics.
+  // Use /admin/info/properties to fetch system properties.
   private Map<String, Object> fetchProps(String nodeName, Collection<String> tags) {
     ModifiableSolrParams msp = new ModifiableSolrParams();
     msp.add(CommonParams.OMIT_HEADER, "true");
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/SolrJMetricTestUtils.java b/solr/solrj/src/test/org/apache/solr/client/solrj/SolrJMetricTestUtils.java
index 755be090eac..d48f79cc55a 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/SolrJMetricTestUtils.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/SolrJMetricTestUtils.java
@@ -20,7 +20,6 @@ package org.apache.solr.client.solrj;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
-import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.impl.Http2SolrClient;
 import org.apache.solr.client.solrj.impl.InputStreamResponseParser;
 import org.apache.solr.client.solrj.request.GenericSolrRequest;
@@ -29,7 +28,7 @@ import org.apache.solr.common.util.NamedList;
 
 public final class SolrJMetricTestUtils {
 
-  public static double getPrometheusMetricValue(CloudSolrClient solrClient, String metricName)
+  public static double getPrometheusMetricValue(SolrClient solrClient, String metricName)
       throws SolrServerException, IOException {
     var req =
         new GenericSolrRequest(
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestCoreAdmin.java b/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestCoreAdmin.java
index 980a326a32f..7cb86c03949 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestCoreAdmin.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestCoreAdmin.java
@@ -16,10 +16,10 @@
  */
 package org.apache.solr.client.solrj.request;
 
+import static org.apache.solr.client.solrj.SolrJMetricTestUtils.getPrometheusMetricValue;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.core.Is.is;
 
-import com.codahale.metrics.MetricRegistry;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -40,8 +40,6 @@ import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.SolrCore;
-import org.apache.solr.metrics.SolrCoreMetricManager;
-import org.apache.solr.metrics.SolrMetricManager;
 import org.junit.Test;
 
 public class TestCoreAdmin extends AbstractEmbeddedSolrServerTestCase {
@@ -168,9 +166,7 @@ public class TestCoreAdmin extends AbstractEmbeddedSolrServerTestCase {
     assertEquals(names.size(), cores.getNumAllCores());
   }
 
-  // NOCOMMIT: We have not yet implemented core swapping or renaming for OTEL yet
   @Test
-  @BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17458")
   public void testCoreSwap() throws Exception {
     // index marker docs to core0
     SolrClient cli0 = getSolrCore0();
@@ -203,19 +199,20 @@ public class TestCoreAdmin extends AbstractEmbeddedSolrServerTestCase {
           assertTrue(doc.toString(), doc.getFieldValue("id").toString().startsWith("core1-"));
         });
 
-    // assert initial metrics
-    SolrMetricManager metricManager = cores.getMetricManager();
-    String core0RegistryName =
-        SolrCoreMetricManager.createRegistryName(false, null, null, null, "core0");
-    String core1RegistryName =
-        SolrCoreMetricManager.createRegistryName(false, null, null, null, "core1");
-    MetricRegistry core0Registry = metricManager.registry(core0RegistryName);
-    MetricRegistry core1Registry = metricManager.registry(core1RegistryName);
-
-    // 2 docs + 1 commit
-    assertEquals(3, core0Registry.counter("UPDATE./update.requests").getCount());
-    // 1 doc + 1 commit
-    assertEquals(2, core1Registry.counter("UPDATE./update.requests").getCount());
+    // Check initial request counts
+    Double core0InitialRequests =
+        getPrometheusMetricValue(
+            getSolrAdmin(),
+            "solr_core_requests_total{category=\"UPDATE\",core=\"core0\",handler=\"/update\",otel_scope_name=\"org.apache.solr\"}");
+    Double core1InitialRequests =
+        getPrometheusMetricValue(
+            getSolrAdmin(),
+            "solr_core_requests_total{category=\"UPDATE\",core=\"core1\",handler=\"/update\",otel_scope_name=\"org.apache.solr\"}");
+
+    // 2 docs + 1 commit = 3 requests for core0
+    assertEquals("Initial core0 update requests", 3.0, core0InitialRequests, 0.1);
+    // 1 doc + 1 commit = 2 requests for core1
+    assertEquals("Initial core1 update requests", 2.0, core1InitialRequests, 0.1);
 
     // swap
     CoreAdminRequest.swapCore("core0", "core1", getSolrAdmin());
@@ -240,11 +237,38 @@ public class TestCoreAdmin extends AbstractEmbeddedSolrServerTestCase {
           assertTrue(doc.toString(), doc.getFieldValue("id").toString().startsWith("core0-"));
         });
 
-    core0Registry = metricManager.registry(core0RegistryName);
-    core1Registry = metricManager.registry(core1RegistryName);
+    Double core0PostSwapRequests =
+        getPrometheusMetricValue(
+            getSolrAdmin(),
+            "solr_core_requests_total{category=\"UPDATE\",core=\"core0\",handler=\"/update\",otel_scope_name=\"org.apache.solr\"}");
+    Double core1PostSwapRequests =
+        getPrometheusMetricValue(
+            getSolrAdmin(),
+            "solr_core_requests_total{category=\"UPDATE\",core=\"core1\",handler=\"/update\",otel_scope_name=\"org.apache.solr\"}");
+
+    // Both cores should have reset metrics after swap (registries recreated)
+    assertEquals("core0 requests should be reset after swap", 0.0, core0PostSwapRequests, 0.1);
+    assertEquals("core1 requests should be reset after swap", 0.0, core1PostSwapRequests, 0.1);
+
+    // Index documents after swap and assert request counts again
+    cli0.add(d);
+    d = new SolrInputDocument("id", "core0-1");
+    cli0.add(d);
+    cli0.commit();
+    d = new SolrInputDocument("id", "core1-0");
+    cli1.add(d);
+    cli1.commit();
 
-    assertEquals(2, core0Registry.counter("UPDATE./update.requests").getCount());
-    assertEquals(3, core1Registry.counter("UPDATE./update.requests").getCount());
+    core0PostSwapRequests =
+        getPrometheusMetricValue(
+            getSolrAdmin(),
+            "solr_core_requests_total{category=\"UPDATE\",core=\"core0\",handler=\"/update\",otel_scope_name=\"org.apache.solr\"}");
+    core1PostSwapRequests =
+        getPrometheusMetricValue(
+            getSolrAdmin(),
+            "solr_core_requests_total{category=\"UPDATE\",core=\"core1\",handler=\"/update\",otel_scope_name=\"org.apache.solr\"}");
+    assertEquals("Initial core0 update requests", 3.0, core0PostSwapRequests, 0.1);
+    assertEquals("Initial core1 update requests", 2.0, core1PostSwapRequests, 0.1);
   }
 
   @Test


(solr) 24/33: SOLR-17806: Migrate Overseer and Blockcache metrics to OTEL (#3607)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit e6d4d446d757ae3c45675ab9c143a6af8845b3fb
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Mon Sep 8 11:37:55 2025 -0400

    SOLR-17806: Migrate Overseer and Blockcache metrics to OTEL (#3607)
    
    * Migrate Overseer and Blockcache metrics
    
    * Single close
    
    ---------
    
    Co-authored-by: Matthew Biscocho <mb...@bloomberg.net>
---
 .../java/org/apache/solr/blockcache/Metrics.java   | 109 +++++++++++++--------
 .../src/java/org/apache/solr/cloud/Overseer.java   |  48 +++++++--
 .../apache/solr/cloud/OverseerTaskProcessor.java   |  44 ++++++++-
 .../java/org/apache/solr/core/SolrInfoBean.java    |   1 +
 .../org/apache/solr/metrics/SolrMetricManager.java |  27 ++++-
 .../apache/solr/metrics/SolrMetricsContext.java    |   9 ++
 .../apache/solr/blockcache/BufferStoreTest.java    |  59 ++++++-----
 7 files changed, 215 insertions(+), 82 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/blockcache/Metrics.java b/solr/core/src/java/org/apache/solr/blockcache/Metrics.java
index dcdb4550d8c..98c9b2dbef6 100644
--- a/solr/core/src/java/org/apache/solr/blockcache/Metrics.java
+++ b/solr/core/src/java/org/apache/solr/blockcache/Metrics.java
@@ -17,11 +17,9 @@
 package org.apache.solr.blockcache;
 
 import io.opentelemetry.api.common.Attributes;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicLong;
+import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.core.SolrInfoBean;
-import org.apache.solr.metrics.MetricsMap;
 import org.apache.solr.metrics.SolrMetricsContext;
 import org.apache.solr.search.SolrCacheBase;
 
@@ -50,19 +48,31 @@ public class Metrics extends SolrCacheBase implements SolrInfoBean {
   public AtomicLong shardBuffercacheAllocate = new AtomicLong(0);
   public AtomicLong shardBuffercacheLost = new AtomicLong(0);
 
-  private MetricsMap metricsMap;
-  private Set<String> metricNames = ConcurrentHashMap.newKeySet();
   private SolrMetricsContext solrMetricsContext;
   private long previous = System.nanoTime();
 
-  // TODO SOLR-17458: Migrate to Otel
+  private AutoCloseable toClose;
+
   @Override
   public void initializeMetrics(
       SolrMetricsContext parentContext, Attributes attributes, String scope) {
     solrMetricsContext = parentContext.getChildContext(this);
-    metricsMap =
-        new MetricsMap(
-            map -> {
+    var baseAttributes =
+        attributes.toBuilder().put(CATEGORY_ATTR, getCategory().toString()).build();
+    var blockcacheStats =
+        solrMetricsContext.longMeasurement("solr_block_cache_stats", "Block cache stats");
+    var hitRatio =
+        solrMetricsContext.doubleMeasurement("solr_block_cache_hit_ratio", "Block cache hit ratio");
+    var perSecStats =
+        solrMetricsContext.doubleMeasurement(
+            "solr_block_cache_stats_per_second", "Block cache per second stats");
+    var bufferCacheStats =
+        solrMetricsContext.doubleMeasurement(
+            "solr_buffer_cache_stats", "Buffer cache per second stats");
+
+    this.toClose =
+        solrMetricsContext.batchCallback(
+            () -> {
               long now = System.nanoTime();
               long delta = Math.max(now - previous, 1);
               double seconds = delta / 1000000000.0;
@@ -86,44 +96,63 @@ public class Metrics extends SolrCacheBase implements SolrInfoBean {
               long lookups_delta = hits_delta + miss_delta;
               long lookups_total = hits_total + miss_total;
 
-              map.put("size", blockCacheSize.get());
-              map.put("lookups", lookups_total);
-              map.put("hits", hits_total);
-              map.put("evictions", evict_total);
-              map.put("storeFails", storeFail_total);
-              map.put(
-                  "hitratio_current",
-                  calcHitRatio(lookups_delta, hits_delta)); // hit ratio since the last call
-              map.put(
-                  "lookups_persec",
-                  getPerSecond(lookups_delta, seconds)); // lookups per second since the last call
-              map.put(
-                  "hits_persec",
-                  getPerSecond(hits_delta, seconds)); // hits per second since the last call
-              map.put(
-                  "evictions_persec",
-                  getPerSecond(evict_delta, seconds)); // evictions per second since the last call
-              map.put(
-                  "storeFails_persec",
-                  getPerSecond(
-                      storeFail_delta, seconds)); // evictions per second since the last call
-              map.put("time_delta", seconds); // seconds since last call
-
-              // TODO: these aren't really related to the BlockCache
-              map.put(
-                  "buffercache.allocations",
-                  getPerSecond(shardBuffercacheAllocate.getAndSet(0), seconds));
-              map.put("buffercache.lost", getPerSecond(shardBuffercacheLost.getAndSet(0), seconds));
-
+              blockcacheStats.record(
+                  blockCacheSize.get(), baseAttributes.toBuilder().put(TYPE_ATTR, "size").build());
+              blockcacheStats.record(
+                  lookups_total, baseAttributes.toBuilder().put(TYPE_ATTR, "lookups").build());
+              blockcacheStats.record(
+                  hits_total, baseAttributes.toBuilder().put(TYPE_ATTR, "hits").build());
+              blockcacheStats.record(
+                  hits_total, baseAttributes.toBuilder().put(TYPE_ATTR, "evictions").build());
+              blockcacheStats.record(
+                  storeFail_total,
+                  baseAttributes.toBuilder().put(TYPE_ATTR, "store_fails").build());
+              perSecStats.record(
+                  getPerSecond(lookups_delta, seconds),
+                  baseAttributes.toBuilder()
+                      .put(TYPE_ATTR, "lookups")
+                      .build()); // lookups per second since the last call
+              perSecStats.record(
+                  getPerSecond(hits_delta, seconds),
+                  baseAttributes.toBuilder()
+                      .put(TYPE_ATTR, "hits")
+                      .build()); // hits per second since the last call
+              perSecStats.record(
+                  getPerSecond(evict_delta, seconds),
+                  baseAttributes.toBuilder()
+                      .put(TYPE_ATTR, "evictions")
+                      .build()); // evictions per second since the last call
+              perSecStats.record(
+                  getPerSecond(storeFail_delta, seconds),
+                  baseAttributes.toBuilder()
+                      .put(TYPE_ATTR, "store_fails")
+                      .build()); // evictions per second since the last call
+              hitRatio.record(
+                  calcHitRatio(lookups_delta, hits_delta),
+                  baseAttributes); // hit ratio since the last call
+              bufferCacheStats.record(
+                  getPerSecond(shardBuffercacheAllocate.getAndSet(0), seconds),
+                  baseAttributes.toBuilder().put(TYPE_ATTR, "allocations").build());
+              bufferCacheStats.record(
+                  getPerSecond(shardBuffercacheLost.getAndSet(0), seconds),
+                  baseAttributes.toBuilder().put(TYPE_ATTR, "lost").build());
               previous = now;
-            });
-    solrMetricsContext.gauge(metricsMap, true, getName(), getCategory().toString(), scope);
+            },
+            blockcacheStats,
+            perSecStats,
+            hitRatio,
+            bufferCacheStats);
   }
 
   private float getPerSecond(long value, double seconds) {
     return (float) (value / seconds);
   }
 
+  @Override
+  public void close() {
+    IOUtils.closeQuietly(toClose);
+  }
+
   // SolrInfoBean methods
 
   @Override
diff --git a/solr/core/src/java/org/apache/solr/cloud/Overseer.java b/solr/core/src/java/org/apache/solr/cloud/Overseer.java
index c6ca1426cdb..6f7cba71767 100644
--- a/solr/core/src/java/org/apache/solr/cloud/Overseer.java
+++ b/solr/core/src/java/org/apache/solr/cloud/Overseer.java
@@ -19,6 +19,7 @@ package org.apache.solr.cloud;
 import static org.apache.solr.common.params.CommonParams.ID;
 
 import com.codahale.metrics.Timer;
+import io.opentelemetry.api.common.Attributes;
 import java.io.Closeable;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
@@ -167,7 +168,7 @@ public class Overseer implements SolrCloseable {
    *
    * <p>The cluster state updater is a single thread dequeueing and executing requests.
    */
-  private class ClusterStateUpdater implements Runnable, Closeable {
+  private class ClusterStateUpdater implements SolrInfoBean, Runnable, Closeable {
 
     private final ZkStateReader reader;
     private final SolrZkClient zkClient;
@@ -195,6 +196,8 @@ public class Overseer implements SolrCloseable {
 
     private boolean isClosed = false;
 
+    private AutoCloseable toClose;
+
     public ClusterStateUpdater(
         final ZkStateReader reader,
         final String myId,
@@ -213,12 +216,27 @@ public class Overseer implements SolrCloseable {
       this.minStateByteLenForCompression = minStateByteLenForCompression;
       this.compressor = compressor;
 
-      clusterStateUpdaterMetricContext = solrMetricsContext.getChildContext(this);
-      clusterStateUpdaterMetricContext.gauge(
-          () -> stateUpdateQueue.getZkStats().getQueueLength(),
-          true,
-          "stateUpdateQueueSize",
-          "queue");
+      this.clusterStateUpdaterMetricContext = solrMetricsContext.getChildContext(this);
+      initializeMetrics(
+          solrMetricsContext, Attributes.of(CATEGORY_ATTR, getCategory().toString()), "");
+    }
+
+    @Override
+    public void initializeMetrics(
+        SolrMetricsContext parentContext, Attributes attributes, String scope) {
+      this.toClose =
+          parentContext.observableLongGauge(
+              "solr_overseer_state_update_queue_size",
+              "Size of overseer's update queue",
+              (observableLongMeasurement) -> {
+                observableLongMeasurement.record(
+                    stateUpdateQueue.getZkStats().getQueueLength(), attributes);
+              });
+    }
+
+    @Override
+    public SolrMetricsContext getSolrMetricsContext() {
+      return clusterStateUpdaterMetricContext;
     }
 
     public Stats getStateUpdateQueueStats() {
@@ -641,8 +659,24 @@ public class Overseer implements SolrCloseable {
     @Override
     public void close() {
       this.isClosed = true;
+      IOUtils.closeQuietly(toClose);
       clusterStateUpdaterMetricContext.unregister();
     }
+
+    @Override
+    public String getName() {
+      return this.getClass().getName();
+    }
+
+    @Override
+    public String getDescription() {
+      return "Cluster leader responsible for processing state updates";
+    }
+
+    @Override
+    public Category getCategory() {
+      return Category.OVERSEER;
+    }
   }
 
   public static class OverseerThread extends Thread implements Closeable {
diff --git a/solr/core/src/java/org/apache/solr/cloud/OverseerTaskProcessor.java b/solr/core/src/java/org/apache/solr/cloud/OverseerTaskProcessor.java
index 3751d850f09..e29df029de7 100644
--- a/solr/core/src/java/org/apache/solr/cloud/OverseerTaskProcessor.java
+++ b/solr/core/src/java/org/apache/solr/cloud/OverseerTaskProcessor.java
@@ -20,6 +20,7 @@ import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
 import static org.apache.solr.common.params.CommonParams.ID;
 
 import com.codahale.metrics.Timer;
+import io.opentelemetry.api.common.Attributes;
 import java.io.Closeable;
 import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
@@ -44,6 +45,7 @@ import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.common.util.SolrNamedThreadFactory;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.common.util.Utils;
+import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.logging.MDCLoggingContext;
 import org.apache.solr.metrics.SolrMetricsContext;
 import org.apache.zookeeper.KeeperException;
@@ -59,7 +61,7 @@ import org.slf4j.LoggerFactory;
  * <p>An {@link OverseerMessageHandlerSelector} determines which {@link OverseerMessageHandler}
  * handles specific messages in the queue.
  */
-public class OverseerTaskProcessor implements Runnable, Closeable {
+public class OverseerTaskProcessor implements SolrInfoBean, Runnable, Closeable {
 
   /** Maximum number of overseer collection operations which can be executed concurrently */
   public static final int MAX_PARALLEL_TASKS = 100;
@@ -91,6 +93,7 @@ public class OverseerTaskProcessor implements Runnable, Closeable {
 
   private final Stats stats;
   private final SolrMetricsContext overseerTaskProcessorMetricsContext;
+  private AutoCloseable toClose;
 
   /**
    * This map may contain tasks which are read from work queue but could not be executed because
@@ -148,9 +151,9 @@ public class OverseerTaskProcessor implements Runnable, Closeable {
     this.runningTasks = new HashMap<>();
     thisNode = MDCLoggingContext.getNodeName();
 
-    overseerTaskProcessorMetricsContext = solrMetricsContext.getChildContext(this);
-    overseerTaskProcessorMetricsContext.gauge(
-        () -> workQueue.getZkStats().getQueueLength(), true, "collectionWorkQueueSize", "queue");
+    this.overseerTaskProcessorMetricsContext = solrMetricsContext.getChildContext(this);
+    initializeMetrics(
+        solrMetricsContext, Attributes.of(CATEGORY_ATTR, getCategory().toString()), "");
   }
 
   @Override
@@ -403,6 +406,23 @@ public class OverseerTaskProcessor implements Runnable, Closeable {
     runningTasks.entrySet().removeIf(e -> e.getValue().isDone());
   }
 
+  @Override
+  public void initializeMetrics(
+      SolrMetricsContext parentContext, Attributes attributes, String scope) {
+    this.toClose =
+        parentContext.observableLongGauge(
+            "solr_overseer_collection_work_queue_size",
+            "Size of overseer's collection work queue",
+            (observableLongMeasurement) -> {
+              observableLongMeasurement.record(workQueue.getZkStats().getQueueLength(), attributes);
+            });
+  }
+
+  @Override
+  public SolrMetricsContext getSolrMetricsContext() {
+    return overseerTaskProcessorMetricsContext;
+  }
+
   @Override
   public void close() {
     isClosed = true;
@@ -413,6 +433,7 @@ public class OverseerTaskProcessor implements Runnable, Closeable {
       }
     }
     IOUtils.closeQuietly(selector);
+    IOUtils.closeQuietly(toClose);
   }
 
   public static List<String> getSortedOverseerNodeNames(SolrZkClient zk)
@@ -673,6 +694,21 @@ public class OverseerTaskProcessor implements Runnable, Closeable {
     return myId;
   }
 
+  @Override
+  public String getName() {
+    return this.getClass().getName();
+  }
+
+  @Override
+  public String getDescription() {
+    return "Processor in Overseer handling items added to a distributed work queue";
+  }
+
+  @Override
+  public Category getCategory() {
+    return Category.OVERSEER;
+  }
+
   /**
    * An interface to determine which {@link OverseerMessageHandler} handles a given message. This
    * could be a single OverseerMessageHandler for the case where a single type of message is handled
diff --git a/solr/core/src/java/org/apache/solr/core/SolrInfoBean.java b/solr/core/src/java/org/apache/solr/core/SolrInfoBean.java
index d62f51e439e..0689edb0d84 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrInfoBean.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrInfoBean.java
@@ -39,6 +39,7 @@ public interface SolrInfoBean extends SolrMetricProducer {
     DIRECTORY,
     HTTP,
     SECURITY,
+    OVERSEER,
     OTHER
   }
 
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
index 4ddc6443f80..e179306eaae 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
@@ -358,11 +358,6 @@ public class SolrMetricManager {
     return builder.buildWithCallback(callback);
   }
 
-  ObservableLongMeasurement longMeasurement(
-      String registry, String gaugeName, String description, OtelUnit unit) {
-    return longGaugeBuilder(registry, gaugeName, description, unit).buildObserver();
-  }
-
   BatchCallback batchCallback(
       String registry,
       Runnable callback,
@@ -373,6 +368,16 @@ public class SolrMetricManager {
         .batchCallback(callback, measurement, additionalMeasurements);
   }
 
+  ObservableLongMeasurement longMeasurement(
+      String registry, String gaugeName, String description, OtelUnit unit) {
+    return longGaugeBuilder(registry, gaugeName, description, unit).buildObserver();
+  }
+
+  ObservableDoubleMeasurement doubleMeasurement(
+      String registry, String gaugeName, String description, OtelUnit unit) {
+    return doubleGaugeBuilder(registry, gaugeName, description, unit).buildObserver();
+  }
+
   private LongGaugeBuilder longGaugeBuilder(
       String registry, String gaugeName, String description, OtelUnit unit) {
     LongGaugeBuilder builder =
@@ -386,6 +391,18 @@ public class SolrMetricManager {
     return builder;
   }
 
+  private DoubleGaugeBuilder doubleGaugeBuilder(
+      String registry, String gaugeName, String description, OtelUnit unit) {
+    DoubleGaugeBuilder builder =
+        meterProvider(registry)
+            .get(OTEL_SCOPE_NAME)
+            .gaugeBuilder(gaugeName)
+            .setDescription(description);
+    if (unit != null) builder.setUnit(unit.getSymbol());
+
+    return builder;
+  }
+
   // for unit tests
   public MetricRegistry.MetricSupplier<Counter> getCounterSupplier() {
     return counterSupplier;
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java
index 14b6affa475..696ace6aa0d 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java
@@ -285,6 +285,15 @@ public class SolrMetricsContext {
     return metricManager.longMeasurement(registryName, metricName, description, unit);
   }
 
+  public ObservableDoubleMeasurement doubleMeasurement(String metricName, String description) {
+    return doubleMeasurement(metricName, description, null);
+  }
+
+  public ObservableDoubleMeasurement doubleMeasurement(
+      String metricName, String description, OtelUnit unit) {
+    return metricManager.doubleMeasurement(registryName, metricName, description, unit);
+  }
+
   public BatchCallback batchCallback(
       Runnable callback,
       ObservableMeasurement measurement,
diff --git a/solr/core/src/test/org/apache/solr/blockcache/BufferStoreTest.java b/solr/core/src/test/org/apache/solr/blockcache/BufferStoreTest.java
index 46bc29685ca..a628b9263a7 100644
--- a/solr/core/src/test/org/apache/solr/blockcache/BufferStoreTest.java
+++ b/solr/core/src/test/org/apache/solr/blockcache/BufferStoreTest.java
@@ -17,11 +17,10 @@
 package org.apache.solr.blockcache;
 
 import io.opentelemetry.api.common.Attributes;
-import java.math.BigDecimal;
-import java.util.Map;
+import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
+import io.prometheus.metrics.model.snapshots.Labels;
 import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.SolrTestCase;
-import org.apache.solr.metrics.MetricsMap;
 import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.metrics.SolrMetricsContext;
 import org.junit.After;
@@ -32,27 +31,19 @@ public class BufferStoreTest extends SolrTestCase {
   private static final int blockSize = 1024;
 
   private Metrics metrics;
-  private MetricsMap metricsMap;
+  private SolrMetricManager metricManager;
+  private String registry;
 
   private Store store;
 
   @Before
   public void setup() {
     metrics = new Metrics();
-    SolrMetricManager metricManager = new SolrMetricManager();
-    String registry = TestUtil.randomSimpleString(random(), 2, 10);
+    metricManager = new SolrMetricManager();
+    registry = TestUtil.randomSimpleString(random(), 2, 10);
     String scope = TestUtil.randomSimpleString(random(), 2, 10);
     SolrMetricsContext solrMetricsContext = new SolrMetricsContext(metricManager, registry, "foo");
-    // TODO SOLR-17458: Fix test later
     metrics.initializeMetrics(solrMetricsContext, Attributes.empty(), scope);
-    metricsMap =
-        (MetricsMap)
-            ((SolrMetricManager.GaugeWrapper)
-                    metricManager
-                        .registry(registry)
-                        .getMetrics()
-                        .get("CACHE." + scope + ".hdfsBlockCache"))
-                .getGauge();
     BufferStore.initNewBuffer(blockSize, blockSize, metrics);
     store = BufferStore.instance(blockSize);
   }
@@ -99,19 +90,35 @@ public class BufferStoreTest extends SolrTestCase {
    * @param lost whether buffers should have been lost since the last call
    */
   private void assertGaugeMetricsChanged(boolean allocated, boolean lost) {
-    Map<String, Object> stats = metricsMap.getValue();
-
-    assertEquals(
-        "Buffer allocation metric not updating correctly.",
-        allocated,
-        isMetricPositive(stats, "buffercache.allocations"));
+    var gauge =
+        metricManager.getPrometheusMetricReader(registry).collect().stream()
+            .filter(ms -> ms.getMetadata().getPrometheusName().equals("solr_buffer_cache_stats"))
+            .map(GaugeSnapshot.class::cast)
+            .findFirst()
+            .orElseThrow(() -> new AssertionError("Missing gauge metric solr_buffer_cache_stats"));
+
+    var actualAllocatedValue = getBufferCacheStatsValue(gauge, "allocations");
+    var actualLostValue = getBufferCacheStatsValue(gauge, "lost");
     assertEquals(
-        "Buffer lost metric not updating correctly.",
-        lost,
-        isMetricPositive(stats, "buffercache.lost"));
+        "Buffer allocation metric not updating correctly.", allocated, actualAllocatedValue > 0);
+    assertEquals("Buffer allocation metric not updating correctly.", lost, actualLostValue > 0);
   }
 
-  private boolean isMetricPositive(Map<String, Object> stats, String metric) {
-    return new BigDecimal(stats.get(metric).toString()).compareTo(BigDecimal.ZERO) > 0;
+  private Double getBufferCacheStatsValue(GaugeSnapshot gaugeSnapshot, String type) {
+    return gaugeSnapshot.getDataPoints().stream()
+        .filter(
+            (dp) ->
+                dp.getLabels()
+                    .equals(
+                        Labels.of(
+                            "category",
+                            "CACHE",
+                            "otel_scope_name",
+                            "org.apache.solr",
+                            "type",
+                            type)))
+        .findFirst()
+        .orElseThrow(() -> new AssertionError("Missing type=" + type + " label on metric"))
+        .getValue();
   }
 }


(solr) 19/33: SOLR-17806: switch ReplicationHandler metrics to OTEL (#3514)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 5c87f8d8597f0174dada2898a3f5e2db7f707bd6
Author: Luke Kot-Zaniewski <lk...@bloomberg.net>
AuthorDate: Fri Sep 5 11:45:27 2025 -0400

    SOLR-17806: switch ReplicationHandler metrics to OTEL (#3514)
    
    * switch ReplicationHandler metrics to OTEL
    
    * switch ReplicationHandler metrics to OTEL
    
    * rename some gauges and IOUtils::closeQuietly
    
    * capture all metrics in the callback
---
 .../apache/solr/handler/ReplicationHandler.java    | 173 ++++++++++++---------
 .../org/apache/solr/metrics/SolrMetricManager.java |  50 +++---
 .../apache/solr/metrics/SolrMetricsContext.java    |  18 +++
 3 files changed, 152 insertions(+), 89 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java b/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
index d3aa17b7b12..b900dc22ddb 100644
--- a/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
@@ -31,6 +31,8 @@ import static org.apache.solr.handler.admin.api.ReplicationAPIBase.STATUS;
 import static org.apache.solr.handler.admin.api.ReplicationAPIBase.TLOG_FILE;
 
 import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.BatchCallback;
+import io.opentelemetry.api.metrics.ObservableLongMeasurement;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
@@ -79,6 +81,7 @@ import org.apache.solr.common.params.CoreAdminParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.ExecutorUtil;
+import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.common.util.SolrNamedThreadFactory;
@@ -100,8 +103,8 @@ import org.apache.solr.handler.admin.api.ReplicationAPIBase;
 import org.apache.solr.handler.admin.api.SnapshotBackupAPI;
 import org.apache.solr.handler.api.V2ApiUtils;
 import org.apache.solr.jersey.APIConfigProvider;
-import org.apache.solr.metrics.MetricsMap;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.OtelUnit;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.search.SolrIndexSearcher;
@@ -151,6 +154,7 @@ public class ReplicationHandler extends RequestHandlerBase
 
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
   SolrCore core;
+  private BatchCallback metricsCallback;
 
   @Override
   public Name getPermissionName(AuthorizationContext request) {
@@ -841,83 +845,110 @@ public class ReplicationHandler extends RequestHandlerBase
   @Override
   public void initializeMetrics(
       SolrMetricsContext parentContext, Attributes attributes, String scope) {
-    super.initializeMetrics(parentContext, attributes, scope);
-    solrMetricsContext.gauge(
-        () ->
-            (core != null && !core.isClosed()
-                ? NumberUtils.readableSize(core.getIndexSize())
-                : parentContext.nullString()),
-        true,
-        "indexSize",
-        getCategory().toString(),
-        scope);
-    solrMetricsContext.gauge(
-        () ->
-            (core != null && !core.isClosed()
-                ? getIndexVersion().toString()
-                : parentContext.nullString()),
-        true,
-        "indexVersion",
-        getCategory().toString(),
-        scope);
-    solrMetricsContext.gauge(
-        () ->
-            (core != null && !core.isClosed()
-                ? getIndexVersion().generation
-                : parentContext.nullNumber()),
-        true,
-        GENERATION,
-        getCategory().toString(),
-        scope);
-    solrMetricsContext.gauge(
-        () -> (core != null && !core.isClosed() ? core.getIndexDir() : parentContext.nullString()),
-        true,
-        "indexPath",
-        getCategory().toString(),
-        scope);
-    solrMetricsContext.gauge(() -> isLeader, true, "isLeader", getCategory().toString(), scope);
-    solrMetricsContext.gauge(() -> isFollower, true, "isFollower", getCategory().toString(), scope);
-    final MetricsMap fetcherMap =
-        new MetricsMap(
-            map -> {
+    Attributes replicationAttributes =
+        Attributes.builder()
+            .putAll(attributes)
+            .put(CATEGORY_ATTR, Category.REPLICATION.toString())
+            .build();
+    super.initializeMetrics(parentContext, replicationAttributes, scope);
+
+    ObservableLongMeasurement indexSizeMetric =
+        solrMetricsContext.longMeasurement(
+            "solr_replication_index_size", "Size of the index in bytes", OtelUnit.BYTES);
+
+    ObservableLongMeasurement indexVersionMetric =
+        solrMetricsContext.longMeasurement(
+            "solr_replication_index_version", "Current index version");
+
+    ObservableLongMeasurement indexGenerationMetric =
+        solrMetricsContext.longMeasurement(
+            "solr_replication_index_generation", "Current index generation");
+
+    ObservableLongMeasurement isLeaderMetric =
+        solrMetricsContext.longMeasurement(
+            "solr_replication_is_leader", "Whether this node is a leader (1) or not (0)");
+
+    ObservableLongMeasurement isFollowerMetric =
+        solrMetricsContext.longMeasurement(
+            "solr_replication_is_follower", "Whether this node is a follower (1) or not (0)");
+
+    ObservableLongMeasurement replicationEnabledMetric =
+        solrMetricsContext.longMeasurement(
+            "solr_replication_is_enabled", "Whether replication is enabled (1) or not (0)");
+
+    ObservableLongMeasurement isPollingDisabledMetric =
+        solrMetricsContext.longMeasurement(
+            "solr_replication_is_polling_disabled", "Whether polling is disabled (1) or not (0)");
+
+    ObservableLongMeasurement isReplicatingMetric =
+        solrMetricsContext.longMeasurement(
+            "solr_replication_is_replicating", "Whether replication is in progress (1) or not (0)");
+
+    ObservableLongMeasurement timeElapsedMetric =
+        solrMetricsContext.longMeasurement(
+            "solr_replication_time_elapsed",
+            "Time elapsed during replication in seconds",
+            OtelUnit.SECONDS);
+
+    ObservableLongMeasurement bytesDownloadedMetric =
+        solrMetricsContext.longMeasurement(
+            "solr_replication_downloaded_size",
+            "Total bytes downloaded during replication",
+            OtelUnit.BYTES);
+
+    ObservableLongMeasurement downloadSpeedMetric =
+        solrMetricsContext.longMeasurement(
+            "solr_replication_download_speed", "Download speed in bytes per second");
+
+    metricsCallback =
+        solrMetricsContext.batchCallback(
+            () -> {
+              if (core != null && !core.isClosed()) {
+                indexSizeMetric.record(core.getIndexSize(), replicationAttributes);
+
+                CommitVersionInfo vInfo = getIndexVersion();
+                if (vInfo != null) {
+                  indexVersionMetric.record(vInfo.version, replicationAttributes);
+                  indexGenerationMetric.record(vInfo.generation, replicationAttributes);
+                }
+              }
+
+              isLeaderMetric.record(isLeader ? 1 : 0, replicationAttributes);
+              isFollowerMetric.record(isFollower ? 1 : 0, replicationAttributes);
+              replicationEnabledMetric.record(
+                  (isLeader && replicationEnabled.get()) ? 1 : 0, replicationAttributes);
+
               IndexFetcher fetcher = currentIndexFetcher;
               if (fetcher != null) {
-                map.put(LEADER_URL, fetcher.getLeaderCoreUrl());
-                if (getPollInterval() != null) {
-                  map.put(ReplicationAPIBase.POLL_INTERVAL, getPollInterval());
-                }
-                map.put("isPollingDisabled", isPollingDisabled());
-                map.put("isReplicating", isReplicating());
+                isPollingDisabledMetric.record(isPollingDisabled() ? 1 : 0, replicationAttributes);
+                isReplicatingMetric.record(isReplicating() ? 1 : 0, replicationAttributes);
+
                 long elapsed = fetcher.getReplicationTimeElapsed();
                 long val = fetcher.getTotalBytesDownloaded();
                 if (elapsed > 0) {
-                  map.put("timeElapsed", elapsed);
-                  map.put("bytesDownloaded", val);
-                  map.put("downloadSpeed", val / elapsed);
+                  timeElapsedMetric.record(elapsed, replicationAttributes);
+                  bytesDownloadedMetric.record(val, replicationAttributes);
+                  downloadSpeedMetric.record(val / elapsed, replicationAttributes);
                 }
-                Properties props = loadReplicationProperties();
-                addReplicationProperties(map::putNoEx, props);
               }
-            });
-    solrMetricsContext.gauge(fetcherMap, true, "fetcher", getCategory().toString(), scope);
-    solrMetricsContext.gauge(
-        () -> isLeader && includeConfFiles != null ? includeConfFiles : "",
-        true,
-        "confFilesToReplicate",
-        getCategory().toString(),
-        scope);
-    solrMetricsContext.gauge(
-        () -> isLeader ? getReplicateAfterStrings() : Collections.<String>emptyList(),
-        true,
-        REPLICATE_AFTER,
-        getCategory().toString(),
-        scope);
-    solrMetricsContext.gauge(
-        () -> isLeader && replicationEnabled.get(),
-        true,
-        "replicationEnabled",
-        getCategory().toString(),
-        scope);
+            },
+            indexSizeMetric,
+            indexVersionMetric,
+            indexGenerationMetric,
+            isLeaderMetric,
+            isFollowerMetric,
+            replicationEnabledMetric,
+            isPollingDisabledMetric,
+            isReplicatingMetric,
+            timeElapsedMetric,
+            bytesDownloadedMetric,
+            downloadSpeedMetric);
+  }
+
+  @Override
+  public void close() throws IOException {
+    IOUtils.closeQuietly(metricsCallback);
+    super.close();
   }
 
   // TODO Should a failure retrieving any piece of info mark the overall request as a failure?  Is
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
index e2dd4f75158..bda5fa4af39 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
@@ -26,6 +26,7 @@ import com.codahale.metrics.MetricRegistry;
 import com.codahale.metrics.MetricSet;
 import com.codahale.metrics.SharedMetricRegistries;
 import com.codahale.metrics.Timer;
+import io.opentelemetry.api.metrics.BatchCallback;
 import io.opentelemetry.api.metrics.DoubleCounter;
 import io.opentelemetry.api.metrics.DoubleCounterBuilder;
 import io.opentelemetry.api.metrics.DoubleGauge;
@@ -50,6 +51,7 @@ import io.opentelemetry.api.metrics.ObservableLongCounter;
 import io.opentelemetry.api.metrics.ObservableLongGauge;
 import io.opentelemetry.api.metrics.ObservableLongMeasurement;
 import io.opentelemetry.api.metrics.ObservableLongUpDownCounter;
+import io.opentelemetry.api.metrics.ObservableMeasurement;
 import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
 import io.opentelemetry.sdk.metrics.SdkMeterProvider;
 import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil;
@@ -261,15 +263,7 @@ public class SolrMetricManager {
   }
 
   public LongGauge longGauge(String registry, String gaugeName, String description, OtelUnit unit) {
-    LongGaugeBuilder builder =
-        meterProvider(registry)
-            .get(OTEL_SCOPE_NAME)
-            .gaugeBuilder(gaugeName)
-            .setDescription(description)
-            .ofLongs();
-    if (unit != null) builder.setUnit(unit.getSymbol());
-
-    return builder.build();
+    return longGaugeBuilder(registry, gaugeName, description, unit).build();
   }
 
   public ObservableLongCounter observableLongCounter(
@@ -311,15 +305,7 @@ public class SolrMetricManager {
       String description,
       Consumer<ObservableLongMeasurement> callback,
       OtelUnit unit) {
-    LongGaugeBuilder builder =
-        meterProvider(registry)
-            .get(OTEL_SCOPE_NAME)
-            .gaugeBuilder(gaugeName)
-            .setDescription(description)
-            .ofLongs();
-    if (unit != null) builder.setUnit(unit.getSymbol());
-
-    return builder.buildWithCallback(callback);
+    return longGaugeBuilder(registry, gaugeName, description, unit).buildWithCallback(callback);
   }
 
   public ObservableDoubleGauge observableDoubleGauge(
@@ -372,6 +358,34 @@ public class SolrMetricManager {
     return builder.buildWithCallback(callback);
   }
 
+  ObservableLongMeasurement longMeasurement(
+      String registry, String gaugeName, String description, OtelUnit unit) {
+    return longGaugeBuilder(registry, gaugeName, description, unit).buildObserver();
+  }
+
+  BatchCallback batchCallback(
+      String registry,
+      Runnable callback,
+      ObservableMeasurement measurement,
+      ObservableMeasurement... additionalMeasurements) {
+    return meterProvider(registry)
+        .get(OTEL_SCOPE_NAME)
+        .batchCallback(callback, measurement, additionalMeasurements);
+  }
+
+  private LongGaugeBuilder longGaugeBuilder(
+      String registry, String gaugeName, String description, OtelUnit unit) {
+    LongGaugeBuilder builder =
+        meterProvider(registry)
+            .get(OTEL_SCOPE_NAME)
+            .gaugeBuilder(gaugeName)
+            .setDescription(description)
+            .ofLongs();
+    if (unit != null) builder.setUnit(unit.getSymbol());
+
+    return builder;
+  }
+
   // for unit tests
   public MetricRegistry.MetricSupplier<Counter> getCounterSupplier() {
     return counterSupplier;
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java
index f7f87303936..14b6affa475 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricsContext.java
@@ -23,6 +23,7 @@ import com.codahale.metrics.Histogram;
 import com.codahale.metrics.Meter;
 import com.codahale.metrics.MetricRegistry;
 import com.codahale.metrics.Timer;
+import io.opentelemetry.api.metrics.BatchCallback;
 import io.opentelemetry.api.metrics.DoubleCounter;
 import io.opentelemetry.api.metrics.DoubleGauge;
 import io.opentelemetry.api.metrics.DoubleHistogram;
@@ -37,6 +38,7 @@ import io.opentelemetry.api.metrics.ObservableDoubleMeasurement;
 import io.opentelemetry.api.metrics.ObservableLongCounter;
 import io.opentelemetry.api.metrics.ObservableLongGauge;
 import io.opentelemetry.api.metrics.ObservableLongMeasurement;
+import io.opentelemetry.api.metrics.ObservableMeasurement;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -274,6 +276,22 @@ public class SolrMetricsContext {
         registryName, metricName, description, callback, unit);
   }
 
+  public ObservableLongMeasurement longMeasurement(String metricName, String description) {
+    return longMeasurement(metricName, description, null);
+  }
+
+  public ObservableLongMeasurement longMeasurement(
+      String metricName, String description, OtelUnit unit) {
+    return metricManager.longMeasurement(registryName, metricName, description, unit);
+  }
+
+  public BatchCallback batchCallback(
+      Runnable callback,
+      ObservableMeasurement measurement,
+      ObservableMeasurement... additionalMeasurements) {
+    return metricManager.batchCallback(registryName, callback, measurement, additionalMeasurements);
+  }
+
   /**
    * Convenience method for {@link SolrMetricManager#meter(SolrMetricsContext, String, String,
    * String...)}.


(solr) 27/33: Migrate FieldCacheMetrics to OTEL (#3619)

Posted by ml...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 598b5d75d4fe5e39799b9637cc629a8c15ad4b43
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Fri Sep 19 16:06:06 2025 -0400

    Migrate FieldCacheMetrics to OTEL (#3619)
    
    Co-authored-by: Matthew Biscocho <mb...@bloomberg.net>
---
 .../java/org/apache/solr/core/CoreContainer.java   |  6 +-
 .../src/java/org/apache/solr/core/SolrCore.java    | 34 ++++----
 .../org/apache/solr/search/SolrFieldCacheBean.java | 37 +++++----
 .../apache/solr/uninverting/UninvertingReader.java |  8 +-
 .../apache/solr/search/TestSolrFieldCacheBean.java | 96 +++++++++++++---------
 5 files changed, 103 insertions(+), 78 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index 2da7fc9ef91..cfcdbd451c1 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -1027,8 +1027,10 @@ public class CoreContainer {
         "version");
 
     SolrFieldCacheBean fieldCacheBean = new SolrFieldCacheBean();
-    // NOCOMMIT SOLR-17458: Otel migration
-    fieldCacheBean.initializeMetrics(solrMetricsContext, Attributes.empty(), "");
+    fieldCacheBean.initializeMetrics(
+        solrMetricsContext,
+        Attributes.of(CATEGORY_ATTR, SolrInfoBean.Category.CACHE.toString()),
+        "");
 
     if (isZooKeeperAware()) {
       metricManager.loadClusterReporters(metricReporters, this);
diff --git a/solr/core/src/java/org/apache/solr/core/SolrCore.java b/solr/core/src/java/org/apache/solr/core/SolrCore.java
index a8cb4800fde..8118ab6c824 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrCore.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java
@@ -259,6 +259,7 @@ public class SolrCore implements SolrInfoBean, Closeable {
 
   private volatile boolean newSearcherReady = false;
 
+  private final Attributes coreAttributes;
   private AttributedLongCounter newSearcherCounter;
   private AttributedLongCounter newSearcherMaxReachedCounter;
   private AttributedLongCounter newSearcherOtherErrorsCounter;
@@ -1088,6 +1089,17 @@ public class SolrCore implements SolrInfoBean, Closeable {
       this.solrMetricsContext = coreMetricManager.getSolrMetricsContext();
       this.coreMetricManager.loadReporters();
 
+      if (coreContainer.isZooKeeperAware()) {
+        this.coreAttributes =
+            Attributes.builder()
+                .put(COLLECTION_ATTR, coreDescriptor.getCollectionName())
+                .put(CORE_ATTR, coreDescriptor.getName())
+                .put(SHARD_ATTR, coreDescriptor.getCloudDescriptor().getShardId())
+                .build();
+      } else {
+        this.coreAttributes = Attributes.builder().put(CORE_ATTR, coreDescriptor.getName()).build();
+      }
+
       if (updateHandler == null) {
         directoryFactory = initDirectoryFactory();
         recoveryStrategyBuilder = initRecoveryStrategyBuilder();
@@ -1109,21 +1121,7 @@ public class SolrCore implements SolrInfoBean, Closeable {
       setLatestSchema(schema);
 
       // initialize core metrics
-      if (coreContainer.isZooKeeperAware()) {
-        initializeMetrics(
-            solrMetricsContext,
-            Attributes.builder()
-                .put(COLLECTION_ATTR, coreDescriptor.getCollectionName())
-                .put(CORE_ATTR, coreDescriptor.getName())
-                .put(SHARD_ATTR, coreDescriptor.getCloudDescriptor().getShardId())
-                .build(),
-            "");
-      } else {
-        initializeMetrics(
-            solrMetricsContext,
-            Attributes.builder().put(CORE_ATTR, coreDescriptor.getName()).build(),
-            "");
-      }
+      initializeMetrics(solrMetricsContext, coreAttributes, "");
 
       // init pluggable circuit breakers, after metrics because some circuit breakers use metrics
       initPlugins(null, CircuitBreaker.class);
@@ -1131,8 +1129,10 @@ public class SolrCore implements SolrInfoBean, Closeable {
       SolrFieldCacheBean solrFieldCacheBean = new SolrFieldCacheBean();
       // this is registered at the CONTAINER level because it's not core-specific - for now we
       // also register it here for back-compat
-      // NOCOMMIT SOLR-17458: Add Otel
-      solrFieldCacheBean.initializeMetrics(solrMetricsContext, Attributes.empty(), "core");
+      solrFieldCacheBean.initializeMetrics(
+          solrMetricsContext,
+          coreAttributes.toBuilder().put(CATEGORY_ATTR, Category.CACHE.toString()).build(),
+          "");
       infoRegistry.put("fieldCache", solrFieldCacheBean);
 
       this.maxWarmingSearchers = solrConfig.maxWarmingSearchers;
diff --git a/solr/core/src/java/org/apache/solr/search/SolrFieldCacheBean.java b/solr/core/src/java/org/apache/solr/search/SolrFieldCacheBean.java
index b0ae73075ab..d341ed743c2 100644
--- a/solr/core/src/java/org/apache/solr/search/SolrFieldCacheBean.java
+++ b/solr/core/src/java/org/apache/solr/search/SolrFieldCacheBean.java
@@ -18,9 +18,10 @@ package org.apache.solr.search;
 
 import io.opentelemetry.api.common.Attributes;
 import org.apache.solr.common.util.EnvUtils;
+import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.core.SolrInfoBean;
-import org.apache.solr.metrics.MetricsMap;
 import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.OtelUnit;
 import org.apache.solr.uninverting.UninvertingReader;
 
 /** A SolrInfoBean that provides introspection of the Solr FieldCache */
@@ -30,6 +31,7 @@ public class SolrFieldCacheBean implements SolrInfoBean {
       EnvUtils.getPropertyAsBool("solr.metrics.fieldcache.entries.enabled", true);
   private boolean enableJmxEntryList =
       EnvUtils.getPropertyAsBool("solr.metrics.fieldcache.entries.jmx.enabled", true);
+  private AutoCloseable toClose;
 
   private SolrMetricsContext solrMetricsContext;
 
@@ -53,28 +55,35 @@ public class SolrFieldCacheBean implements SolrInfoBean {
     return solrMetricsContext;
   }
 
-  // TODO SOLR-17458: Migrate to Otel
   @Override
   public void initializeMetrics(
       SolrMetricsContext parentContext, Attributes attributes, String scope) {
     this.solrMetricsContext = parentContext;
-    MetricsMap metricsMap =
-        new MetricsMap(
-            map -> {
+    var solrCacheStats =
+        solrMetricsContext.longMeasurement(
+            "solr_field_cache_entries", "Number of field cache entries");
+    var solrCacheSize =
+        solrMetricsContext.longMeasurement(
+            "solr_field_cache_size", "Size of field cache in bytes", OtelUnit.BYTES);
+    this.toClose =
+        solrMetricsContext.batchCallback(
+            () -> {
               if (enableEntryList && enableJmxEntryList) {
                 UninvertingReader.FieldCacheStats fieldCacheStats =
                     UninvertingReader.getUninvertedStats();
                 String[] entries = fieldCacheStats.info;
-                map.put("entries_count", entries.length);
-                map.put("total_size", fieldCacheStats.totalSize);
-                for (int i = 0; i < entries.length; i++) {
-                  final String entry = entries[i];
-                  map.put("entry#" + i, entry);
-                }
+                solrCacheStats.record(entries.length, attributes);
+                solrCacheSize.record(fieldCacheStats.totalSize, attributes);
               } else {
-                map.put("entries_count", UninvertingReader.getUninvertedStatsSize());
+                solrCacheStats.record(UninvertingReader.getUninvertedStatsSize(), attributes);
               }
-            });
-    solrMetricsContext.gauge(metricsMap, true, "fieldCache", Category.CACHE.toString(), scope);
+            },
+            solrCacheStats,
+            solrCacheSize);
+  }
+
+  @Override
+  public void close() {
+    IOUtils.closeQuietly(toClose);
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/uninverting/UninvertingReader.java b/solr/core/src/java/org/apache/solr/uninverting/UninvertingReader.java
index c0f4f209921..5f85aac034e 100644
--- a/solr/core/src/java/org/apache/solr/uninverting/UninvertingReader.java
+++ b/solr/core/src/java/org/apache/solr/uninverting/UninvertingReader.java
@@ -38,7 +38,6 @@ import org.apache.lucene.index.LeafReader;
 import org.apache.lucene.index.NumericDocValues;
 import org.apache.lucene.index.SortedDocValues;
 import org.apache.lucene.index.SortedSetDocValues;
-import org.apache.lucene.util.RamUsageEstimator;
 import org.apache.solr.uninverting.FieldCache.CacheEntry;
 
 /**
@@ -467,8 +466,7 @@ public class UninvertingReader extends FilterLeafReader {
       info[i] = entries[i].toString();
       totalBytesUsed += entries[i].getValue().ramBytesUsed();
     }
-    String totalSize = RamUsageEstimator.humanReadableUnits(totalBytesUsed);
-    return new FieldCacheStats(totalSize, info);
+    return new FieldCacheStats(totalBytesUsed, info);
   }
 
   public static int getUninvertedStatsSize() {
@@ -481,10 +479,10 @@ public class UninvertingReader extends FilterLeafReader {
    * @lucene.internal
    */
   public static class FieldCacheStats {
-    public String totalSize;
+    public Long totalSize;
     public String[] info;
 
-    public FieldCacheStats(String totalSize, String[] info) {
+    public FieldCacheStats(Long totalSize, String[] info) {
       this.totalSize = totalSize;
       this.info = info;
     }
diff --git a/solr/core/src/test/org/apache/solr/search/TestSolrFieldCacheBean.java b/solr/core/src/test/org/apache/solr/search/TestSolrFieldCacheBean.java
index a69730afc4b..d39411ad5d8 100644
--- a/solr/core/src/test/org/apache/solr/search/TestSolrFieldCacheBean.java
+++ b/solr/core/src/test/org/apache/solr/search/TestSolrFieldCacheBean.java
@@ -16,12 +16,14 @@
  */
 package org.apache.solr.search;
 
+import static org.apache.solr.metrics.SolrMetricProducer.CATEGORY_ATTR;
+
 import io.opentelemetry.api.common.Attributes;
-import java.util.Map;
-import java.util.Random;
+import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
+import java.util.Optional;
 import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.metrics.MetricsMap;
+import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.metrics.SolrMetricsContext;
 import org.junit.BeforeClass;
@@ -29,6 +31,12 @@ import org.junit.Test;
 
 public class TestSolrFieldCacheBean extends SolrTestCaseJ4 {
 
+  private static final String ENTRIES_METRIC_NAME = "solr_field_cache_entries";
+  private static final String SIZE_BYTES_METRIC_NAME = "solr_field_cache_size_bytes";
+  private static final String DISABLE_ENTRY_LIST_PROPERTY = "disableSolrFieldCacheMBeanEntryList";
+  private static final String DISABLE_ENTRY_LIST_JMX_PROPERTY =
+      "disableSolrFieldCacheMBeanEntryListJmx";
+
   @BeforeClass
   public static void beforeClass() throws Exception {
     initCore("solrconfig.xml", "schema-minimal.xml");
@@ -36,69 +44,77 @@ public class TestSolrFieldCacheBean extends SolrTestCaseJ4 {
 
   @Test
   public void testEntryList() {
-    // ensure entries to FieldCache
+    // Ensure entries to FieldCache
     assertU(adoc("id", "id0"));
     assertU(commit());
     assertQ(req("q", "*:*", "sort", "id asc"), "//*[@numFound='1']");
 
     // Test with entry list enabled
-    assertEntryListIncluded(false);
+    assertEntryList(true);
 
     // Test again with entry list disabled
     System.setProperty("solr.metrics.fieldcache.entries.enabled", "false");
     try {
-      assertEntryListNotIncluded(false);
+      assertEntryList(false);
     } finally {
       System.clearProperty("solr.metrics.fieldcache.entries.enabled");
     }
 
-    // Test with entry list enabled for jmx
-    assertEntryListIncluded(true);
+    // Test with entry list enabled again
+    assertEntryList(true);
 
     // Test with entry list disabled for jmx
     System.setProperty("solr.metrics.fieldcache.entries.jmx.enabled", "false");
     try {
-      assertEntryListNotIncluded(true);
+      assertEntryList(false);
     } finally {
       System.clearProperty("solr.metrics.fieldcache.entries.jmx.enabled");
     }
   }
 
-  private void assertEntryListIncluded(boolean checkJmx) {
-    SolrFieldCacheBean mbean = new SolrFieldCacheBean();
-    Random r = random();
-    String registryName = TestUtil.randomSimpleString(r, 1, 10);
-    SolrMetricManager metricManager = h.getCoreContainer().getMetricManager();
-    SolrMetricsContext solrMetricsContext =
-        new SolrMetricsContext(metricManager, registryName, "foo");
-    mbean.initializeMetrics(solrMetricsContext, Attributes.empty(), null);
-    MetricsMap metricsMap =
-        (MetricsMap)
-            ((SolrMetricManager.GaugeWrapper)
-                    metricManager.registry(registryName).getMetrics().get("CACHE.fieldCache"))
-                .getGauge();
-    Map<String, Object> metrics = checkJmx ? metricsMap.getValue(true) : metricsMap.getValue();
-    assertTrue(((Number) metrics.get("entries_count")).longValue() > 0);
-    assertNotNull(metrics.get("total_size"));
-    assertNotNull(metrics.get("entry#0"));
+  private void assertEntryList(boolean bytesMetricIncluded) {
+    FieldCacheMetrics metrics = getFieldCacheMetrics();
+    assertTrue(
+        "Field cache entries count should be greater than 0",
+        metrics.entries().get().getDataPoints().getFirst().getValue() > 0);
+
+    if (bytesMetricIncluded) {
+      assertTrue("Size bytes metric should be present", metrics.sizeBytes().isPresent());
+    } else {
+      assertTrue("Size bytes metric should not be present", metrics.sizeBytes().isEmpty());
+    }
   }
 
-  private void assertEntryListNotIncluded(boolean checkJmx) {
-    SolrFieldCacheBean mbean = new SolrFieldCacheBean();
-    Random r = random();
-    String registryName = TestUtil.randomSimpleString(r, 1, 10);
+  private FieldCacheMetrics getFieldCacheMetrics() {
+    String registryName = TestUtil.randomSimpleString(random(), 1, 10);
     SolrMetricManager metricManager = h.getCoreContainer().getMetricManager();
     SolrMetricsContext solrMetricsContext =
         new SolrMetricsContext(metricManager, registryName, "foo");
-    mbean.initializeMetrics(solrMetricsContext, Attributes.empty(), null);
-    MetricsMap metricsMap =
-        (MetricsMap)
-            ((SolrMetricManager.GaugeWrapper)
-                    metricManager.registry(registryName).getMetrics().get("CACHE.fieldCache"))
-                .getGauge();
-    Map<String, Object> metrics = checkJmx ? metricsMap.getValue(true) : metricsMap.getValue();
-    assertTrue(((Number) metrics.get("entries_count")).longValue() > 0);
-    assertNull(metrics.get("total_size"));
-    assertNull(metrics.get("entry#0"));
+
+    try (SolrFieldCacheBean mbean = new SolrFieldCacheBean()) {
+      mbean.initializeMetrics(
+          solrMetricsContext,
+          Attributes.of(CATEGORY_ATTR, SolrInfoBean.Category.CACHE.toString()),
+          null);
+
+      var metrics = metricManager.getPrometheusMetricReader(registryName).collect();
+
+      var entryCount =
+          metrics.stream()
+              .filter(ms -> ENTRIES_METRIC_NAME.equals(ms.getMetadata().getPrometheusName()))
+              .map(GaugeSnapshot.class::cast)
+              .findFirst();
+
+      var sizeBytes =
+          metrics.stream()
+              .filter(ms -> SIZE_BYTES_METRIC_NAME.equals(ms.getMetadata().getPrometheusName()))
+              .map(GaugeSnapshot.class::cast)
+              .findFirst();
+
+      return new FieldCacheMetrics(entryCount, sizeBytes);
+    }
   }
+
+  private record FieldCacheMetrics(
+      Optional<GaugeSnapshot> entries, Optional<GaugeSnapshot> sizeBytes) {}
 }