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

andor pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zookeeper.git


The following commit(s) were added to refs/heads/master by this push:
     new 3fc7ccc05 ZOOKEEPER-4741: Modernize PrometheusMetricsProvider with 
Jetty and ne…
3fc7ccc05 is described below

commit 3fc7ccc053b1fb21aa553d9f41f263310a86d550
Author: Vasilii Alferov <[email protected]>
AuthorDate: Fri Dec 12 21:13:00 2025 +0000

    ZOOKEEPER-4741: Modernize PrometheusMetricsProvider with Jetty and ne…
    
    Reviewers: anmolnar
    Author: dukelion
    Closes #2291 from dukelion/ModernPrometheusClient
---
 .../zookeeper-prometheus-metrics/pom.xml           |  49 +-
 .../prometheus/PrometheusMetricsProvider.java      | 842 ++++++++-------------
 .../prometheus/PrometheusRegistryDumper.java       | 160 ++++
 .../metrics/prometheus/ExportJvmInfoTest.java      |   5 +-
 .../PrometheusHttpsMetricsProviderTest.java        |  98 ++-
 .../PrometheusMetricsProviderConfigTest.java       |   5 +-
 .../prometheus/PrometheusMetricsProviderTest.java  | 338 +++++----
 .../prometheus/PrometheusMetricsTestBase.java      |  31 +-
 .../test/resources/data/ssl/client_keystore.jks    | Bin 2246 -> 2591 bytes
 .../test/resources/data/ssl/client_truststore.jks  | Bin 986 -> 1218 bytes
 .../test/resources/data/ssl/server_keystore.jks    | Bin 2276 -> 2615 bytes
 .../test/resources/data/ssl/server_truststore.jks  | Bin 958 -> 1186 bytes
 12 files changed, 771 insertions(+), 757 deletions(-)

diff --git a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/pom.xml 
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/pom.xml
index 4b13fa6ad..ddc4fe22e 100644
--- a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/pom.xml
+++ b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/pom.xml
@@ -32,7 +32,8 @@
   <description>ZooKeeper Prometheus.io Metrics Provider 
implementation</description>
 
   <properties>
-      <prometheus.version>0.9.0</prometheus.version>
+      <prometheus.version>1.3.10</prometheus.version>
+      <jetty.version>9.4.57.v20241219</jetty.version>
   </properties>
   <dependencies>
     <dependency>
@@ -46,28 +47,56 @@
     </dependency>    
     <dependency>
       <groupId>io.prometheus</groupId>
-      <artifactId>simpleclient</artifactId>
+      <artifactId>prometheus-metrics-core</artifactId>
       <version>${prometheus.version}</version>
     </dependency>
     <dependency>
       <groupId>io.prometheus</groupId>
-      <artifactId>simpleclient_hotspot</artifactId>
+      <artifactId>prometheus-metrics-instrumentation-jvm</artifactId>
       <version>${prometheus.version}</version>
     </dependency>
     <dependency>
       <groupId>io.prometheus</groupId>
-      <artifactId>simpleclient_servlet</artifactId>
+      <artifactId>prometheus-metrics-exporter-servlet-javax</artifactId>
       <version>${prometheus.version}</version>
     </dependency>
     <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-server</artifactId>
-      <scope>provided</scope>
+      <groupId>io.prometheus</groupId>
+      <artifactId>prometheus-metrics-exporter-httpserver</artifactId>
+      <version>${prometheus.version}</version>
+    </dependency>
+    <dependency>
+        <groupId>io.prometheus</groupId>
+        <artifactId>prometheus-metrics-exposition-formats</artifactId>
+        <version>${prometheus.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>io.prometheus</groupId>
+      <artifactId>prometheus-metrics-config</artifactId>
+      <version>${prometheus.version}</version>
+    </dependency>
+    <dependency>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-server</artifactId>
+        <version>${jetty.version}</version>
+    </dependency>
+    <dependency>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-servlet</artifactId>
+        <version>${jetty.version}</version>
+    </dependency>
+    <!-- You likely don't need this, but if you do, here's the correct syntax 
-->
+    <dependency>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-util</artifactId>
+        <version>${jetty.version}</version>
+        <!-- You could add a scope here if needed, e.g., 
<scope>provided</scope> -->
     </dependency>
     <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-servlet</artifactId>
-      <scope>provided</scope>
+        <groupId>javax.servlet</groupId>
+        <artifactId>javax.servlet-api</artifactId>
+        <version>3.1.0</version>
+        <scope>provided</scope>
     </dependency>
     <dependency>
       <groupId>ch.qos.logback</groupId>
diff --git 
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/main/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProvider.java
 
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/main/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProvider.java
index 76739787c..1fbb34169 100644
--- 
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/main/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProvider.java
+++ 
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/main/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProvider.java
@@ -7,7 +7,7 @@
  * "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
+ * 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,
@@ -18,25 +18,16 @@
 
 package org.apache.zookeeper.metrics.prometheus;
 
-import io.prometheus.client.Collector;
-import io.prometheus.client.CollectorRegistry;
-import io.prometheus.client.exporter.MetricsServlet;
-import io.prometheus.client.hotspot.DefaultExports;
+import io.prometheus.metrics.core.metrics.GaugeWithCallback;
+import io.prometheus.metrics.exporter.servlet.javax.PrometheusMetricsServlet;
+import io.prometheus.metrics.instrumentation.jvm.JvmMetrics;
+import io.prometheus.metrics.model.registry.PrometheusRegistry;
 import java.io.IOException;
-import java.util.Enumeration;
+import java.util.Map;
 import java.util.Objects;
-import java.util.Optional;
 import java.util.Properties;
-import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.RejectedExecutionHandler;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.BiConsumer;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
@@ -46,699 +37,494 @@
 import org.apache.zookeeper.metrics.Gauge;
 import org.apache.zookeeper.metrics.GaugeSet;
 import org.apache.zookeeper.metrics.MetricsContext;
+import org.apache.zookeeper.metrics.MetricsContext.DetailLevel;
 import org.apache.zookeeper.metrics.MetricsProvider;
 import org.apache.zookeeper.metrics.MetricsProviderLifeCycleException;
 import org.apache.zookeeper.metrics.Summary;
 import org.apache.zookeeper.metrics.SummarySet;
-import org.apache.zookeeper.server.RateLogger;
-import org.eclipse.jetty.security.ConstraintMapping;
-import org.eclipse.jetty.security.ConstraintSecurityHandler;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.util.security.Constraint;
 import org.eclipse.jetty.util.ssl.KeyStoreScanner;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
  * A Metrics Provider implementation based on https://prometheus.io.
- *
+ * This implementation uses prometheus-metrics-core interfaces and exposes 
metrics via an embedded Jetty server
  * @since 3.6.0
  */
 public class PrometheusMetricsProvider implements MetricsProvider {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(PrometheusMetricsProvider.class);
     private static final String LABEL = "key";
-    private static final String[] LABELS = {LABEL};
-
-    /**
-     * Number of worker threads for reporting Prometheus summary metrics.
-     * Default value is 1.
-     * If the number is less than 1, the main thread will be used.
-     */
-    static final String NUM_WORKER_THREADS = "numWorkerThreads";
-
-    /**
-     * The max queue size for Prometheus summary metrics reporting task.
-     * Default value is 10000.
-     */
-    static final String MAX_QUEUE_SIZE = "maxQueueSize";
-
-    /**
-     * The timeout in ms for Prometheus worker threads shutdown.
-     * Default value is 1000ms.
-     */
-    static final String WORKER_SHUTDOWN_TIMEOUT_MS = "workerShutdownTimeoutMs";
 
-    /**
-     * We are using the 'defaultRegistry'.
-     * <p>
-     * When you are running ZooKeeper (server or client) together with other
-     * libraries every metrics will be expected as a single view.
-     * </p>
-     */
-    private final CollectorRegistry collectorRegistry = 
CollectorRegistry.defaultRegistry;
-    private final RateLogger rateLogger = new RateLogger(LOG, 60 * 1000);
-    private String host = "0.0.0.0";
+    private final PrometheusRegistry registry = 
PrometheusRegistry.defaultRegistry;
     private int httpPort = -1;
     private int httpsPort = -1;
     private boolean exportJvmInfo = true;
-    private Server server;
-    private final MetricsServletImpl servlet = new MetricsServletImpl();
     private final Context rootContext = new Context();
-    private int numWorkerThreads = 1;
-    private int maxQueueSize = 10000;
-    private long workerShutdownTimeoutMs = 1000;
-    private Optional<ExecutorService> executorOptional = Optional.empty();
+    private PrometheusRegistryDumper dumper;
+    private CustomPrometheusMetricsServlet servlet;
 
-    // Constants for SSL configuration
-    public static final int SCAN_INTERVAL = 60 * 10; // 10 minutes
+    private Server server;
+    private int numWorkerThreads;
+    private String host;
+
+    // SSL Configuration fields
+    private String keyStorePath;
+    private String keyStorePassword;
+    private String keyStoreType;
+    private String trustStorePath;
+    private String trustStorePassword;
+    private String trustStoreType;
+    private boolean needClientAuth = true; // Secure default
+    private boolean wantClientAuth = true; // Secure default
+
+    // Constants for configuration
+    public static final String NUM_WORKER_THREADS = "numWorkerThreads";
     public static final String SSL_KEYSTORE_LOCATION = "ssl.keyStore.location";
     public static final String SSL_KEYSTORE_PASSWORD = "ssl.keyStore.password";
     public static final String SSL_KEYSTORE_TYPE = "ssl.keyStore.type";
     public static final String SSL_TRUSTSTORE_LOCATION = 
"ssl.trustStore.location";
     public static final String SSL_TRUSTSTORE_PASSWORD = 
"ssl.trustStore.password";
     public static final String SSL_TRUSTSTORE_TYPE = "ssl.trustStore.type";
-    public static final String SSL_X509_CN = "ssl.x509.cn";
-    public static final String SSL_X509_REGEX_CN = "ssl.x509.cn.regex";
     public static final String SSL_NEED_CLIENT_AUTH = "ssl.need.client.auth";
     public static final String SSL_WANT_CLIENT_AUTH = "ssl.want.client.auth";
+    public static final int SCAN_INTERVAL = 60 * 10; // 10 minutes
 
-    private String keyStorePath;
-    private String keyStorePassword;
-    private String keyStoreType;
-    private String trustStorePath;
-    private String trustStorePassword;
-    private String trustStoreType;
-    private boolean needClientAuth = true;
-    private boolean wantClientAuth = true;
+    /**
+     * Custom servlet to disable the TRACE method for security reasons.
+     */
+    private static class CustomPrometheusMetricsServlet extends 
PrometheusMetricsServlet {
+        public CustomPrometheusMetricsServlet(PrometheusRegistry registry) {
+            super(registry);
+        }
+
+        @Override
+        protected void doTrace(HttpServletRequest req, HttpServletResponse 
resp) throws ServletException, IOException {
+            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+        }
+    }
 
     @Override
     public void configure(Properties configuration) throws 
MetricsProviderLifeCycleException {
-        LOG.info("Initializing metrics, configuration: {}", configuration);
+        LOG.info("Initializing Prometheus metrics with Jetty, configuration: 
{}", configuration);
+
         this.host = configuration.getProperty("httpHost", "0.0.0.0");
-        if (configuration.containsKey("httpsPort")) {
-            this.httpsPort = 
Integer.parseInt(configuration.getProperty("httpsPort"));
+        this.httpPort = Integer.parseInt(configuration.getProperty("httpPort", 
"-1"));
+        this.httpsPort = 
Integer.parseInt(configuration.getProperty("httpsPort", "-1"));
+        this.exportJvmInfo = 
Boolean.parseBoolean(configuration.getProperty("exportJvmInfo", "true"));
+        this.numWorkerThreads = 
Integer.parseInt(configuration.getProperty(NUM_WORKER_THREADS, "10"));
+
+        // If httpsPort is specified, parse all SSL properties
+        if (this.httpsPort != -1) {
             this.keyStorePath = 
configuration.getProperty(SSL_KEYSTORE_LOCATION);
             this.keyStorePassword = 
configuration.getProperty(SSL_KEYSTORE_PASSWORD);
-            this.keyStoreType = configuration.getProperty(SSL_KEYSTORE_TYPE);
+            this.keyStoreType = configuration.getProperty(SSL_KEYSTORE_TYPE, 
"PKCS12");
             this.trustStorePath = 
configuration.getProperty(SSL_TRUSTSTORE_LOCATION);
             this.trustStorePassword = 
configuration.getProperty(SSL_TRUSTSTORE_PASSWORD);
-            this.trustStoreType = 
configuration.getProperty(SSL_TRUSTSTORE_TYPE);
+            this.trustStoreType = 
configuration.getProperty(SSL_TRUSTSTORE_TYPE, "PKCS12");
             this.needClientAuth = 
Boolean.parseBoolean(configuration.getProperty(SSL_NEED_CLIENT_AUTH, "true"));
             this.wantClientAuth = 
Boolean.parseBoolean(configuration.getProperty(SSL_WANT_CLIENT_AUTH, "true"));
-            //check if httpPort is also configured
-            this.httpPort = 
Integer.parseInt(configuration.getProperty("httpPort", "-1"));
-        } else {
-            // Use the default HTTP port (7000) or the configured port if 
HTTPS is not set.
-            this.httpPort = 
Integer.parseInt(configuration.getProperty("httpPort", "7000"));
         }
-        this.exportJvmInfo = 
Boolean.parseBoolean(configuration.getProperty("exportJvmInfo", "true"));
-        this.numWorkerThreads = Integer.parseInt(
-                configuration.getProperty(NUM_WORKER_THREADS, "1"));
-        this.maxQueueSize = Integer.parseInt(
-                configuration.getProperty(MAX_QUEUE_SIZE, "10000"));
-        this.workerShutdownTimeoutMs = Long.parseLong(
-                configuration.getProperty(WORKER_SHUTDOWN_TIMEOUT_MS, "1000"));
+
+        // Validate that at least one port is configured.
+        if (httpPort == -1 && httpsPort == -1) {
+            throw new MetricsProviderLifeCycleException(
+                    "Either httpPort or httpsPort must be configured for 
Prometheus exporter.");
+        }
+
+        this.dumper = new PrometheusRegistryDumper(this.registry);
+        this.servlet = new CustomPrometheusMetricsServlet(this.registry);
     }
 
     @Override
     public void start() throws MetricsProviderLifeCycleException {
-        this.executorOptional = createExecutor();
+        // Register JVM metrics if enabled
+        if (exportJvmInfo) {
+            JvmMetrics.builder().register(this.registry);
+        }
         try {
-            LOG.info("Starting /metrics endpoint at HTTP port: {}, HTTPS port: 
{}, exportJvmInfo: {}",
-                    httpPort >= 0 ? httpPort : "disabled",
-                    httpsPort >= 0 ? httpsPort : "disabled",
-                    exportJvmInfo);
-            if (exportJvmInfo) {
-                DefaultExports.initialize();
-            }
-            server = new Server();
-            ServerConnector httpConnector = null;
-            ServerConnector httpsConnector = null;
-            if (httpPort >= 0) {
-                httpConnector = new ServerConnector(server);
-                httpConnector.setHost(host);
-                httpConnector.setPort(httpPort);
+            LOG.info("Starting Prometheus Jetty server...");
+
+            // QueuedThreadPool needs a minimum of 4 threads for stable 
operation
+            QueuedThreadPool threadPool = new 
QueuedThreadPool(Math.max(this.numWorkerThreads + 3, 4));
+            threadPool.setReservedThreads(0);
+            threadPool.setName("prometheus-jetty-server");
+
+            this.server = new Server(threadPool);
+
+            // Define number of acceptors and selectors for connectors
+            int acceptors = 1;
+            int selectors = 1;
+
+            // Configure HTTP connector if enabled
+            if (this.httpPort != -1) {
+                ServerConnector httpConnector = new ServerConnector(server, 
acceptors, selectors);
+                httpConnector.setPort(this.httpPort);
+                httpConnector.setHost(this.host);
                 server.addConnector(httpConnector);
             }
-            if (httpsPort >= 0) {
-                SslContextFactory sslServerContextFactory = new 
SslContextFactory.Server();
-                configureSslContextFactory(sslServerContextFactory);
-                KeyStoreScanner keystoreScanner = new 
KeyStoreScanner(sslServerContextFactory);
+
+            // Configure HTTPS connector if enabled
+            if (this.httpsPort != -1) {
+                SslContextFactory.Server sslContextFactory = 
createSslContextFactory();
+                KeyStoreScanner keystoreScanner = new 
KeyStoreScanner(sslContextFactory);
                 keystoreScanner.setScanInterval(SCAN_INTERVAL);
                 server.addBean(keystoreScanner);
-                httpsConnector = new ServerConnector(server, 
sslServerContextFactory);
-                httpsConnector.setHost(host);
-                httpsConnector.setPort(httpsPort);
-                server.addConnector(httpsConnector);
+                server.addConnector(createSslConnector(server, acceptors, 
selectors, sslContextFactory));
             }
+
+            // Set up the servlet context handler
             ServletContextHandler context = new ServletContextHandler();
             context.setContextPath("/");
-            constrainTraceMethod(context);
             server.setHandler(context);
             context.addServlet(new ServletHolder(servlet), "/metrics");
+
             server.start();
-            if (httpPort == 0) {
-                LOG.info("Bound /metrics endpoint to HTTP port: {}", 
httpConnector.getLocalPort());
-            }
-            if (httpsPort == 0) {
-                LOG.info("Bound /metrics endpoint to HTTPS port: {}", 
httpsConnector.getLocalPort());
-            }
-        } catch (Exception err) {
-            LOG.error("Cannot start /metrics server", err);
-            if (server != null) {
-                try {
-                    server.stop();
-                } catch (Exception suppressed) {
-                    err.addSuppressed(suppressed);
-                } finally {
-                    server = null;
-                }
-            }
-            throw new MetricsProviderLifeCycleException(err);
+
+            LOG.info("Prometheus metrics provider with Jetty started. HTTP 
port: {}, HTTPS port: {}",
+                    httpPort != -1 ? httpPort : "disabled", httpsPort != -1 ? 
httpsPort : "disabled");
+
+        } catch (Exception e) {
+            LOG.error("Failed to start Prometheus Jetty server", e);
+            // Ensure server is stopped on startup failure
+            stop();
+            throw new MetricsProviderLifeCycleException("Failed to start 
Prometheus Jetty server", e);
         }
     }
 
-    @SuppressWarnings("deprecation")
-    private void configureSslContextFactory(SslContextFactory 
sslServerContextFactory) {
-        if (keyStorePath != null) {
-            sslServerContextFactory.setKeyStorePath(keyStorePath);
-        } else {
-            LOG.error("KeyStore configuration is incomplete keyStorePath: {}", 
keyStorePath);
-            throw new IllegalStateException("KeyStore configuration is 
incomplete keyStorePath: " + keyStorePath);
-        }
-        if (keyStorePassword != null) {
-            sslServerContextFactory.setKeyStorePassword(keyStorePassword);
-        } else {
-            LOG.error("keyStorePassword configuration is incomplete ");
-            throw new IllegalStateException("keyStorePassword configuration is 
incomplete ");
-        }
-        if (keyStoreType != null) {
-            sslServerContextFactory.setKeyStoreType(keyStoreType);
+    /**
+     * Creates and configures the SslContextFactory for the server.
+     *
+     * @return A configured SslContextFactory.Server instance.
+     */
+    private SslContextFactory.Server createSslContextFactory() {
+        SslContextFactory.Server sslContextFactory = new 
SslContextFactory.Server();
+
+        // Validate and set KeyStore properties
+        if (this.keyStorePath == null || this.keyStorePath.isEmpty()) {
+            throw new IllegalArgumentException("SSL/TLS is enabled, but '" + 
SSL_KEYSTORE_LOCATION + "' is not set.");
         }
-        if (trustStorePath != null) {
-            sslServerContextFactory.setTrustStorePath(trustStorePath);
-        } else {
-            LOG.error("TrustStore configuration is incomplete trustStorePath: 
{}", trustStorePath);
-            throw new IllegalStateException("TrustStore configuration is 
incomplete trustStorePath: " + trustStorePath);
+        sslContextFactory.setKeyStorePath(this.keyStorePath);
+        sslContextFactory.setKeyStorePassword(this.keyStorePassword);
+        if (this.keyStoreType != null) {
+            sslContextFactory.setKeyStoreType(this.keyStoreType);
         }
-        if (trustStorePassword != null) {
-            sslServerContextFactory.setTrustStorePassword(trustStorePassword);
-        } else {
-            LOG.error("trustStorePassword configuration is incomplete");
-            throw new IllegalStateException("trustStorePassword configuration 
is incomplete");
+
+        // Validate and set TrustStore properties (often needed for client 
auth)
+        if (this.needClientAuth && (this.trustStorePath == null || 
this.trustStorePath.isEmpty())) {
+            throw new IllegalArgumentException(
+                    "'" + SSL_NEED_CLIENT_AUTH + "' is true, but '" + 
SSL_TRUSTSTORE_LOCATION + "' is not set.");
         }
-        if (trustStoreType != null) {
-            sslServerContextFactory.setTrustStoreType(trustStoreType);
+        if (this.trustStorePath != null) {
+            sslContextFactory.setTrustStorePath(this.trustStorePath);
+            sslContextFactory.setTrustStorePassword(this.trustStorePassword);
+            if (this.trustStoreType != null) {
+                sslContextFactory.setTrustStoreType(this.trustStoreType);
+            }
         }
-        sslServerContextFactory
-                .setNeedClientAuth(needClientAuth);
-        sslServerContextFactory
-                .setWantClientAuth(wantClientAuth);
-    }
 
-    // for tests
-    MetricsServletImpl getServlet() {
-        return servlet;
+        sslContextFactory.setNeedClientAuth(this.needClientAuth);
+        sslContextFactory.setWantClientAuth(this.wantClientAuth);
+
+        return sslContextFactory;
     }
 
-    @Override
-    public MetricsContext getRootContext() {
-        return rootContext;
+    /**
+     * Creates and configures an SSL/TLS connector for the Jetty server.
+     *
+     * @param server
+     *            The server instance.
+     * @param acceptors
+     *            The number of acceptor threads.
+     * @param selectors
+     *            The number of selector threads.
+     * @param sslContextFactory
+     *            The pre-configured SslContextFactory.
+     *
+     * @return A configured ServerConnector for HTTPS.
+     */
+    private ServerConnector createSslConnector(Server server, int acceptors, 
int selectors,
+            SslContextFactory.Server sslContextFactory) {
+        ServerConnector sslConnector = new ServerConnector(server, acceptors, 
selectors, sslContextFactory);
+        sslConnector.setPort(this.httpsPort);
+        sslConnector.setHost(this.host);
+        return sslConnector;
     }
 
     @Override
     public void stop() {
-        shutdownExecutor();
         if (server != null) {
             try {
+                LOG.info("Stopping Prometheus Jetty server.");
                 server.stop();
             } catch (Exception err) {
-                LOG.error("Cannot safely stop Jetty server", err);
+                LOG.error("Cannot safely stop Prometheus Jetty server", err);
             } finally {
                 server = null;
             }
         }
+        registry.clear();
     }
 
     /**
-     * Dump all values to the 4lw interface and to the Admin server.
-     * <p>
-     * This method is not expected to be used to serve metrics to Prometheus. 
We
-     * are using the MetricsServlet provided by Prometheus for that, leaving 
the
-     * real representation to the Prometheus Java client.
-     * </p>
-     *
-     * @param sink the receiver of data (4lw interface, Admin server or tests)
+     * Returns a Prometheus servlet for integration with existing web 
applications. This is primarily used for testing
+     * purposes.
      */
-    @Override
-    public void dump(BiConsumer<String, Object> sink) {
-        sampleGauges();
-        Enumeration<Collector.MetricFamilySamples> samplesFamilies = 
collectorRegistry.metricFamilySamples();
-        while (samplesFamilies.hasMoreElements()) {
-            Collector.MetricFamilySamples samples = 
samplesFamilies.nextElement();
-            samples.samples.forEach(sample -> {
-                String key = buildKeyForDump(sample);
-                sink.accept(key, sample.value);
-            });
-        }
+    public PrometheusMetricsServlet getServlet() {
+        return this.servlet;
     }
 
-    private static String buildKeyForDump(Collector.MetricFamilySamples.Sample 
sample) {
-        StringBuilder keyBuilder = new StringBuilder();
-        keyBuilder.append(sample.name);
-        if (sample.labelNames.size() > 0) {
-            keyBuilder.append('{');
-            for (int i = 0; i < sample.labelNames.size(); ++i) {
-                if (i > 0) {
-                    keyBuilder.append(',');
-                }
-                keyBuilder.append(sample.labelNames.get(i));
-                keyBuilder.append("=\"");
-                keyBuilder.append(sample.labelValues.get(i));
-                keyBuilder.append('"');
-            }
-            keyBuilder.append('}');
-        }
-        return keyBuilder.toString();
+    @Override
+    public MetricsContext getRootContext() {
+        return rootContext;
     }
 
-    /**
-     * Update Gauges. In ZooKeeper Metrics API Gauges are callbacks served by
-     * internal components and the value is not held by Prometheus structures.
-     */
-    private void sampleGauges() {
-        rootContext.gauges.values()
-                .forEach(PrometheusGaugeWrapper::sample);
-
-        rootContext.gaugeSets.values()
-                .forEach(PrometheusLabelledGaugeWrapper::sample);
+    @Override
+    public void dump(BiConsumer<String, Object> sink) {
+        dumper.dump().forEach(sink);
     }
 
     @Override
     public void resetAllValues() {
-        // not supported on Prometheus
+        // The new prometheus client does not support resetting metric values.
+        LOG.debug("resetAllValues is a no-op for PrometheusMetricsProvider");
     }
 
     /**
-     * Add constraint to a given context to disallow TRACE method.
-     * @param ctxHandler the context to modify
+     * Inner class implementing the MetricsContext interface. It handles the 
creation and registration of different
+     * metric types.
      */
-    private void constrainTraceMethod(ServletContextHandler ctxHandler) {
-        Constraint c = new Constraint();
-        c.setAuthenticate(true);
-
-        ConstraintMapping cmt = new ConstraintMapping();
-        cmt.setConstraint(c);
-        cmt.setMethod("TRACE");
-        cmt.setPathSpec("/*");
-
-        ConstraintSecurityHandler securityHandler = new 
ConstraintSecurityHandler();
-        securityHandler.setConstraintMappings(new ConstraintMapping[] {cmt});
-
-        ctxHandler.setSecurityHandler(securityHandler);
-    }
-
     private class Context implements MetricsContext {
 
-        private final ConcurrentMap<String, PrometheusGaugeWrapper> gauges = 
new ConcurrentHashMap<>();
-        private final ConcurrentMap<String, PrometheusLabelledGaugeWrapper> 
gaugeSets = new ConcurrentHashMap<>();
-        private final ConcurrentMap<String, PrometheusCounter> counters = new 
ConcurrentHashMap<>();
-        private final ConcurrentMap<String, PrometheusLabelledCounter> 
counterSets = new ConcurrentHashMap<>();
-        private final ConcurrentMap<String, PrometheusSummary> basicSummaries 
= new ConcurrentHashMap<>();
-        private final ConcurrentMap<String, PrometheusSummary> summaries = new 
ConcurrentHashMap<>();
-        private final ConcurrentMap<String, PrometheusLabelledSummary> 
basicSummarySets = new ConcurrentHashMap<>();
-        private final ConcurrentMap<String, PrometheusLabelledSummary> 
summarySets = new ConcurrentHashMap<>();
+        private final ConcurrentMap<String, PrometheusCounterWrapper> counters 
=
+            new ConcurrentHashMap<>();
+        private final ConcurrentMap<String, PrometheusLabelledCounterWrapper> 
counterSets =
+            new ConcurrentHashMap<>();
+        private final ConcurrentMap<String, GaugeWithCallback> 
registeredGauges =
+            new ConcurrentHashMap<>();
+        private final ConcurrentMap<String, PrometheusSummaryWrapper> 
basicSummaries =
+            new ConcurrentHashMap<>();
+        private final ConcurrentMap<String, PrometheusSummaryWrapper> 
advancedSummaries =
+            new ConcurrentHashMap<>();
+        private final ConcurrentMap<String, PrometheusLabelledSummaryWrapper> 
basicSummarySets =
+            new ConcurrentHashMap<>();
+        private final ConcurrentMap<String, PrometheusLabelledSummaryWrapper> 
advancedSummarySets =
+            new ConcurrentHashMap<>();
 
         @Override
         public MetricsContext getContext(String name) {
-            // no hierarchy yet
+            // This provider uses a flat namespace, so sub-contexts are not 
needed.
             return this;
         }
 
         @Override
         public Counter getCounter(String name) {
-            return counters.computeIfAbsent(name, PrometheusCounter::new);
+            return counters.computeIfAbsent(name, key -> {
+                io.prometheus.metrics.core.metrics.Counter prometheusCounter =
+                        io.prometheus.metrics.core.metrics.Counter
+                        .builder().name(key).help(key + " 
counter").register(registry);
+                return new PrometheusCounterWrapper(prometheusCounter);
+            });
         }
 
         @Override
-        public CounterSet getCounterSet(final String name) {
-            Objects.requireNonNull(name, "Cannot register a CounterSet with 
null name");
-            return counterSets.computeIfAbsent(name, 
PrometheusLabelledCounter::new);
+        public CounterSet getCounterSet(String name) {
+            return counterSets.computeIfAbsent(name, key -> {
+                Objects.requireNonNull(name, "Cannot register a CounterSet 
with null name");
+                io.prometheus.metrics.core.metrics.Counter prometheusCounter =
+                        io.prometheus.metrics.core.metrics.Counter
+                        .builder().name(key).help(key + " counter 
set").labelNames(LABEL).register(registry);
+                return new PrometheusLabelledCounterWrapper(prometheusCounter);
+            });
         }
 
-        /**
-         * Gauges may go up and down, in ZooKeeper they are a way to export
-         * internal values with a callback.
-         *
-         * @param name  the name of the gauge
-         * @param gauge the callback
-         */
         @Override
-        public void registerGauge(String name, Gauge gauge) {
-            Objects.requireNonNull(name);
-            gauges.compute(name, (id, prev) ->
-                    new PrometheusGaugeWrapper(id, gauge, prev != null ? 
prev.inner : null));
+        public void registerGaugeSet(final String name, final GaugeSet 
gaugeSet) {
+            Objects.requireNonNull(name, "Cannot register a GaugeSet with null 
name");
+            Objects.requireNonNull(gaugeSet, "Cannot register a null GaugeSet 
for " + name);
+
+            GaugeWithCallback oldGauge = registeredGauges.get(name);
+            if (oldGauge != null) {
+                registry.unregister(oldGauge);
+            }
+
+            GaugeWithCallback newGauge = 
GaugeWithCallback.builder().name(name).help(name).labelNames(LABEL)
+                    .callback(callback -> {
+                        Map<String, Number> values = gaugeSet.values();
+                        if (values != null) {
+                            for (Map.Entry<String, Number> value : 
values.entrySet()) {
+                                if (value.getKey() == null) {
+                                    throw new 
IllegalArgumentException("GaugeSet key cannot be null.");
+                                }
+                                callback.call(value.getValue().doubleValue(), 
value.getKey());
+                            }
+                        }
+                    }).register(registry);
+            registeredGauges.put(name, newGauge);
         }
 
         @Override
-        public void unregisterGauge(String name) {
-            PrometheusGaugeWrapper existing = gauges.remove(name);
-            if (existing != null) {
-                existing.unregister();
+        public void registerGauge(String name, Gauge gauge) {
+            if (name == null) {
+                throw new IllegalArgumentException("Gauge name cannot be 
null.");
+            }
+            if (gauge == null) {
+                throw new IllegalArgumentException("Cannot register a null 
Gauge for " + name);
             }
+
+            GaugeWithCallback oldGauge = registeredGauges.get(name);
+            if (oldGauge != null) {
+                registry.unregister(oldGauge);
+            }
+
+            GaugeWithCallback newGauge = 
GaugeWithCallback.builder().name(name).help(name).callback(callback -> {
+                Number value = gauge.get();
+                if (value != null) {
+                    callback.call(value.doubleValue());
+                }
+            }).register(registry);
+            registeredGauges.put(name, newGauge);
         }
 
         @Override
-        public void registerGaugeSet(final String name, final GaugeSet 
gaugeSet) {
-            Objects.requireNonNull(name, "Cannot register a GaugeSet with null 
name");
-            Objects.requireNonNull(gaugeSet, "Cannot register a null GaugeSet 
for " + name);
-
-            gaugeSets.compute(name, (id, prev) ->
-                new PrometheusLabelledGaugeWrapper(name, gaugeSet, prev != 
null ? prev.inner : null));
+        public void unregisterGauge(String name) {
+            GaugeWithCallback gauge = registeredGauges.remove(name);
+            if (gauge != null) {
+                registry.unregister(gauge);
+            }
         }
 
         @Override
         public void unregisterGaugeSet(final String name) {
             Objects.requireNonNull(name, "Cannot unregister GaugeSet with null 
name");
+            unregisterGauge(name);
+        }
+
+        private io.prometheus.metrics.core.metrics.Summary 
createPrometheusSummary(String name, DetailLevel detailLevel,
+                String... labelNames) {
+            io.prometheus.metrics.core.metrics.Summary.Builder builder = 
io.prometheus.metrics.core.metrics.Summary
+                    .builder().name(name).help(name + " 
summary").quantile(0.5, 0.05); // Median
 
-            final PrometheusLabelledGaugeWrapper existing = 
gaugeSets.remove(name);
-            if (existing != null) {
-                existing.unregister();
+            if (detailLevel == DetailLevel.ADVANCED) {
+                builder.quantile(0.95, 0.05)   // 95th percentile
+                        .quantile(0.99, 0.05); // 99th percentile
             }
+
+            if (labelNames.length > 0) {
+                builder.labelNames(labelNames);
+            }
+            return builder.register(registry);
         }
 
         @Override
         public Summary getSummary(String name, DetailLevel detailLevel) {
-            if (detailLevel == DetailLevel.BASIC) {
-                return basicSummaries.computeIfAbsent(name, (n) -> {
-                    if (summaries.containsKey(n)) {
-                        throw new IllegalArgumentException("Already registered 
a non basic summary as " + n);
-                    }
-                    return new PrometheusSummary(name, detailLevel);
-                });
-            } else {
-                return summaries.computeIfAbsent(name, (n) -> {
-                    if (basicSummaries.containsKey(n)) {
-                        throw new IllegalArgumentException("Already registered 
a basic summary as " + n);
-                    }
-                    return new PrometheusSummary(name, detailLevel);
-                });
-            }
+            ConcurrentMap<String, PrometheusSummaryWrapper> map = detailLevel 
== DetailLevel.BASIC ? basicSummaries
+                    : advancedSummaries;
+            return map.computeIfAbsent(name, key -> {
+                if ((detailLevel == DetailLevel.BASIC && 
advancedSummaries.containsKey(key))
+                        || (detailLevel == DetailLevel.ADVANCED && 
basicSummaries.containsKey(key))) {
+                    throw new IllegalArgumentException(
+                            "Already registered a summary as " + key + " with 
a different detail level");
+                }
+                io.prometheus.metrics.core.metrics.Summary prometheusSummary = 
createPrometheusSummary(key,
+                        detailLevel);
+                return new PrometheusSummaryWrapper(prometheusSummary);
+            });
         }
 
         @Override
         public SummarySet getSummarySet(String name, DetailLevel detailLevel) {
-            if (detailLevel == DetailLevel.BASIC) {
-                return basicSummarySets.computeIfAbsent(name, (n) -> {
-                    if (summarySets.containsKey(n)) {
-                        throw new IllegalArgumentException("Already registered 
a non basic summary set as " + n);
-                    }
-                    return new PrometheusLabelledSummary(name, detailLevel);
-                });
-            } else {
-                return summarySets.computeIfAbsent(name, (n) -> {
-                    if (basicSummarySets.containsKey(n)) {
-                        throw new IllegalArgumentException("Already registered 
a basic summary set as " + n);
-                    }
-                    return new PrometheusLabelledSummary(name, detailLevel);
-                });
-            }
+            ConcurrentMap<String, PrometheusLabelledSummaryWrapper> map = 
detailLevel == DetailLevel.BASIC
+                    ? basicSummarySets : advancedSummarySets;
+            return map.computeIfAbsent(name, key -> {
+                if ((detailLevel == DetailLevel.BASIC && 
advancedSummarySets.containsKey(key))
+                        || (detailLevel == DetailLevel.ADVANCED && 
basicSummarySets.containsKey(key))) {
+                    throw new IllegalArgumentException(
+                            "Already registered a summary set as " + key + " 
with a different detail level");
+                }
+                io.prometheus.metrics.core.metrics.Summary prometheusSummary = 
createPrometheusSummary(key, detailLevel,
+                        LABEL);
+                return new PrometheusLabelledSummaryWrapper(prometheusSummary);
+            });
         }
-
     }
 
-    private class PrometheusCounter implements Counter {
+    // --- Wrapper classes to adapt Prometheus metrics to ZooKeeper's metric 
interfaces ---
 
-        private final io.prometheus.client.Counter inner;
-        private final String name;
+    private static class PrometheusCounterWrapper implements Counter {
+        private final io.prometheus.metrics.core.metrics.Counter 
prometheusCounter;
 
-        public PrometheusCounter(String name) {
-            this.name = name;
-            this.inner = io.prometheus.client.Counter
-                    .build(name, name)
-                    .register(collectorRegistry);
+        public 
PrometheusCounterWrapper(io.prometheus.metrics.core.metrics.Counter 
prometheusCounter) {
+            this.prometheusCounter = prometheusCounter;
         }
 
         @Override
         public void add(long delta) {
             try {
-                inner.inc(delta);
-            } catch (IllegalArgumentException err) {
-                LOG.error("invalid delta {} for metric {}", delta, name, err);
+                this.prometheusCounter.inc(delta);
+            } catch (final IllegalArgumentException e) {
+                LOG.error("invalid delta {} for metric {}", delta, 
prometheusCounter.getPrometheusName(), e);
             }
         }
 
         @Override
         public long get() {
-            // this method is used only for tests
-            // Prometheus returns a "double"
-            // it is safe to fine to a long
-            // we are never setting non-integer values
-            return (long) inner.get();
+            return (long) this.prometheusCounter.get();
         }
-
     }
 
-    private class PrometheusLabelledCounter implements CounterSet {
-        private final String name;
-        private final io.prometheus.client.Counter inner;
+    private static class PrometheusLabelledCounterWrapper implements 
CounterSet {
+        private final io.prometheus.metrics.core.metrics.Counter 
prometheusCounter;
 
-        public PrometheusLabelledCounter(final String name) {
-            this.name = name;
-            this.inner = io.prometheus.client.Counter
-                    .build(name, name)
-                    .labelNames(LABELS)
-                    .register(collectorRegistry);
+        public 
PrometheusLabelledCounterWrapper(io.prometheus.metrics.core.metrics.Counter 
prometheusCounter) {
+            this.prometheusCounter = prometheusCounter;
         }
 
         @Override
-        public void add(final String key, final long delta) {
+        public void add(String key, long delta) {
             try {
-                inner.labels(key).inc(delta);
+                this.prometheusCounter.labelValues(key).inc(delta);
             } catch (final IllegalArgumentException e) {
-                LOG.error("invalid delta {} for metric {} with key {}", delta, 
name, key, e);
-            }
-        }
-    }
-
-    private class PrometheusGaugeWrapper {
-
-        private final io.prometheus.client.Gauge inner;
-        private final Gauge gauge;
-        private final String name;
-
-        public PrometheusGaugeWrapper(String name, Gauge gauge, 
io.prometheus.client.Gauge prev) {
-            this.name = name;
-            this.gauge = gauge;
-            this.inner = prev != null ? prev
-                    : io.prometheus.client.Gauge
-                    .build(name, name)
-                    .register(collectorRegistry);
-        }
-
-        /**
-         * Call the callback and update Prometheus Gauge. This method is called
-         * when the server is polling for a value.
-         */
-        private void sample() {
-            Number value = gauge.get();
-            this.inner.set(value != null ? value.doubleValue() : 0);
-        }
-
-        private void unregister() {
-            collectorRegistry.unregister(inner);
-        }
-    }
-
-    /**
-     * Prometheus implementation of GaugeSet interface. It wraps the GaugeSet 
object and
-     * uses the callback API to update the Prometheus Gauge.
-     */
-    private class PrometheusLabelledGaugeWrapper {
-        private final GaugeSet gaugeSet;
-        private final io.prometheus.client.Gauge inner;
-
-        private PrometheusLabelledGaugeWrapper(final String name,
-                                               final GaugeSet gaugeSet,
-                                               final 
io.prometheus.client.Gauge prev) {
-            this.gaugeSet = gaugeSet;
-            this.inner = prev != null ? prev :
-                    io.prometheus.client.Gauge
-                            .build(name, name)
-                            .labelNames(LABELS)
-                            .register(collectorRegistry);
-        }
-
-        /**
-         * Call the callback provided by the GaugeSet and update Prometheus 
Gauge.
-         * This method is called when the server is polling for a value.
-         */
-        private void sample() {
-            gaugeSet.values().forEach((key, value) ->
-                this.inner.labels(key).set(value != null ? value.doubleValue() 
: 0));
-        }
-
-        private void unregister() {
-            collectorRegistry.unregister(inner);
-        }
-    }
-
-    private class PrometheusSummary implements Summary {
-
-        private final io.prometheus.client.Summary inner;
-        private final String name;
-
-        public PrometheusSummary(String name, MetricsContext.DetailLevel 
level) {
-            this.name = name;
-            if (level == MetricsContext.DetailLevel.ADVANCED) {
-                this.inner = io.prometheus.client.Summary
-                        .build(name, name)
-                        .quantile(0.5, 0.05) // Add 50th percentile (= median) 
with 5% tolerated error
-                        .quantile(0.9, 0.01) // Add 90th percentile with 1% 
tolerated error
-                        .quantile(0.99, 0.001) // Add 99th percentile with 
0.1% tolerated error
-                        .register(collectorRegistry);
-            } else {
-                this.inner = io.prometheus.client.Summary
-                        .build(name, name)
-                        .quantile(0.5, 0.05) // Add 50th percentile (= median) 
with 5% tolerated error
-                        .register(collectorRegistry);
+                LOG.error("invalid delta {} for metric {} with key {}", delta, 
prometheusCounter.getPrometheusName(),
+                        key, e);
             }
         }
 
         @Override
-        public void add(long delta) {
-            reportMetrics(() -> observe(delta));
-        }
-
-        private void observe(final long delta) {
-            try {
-                inner.observe(delta);
-            } catch (final IllegalArgumentException err) {
-                LOG.error("invalid delta {} for metric {}", delta, name, err);
-            }
+        public void inc(String key) {
+            add(key, 1);
         }
     }
 
-    private class PrometheusLabelledSummary implements SummarySet {
-
-        private final io.prometheus.client.Summary inner;
-        private final String name;
-
-        public PrometheusLabelledSummary(String name, 
MetricsContext.DetailLevel level) {
-            this.name = name;
-            if (level == MetricsContext.DetailLevel.ADVANCED) {
-                this.inner = io.prometheus.client.Summary
-                        .build(name, name)
-                        .labelNames(LABELS)
-                        .quantile(0.5, 0.05) // Add 50th percentile (= median) 
with 5% tolerated error
-                        .quantile(0.9, 0.01) // Add 90th percentile with 1% 
tolerated error
-                        .quantile(0.99, 0.001) // Add 99th percentile with 
0.1% tolerated error
-                        .register(collectorRegistry);
-            } else {
-                this.inner = io.prometheus.client.Summary
-                        .build(name, name)
-                        .labelNames(LABELS)
-                        .quantile(0.5, 0.05) // Add 50th percentile (= median) 
with 5% tolerated error
-                        .register(collectorRegistry);
-            }
-        }
-
-        @Override
-        public void add(String key, long value) {
-            reportMetrics(() -> observe(key, value));
-        }
+    private static class PrometheusSummaryWrapper implements Summary {
+        private final io.prometheus.metrics.core.metrics.Summary 
prometheusSummary;
 
-        private void observe(final String key, final long value) {
-            try {
-                inner.labels(key).observe(value);
-            } catch (final IllegalArgumentException err) {
-                LOG.error("invalid value {} for metric {} with key {}", value, 
name, key, err);
-            }
+        public 
PrometheusSummaryWrapper(io.prometheus.metrics.core.metrics.Summary 
prometheusSummary) {
+            this.prometheusSummary = prometheusSummary;
         }
 
-    }
-
-    class MetricsServletImpl extends MetricsServlet {
-
         @Override
-        protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
-            // little trick: update the Gauges before serving data
-            // from Prometheus CollectorRegistry
-            sampleGauges();
-            // serve data using Prometheus built in client.
-            super.doGet(req, resp);
+        public void add(long value) {
+            this.prometheusSummary.observe(value);
         }
     }
 
-    private Optional<ExecutorService> createExecutor() {
-        if (numWorkerThreads < 1) {
-            LOG.info("Executor service was not created as numWorkerThreads {} 
is less than 1", numWorkerThreads);
-            return Optional.empty();
-        }
-
-        final BlockingQueue<Runnable> queue = new 
LinkedBlockingQueue<>(maxQueueSize);
-        final ThreadPoolExecutor executor = new 
ThreadPoolExecutor(numWorkerThreads,
-                numWorkerThreads,
-                0L,
-                TimeUnit.MILLISECONDS,
-                queue,
-                new PrometheusWorkerThreadFactory(),
-                new PrometheusRejectedExecutionHandler());
-        LOG.info("Executor service was created with numWorkerThreads {} and 
maxQueueSize {}",
-                numWorkerThreads,
-                maxQueueSize);
-        return Optional.of(executor);
-    }
+    private static class PrometheusLabelledSummaryWrapper implements 
SummarySet {
+        private final io.prometheus.metrics.core.metrics.Summary 
prometheusSummary;
 
-    private void shutdownExecutor() {
-        if (executorOptional.isPresent()) {
-            LOG.info("Shutdown executor service with timeout {}", 
workerShutdownTimeoutMs);
-            final ExecutorService executor = executorOptional.get();
-            executor.shutdown();
-            try {
-                if (!executor.awaitTermination(workerShutdownTimeoutMs, 
TimeUnit.MILLISECONDS)) {
-                    LOG.error("Not all the Prometheus worker threads 
terminated properly after {} timeout",
-                            workerShutdownTimeoutMs);
-                    executor.shutdownNow();
-                }
-            } catch (final Exception e) {
-                LOG.error("Error occurred while terminating Prometheus worker 
threads", e);
-                executor.shutdownNow();
-            }
+        public 
PrometheusLabelledSummaryWrapper(io.prometheus.metrics.core.metrics.Summary 
prometheusSummary) {
+            this.prometheusSummary = prometheusSummary;
         }
-    }
-
-    private static class PrometheusWorkerThreadFactory implements 
ThreadFactory {
-        private static final AtomicInteger workerCounter = new 
AtomicInteger(1);
 
         @Override
-        public Thread newThread(final Runnable runnable) {
-            final String threadName = "PrometheusMetricsProviderWorker-" + 
workerCounter.getAndIncrement();
-            final Thread thread = new Thread(runnable, threadName);
-            thread.setDaemon(true);
-            return thread;
-        }
-    }
-
-    private class PrometheusRejectedExecutionHandler implements 
RejectedExecutionHandler {
-        private final String queueExceededMessage = "Prometheus metrics queue 
size exceeded the max " + maxQueueSize;
-
-        @Override
-        public void rejectedExecution(final Runnable r, final 
ThreadPoolExecutor e) {
-            rateLogger.rateLimitLog(queueExceededMessage);
-        }
-    }
-
-    private void reportMetrics(final Runnable task) {
-        if (executorOptional.isPresent()) {
-            executorOptional.get().submit(task);
-        } else {
-            task.run();
+        public void add(String key, long value) {
+            this.prometheusSummary.labelValues(key).observe(value);
         }
     }
 }
diff --git 
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/main/java/org/apache/zookeeper/metrics/prometheus/PrometheusRegistryDumper.java
 
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/main/java/org/apache/zookeeper/metrics/prometheus/PrometheusRegistryDumper.java
new file mode 100644
index 000000000..146f76159
--- /dev/null
+++ 
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/main/java/org/apache/zookeeper/metrics/prometheus/PrometheusRegistryDumper.java
@@ -0,0 +1,160 @@
+/*
+ * 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.zookeeper.metrics.prometheus;
+
+import io.prometheus.metrics.model.registry.PrometheusRegistry;
+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.Quantile;
+import io.prometheus.metrics.model.snapshots.SummarySnapshot;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Handles the logic of converting a PrometheusRegistry scrape result into a 
sequence of key-value pairs.
+ */
+public class PrometheusRegistryDumper {
+
+    private final PrometheusRegistry registry;
+
+    public PrometheusRegistryDumper(PrometheusRegistry registry) {
+        this.registry = registry;
+    }
+
+    /**
+     * Dumps all metrics from the PrometheusRegistry into a key-value map.
+     *
+     * @return a map containing all the metrics
+     */
+    public Map<String, Object> dump() {
+        Map<String, Object> allMetrics = new LinkedHashMap<>();
+        MetricSnapshots metricSnapshots = registry.scrape();
+        for (MetricSnapshot snapshot : metricSnapshots) {
+            Map<String, Object> convertedMetrics = null;
+            if (snapshot instanceof CounterSnapshot) {
+                convertedMetrics = convert((CounterSnapshot) snapshot);
+            } else if (snapshot instanceof GaugeSnapshot) {
+                convertedMetrics = convert((GaugeSnapshot) snapshot);
+            } else if (snapshot instanceof SummarySnapshot) {
+                convertedMetrics = convert((SummarySnapshot) snapshot);
+            }
+
+            if (convertedMetrics != null) {
+                allMetrics.putAll(convertedMetrics);
+            }
+        }
+        return allMetrics;
+    }
+
+    private Map<String, Object> convert(CounterSnapshot snapshot) {
+        Map<String, Object> result = new LinkedHashMap<>();
+        String metricName = snapshot.getMetadata().getName();
+        for (CounterSnapshot.CounterDataPointSnapshot dataPoint : 
snapshot.getDataPoints()) {
+            result.put(buildKeyForDump(metricName, dataPoint.getLabels()), 
dataPoint.getValue());
+        }
+        return result;
+    }
+
+    private Map<String, Object> convert(GaugeSnapshot snapshot) {
+        Map<String, Object> result = new LinkedHashMap<>();
+        String metricName = snapshot.getMetadata().getName();
+        for (GaugeSnapshot.GaugeDataPointSnapshot dataPoint : 
snapshot.getDataPoints()) {
+            result.put(buildKeyForDump(metricName, dataPoint.getLabels()), 
dataPoint.getValue());
+        }
+        return result;
+    }
+
+    private Map<String, Object> convert(SummarySnapshot snapshot) {
+        Map<String, Object> result = new LinkedHashMap<>();
+        String metricName = snapshot.getMetadata().getName();
+        for (SummarySnapshot.SummaryDataPointSnapshot dataPoint : 
snapshot.getDataPoints()) {
+            double count = dataPoint.getCount();
+            double sum = dataPoint.getSum();
+            double avg = (count == 0) ? 0 : sum / count;
+
+            // Add metrics in the requested order with prefixes
+            result.put(buildKeyForDump(metricName + "_avg", 
dataPoint.getLabels()), avg);
+            // Note: Prometheus Summary does not provide min/max, so they are 
omitted.
+            result.put(buildKeyForDump(metricName + "_count", 
dataPoint.getLabels()), count);
+            result.put(buildKeyForDump(metricName + "_sum", 
dataPoint.getLabels()), sum);
+
+            // A summary is considered "advanced" if it has more than one 
quantile configured.
+            boolean isAdvanced = dataPoint.getQuantiles().size() > 1;
+
+            if (isAdvanced) {
+                List<Quantile> quantiles = new ArrayList<>();
+                dataPoint.getQuantiles().forEach(quantiles::add);
+                
quantiles.sort(Comparator.comparingDouble(Quantile::getQuantile));
+
+                for (Quantile quantile : quantiles) {
+                    String quantileValue = 
String.valueOf(quantile.getQuantile());
+                    switch (quantileValue) {
+                    default:
+                        break;
+                    case "0.5":
+                        result.put(buildKeyForDump(metricName, 
dataPoint.getLabels().add("quantile", quantileValue)),
+                                quantile.getValue());
+                        break;
+                    case "0.95":
+                        result.put(buildKeyForDump(metricName, 
dataPoint.getLabels().add("quantile", quantileValue)),
+                                quantile.getValue());
+                        break;
+                    case "0.99":
+                        result.put(buildKeyForDump(metricName, 
dataPoint.getLabels().add("quantile", quantileValue)),
+                                quantile.getValue());
+                        break;
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Builds a string key for a given metric and its labels, in a format 
suitable for the dump output.
+     *
+     * @param metricName
+     *            the name of the metric
+     * @param labels
+     *            the labels associated with the metric
+     *
+     * @return a formatted string key
+     */
+    private String buildKeyForDump(String metricName, Labels labels) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(metricName);
+        if (labels.size() > 0) {
+            sb.append("{");
+            for (int i = 0; i < labels.size(); i++) {
+                if (i > 0) {
+                    sb.append(",");
+                }
+                
sb.append(labels.getName(i)).append("=\"").append(labels.getValue(i)).append("\"");
+            }
+            sb.append("}");
+        }
+        return sb.toString();
+    }
+}
diff --git 
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/ExportJvmInfoTest.java
 
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/ExportJvmInfoTest.java
index 5d51f9c1e..a923348f7 100644
--- 
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/ExportJvmInfoTest.java
+++ 
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/ExportJvmInfoTest.java
@@ -23,8 +23,7 @@
 import org.junit.jupiter.api.Test;
 
 /**
- * Tests about Prometheus Metrics Provider. Please note that we are not testing
- * Prometheus but our integration.
+ * Tests about Prometheus Metrics Provider. Please note that we are not 
testing Prometheus but our integration.
  */
 public class ExportJvmInfoTest extends PrometheusMetricsTestBase {
 
@@ -46,7 +45,7 @@ private void runTest(boolean exportJvmInfo) throws Exception {
             configuration.setProperty("exportJvmInfo", exportJvmInfo + "");
             provider.configure(configuration);
             provider.start();
-            boolean[] found = {false};
+            boolean[] found = { false };
             provider.dump((k, v) -> {
                 found[0] = found[0] || k.contains("heap");
             });
diff --git 
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/PrometheusHttpsMetricsProviderTest.java
 
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/PrometheusHttpsMetricsProviderTest.java
index 26cdb4c23..c09193470 100644
--- 
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/PrometheusHttpsMetricsProviderTest.java
+++ 
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/PrometheusHttpsMetricsProviderTest.java
@@ -7,7 +7,7 @@
  * "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
+ * 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,
@@ -20,6 +20,7 @@
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import java.io.BufferedReader;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -37,8 +38,7 @@
 import org.junit.jupiter.api.Test;
 
 /**
- * Tests about Prometheus Metrics Provider. Please note that we are not testing
- * Prometheus but only our integration.
+ * Tests about Prometheus Metrics Provider. Please note that we are not 
testing Prometheus but only our integration.
  */
 public class PrometheusHttpsMetricsProviderTest extends 
PrometheusMetricsTestBase {
 
@@ -75,7 +75,14 @@ void testHttpResponse() throws Exception {
         configuration.setProperty("httpPort", String.valueOf(httpPort));
         initializeProviderWithCustomConfig(configuration);
         simulateMetricIncrement();
-        validateMetricResponse(callHttpServlet("http://"; + httpHost + ":" + 
httpPort + "/metrics"));
+        String metricsUrl = String.format("http://%s:%d/metrics";, httpHost, 
httpPort);
+
+        HttpURLConnection conn = callAndGetResponse(metricsUrl, "GET");
+        validateMetricResponse(readResponse(conn));
+
+        conn = callAndGetResponse(metricsUrl, "TRACE");
+        assertEquals(HttpURLConnection.HTTP_BAD_METHOD, 
conn.getResponseCode());
+        conn.disconnect();
     }
 
     @Test
@@ -84,7 +91,14 @@ void testHttpsResponse() throws Exception {
         configuration.setProperty("httpsPort", String.valueOf(httpsPort));
         initializeProviderWithCustomConfig(configuration);
         simulateMetricIncrement();
-        validateMetricResponse(callHttpsServlet("https://"; + httpHost + ":" + 
httpsPort + "/metrics"));
+        String metricsUrl = String.format("https://%s:%d/metrics";, httpHost, 
httpsPort);
+
+        HttpURLConnection conn = callAndGetResponse(metricsUrl, "GET");
+        validateMetricResponse(readResponse(conn));
+
+        conn = callAndGetResponse(metricsUrl, "TRACE");
+        assertEquals(HttpURLConnection.HTTP_BAD_METHOD, 
conn.getResponseCode());
+        conn.disconnect();
     }
 
     @Test
@@ -94,44 +108,48 @@ void testHttpAndHttpsResponse() throws Exception {
         configuration.setProperty("httpPort", String.valueOf(httpPort));
         initializeProviderWithCustomConfig(configuration);
         simulateMetricIncrement();
-        validateMetricResponse(callHttpServlet("http://"; + httpHost + ":" + 
httpPort + "/metrics"));
-        validateMetricResponse(callHttpsServlet("https://"; + httpHost + ":" + 
httpsPort + "/metrics"));
-    }
 
-    private String callHttpsServlet(String urlString) throws Exception {
-        // Load and configure the SSL context from the keystore and truststore
-        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
-        try (FileInputStream keystoreStream = new FileInputStream(testDataPath 
+ "/ssl/client_keystore.jks")) {
-            keyStore.load(keystoreStream, "testpass".toCharArray());
-        }
+        HttpURLConnection conn = 
callAndGetResponse(String.format("https://%s:%d/metrics";, httpHost, httpsPort), 
"GET");
+        validateMetricResponse(readResponse(conn));
+    }
 
-        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
-        try (FileInputStream trustStoreStream = new 
FileInputStream(testDataPath + "/ssl/client_truststore.jks")) {
-            trustStore.load(trustStoreStream, "testpass".toCharArray());
-        }
+    private HttpURLConnection callAndGetResponse(String urlString, String 
method) throws Exception {
+        URL url = new URL(urlString);
+        HttpURLConnection conn;
 
-        SSLContext sslContext = SSLContext.getInstance("TLS");
-        KeyManagerFactory keyManagerFactory = 
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
-        keyManagerFactory.init(keyStore, "testpass".toCharArray());
-        TrustManagerFactory trustManagerFactory = TrustManagerFactory
-                .getInstance(TrustManagerFactory.getDefaultAlgorithm());
-        trustManagerFactory.init(trustStore);
-        sslContext.init(keyManagerFactory.getKeyManagers(), 
trustManagerFactory.getTrustManagers(),
-                new java.security.SecureRandom());
+        if (url.getProtocol().equalsIgnoreCase("https")) {
+            // Re-use the existing SSL setup logic.
+            KeyStore keyStore = 
KeyStore.getInstance(KeyStore.getDefaultType());
+            try (FileInputStream keystoreStream = new 
FileInputStream(testDataPath + "/ssl/client_keystore.jks")) {
+                keyStore.load(keystoreStream, "testpass".toCharArray());
+            }
 
-        
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
-        URL url = new URL(urlString);
-        HttpsURLConnection connection = (HttpsURLConnection) 
url.openConnection();
-        connection.setRequestMethod("GET");
+            KeyStore trustStore = 
KeyStore.getInstance(KeyStore.getDefaultType());
+            try (FileInputStream trustStoreStream = new 
FileInputStream(testDataPath + "/ssl/client_truststore.jks")) {
+                trustStore.load(trustStoreStream, "testpass".toCharArray());
+            }
 
-        return readResponse(connection);
-    }
+            SSLContext sslContext = SSLContext.getInstance("TLS");
+            KeyManagerFactory keyManagerFactory = KeyManagerFactory
+                    .getInstance(KeyManagerFactory.getDefaultAlgorithm());
+            keyManagerFactory.init(keyStore, "testpass".toCharArray());
+            TrustManagerFactory trustManagerFactory = TrustManagerFactory
+                    .getInstance(TrustManagerFactory.getDefaultAlgorithm());
+            trustManagerFactory.init(trustStore);
+            sslContext.init(keyManagerFactory.getKeyManagers(), 
trustManagerFactory.getTrustManagers(),
+                    new java.security.SecureRandom());
+
+            HttpsURLConnection httpsConn = (HttpsURLConnection) 
url.openConnection();
+            httpsConn.setSSLSocketFactory(sslContext.getSocketFactory());
+            httpsConn.setHostnameVerifier((hostname, session) -> true);
+            conn = httpsConn;
+        } else {
+            conn = (HttpURLConnection) url.openConnection();
+        }
 
-    private String callHttpServlet(String urlString) throws IOException {
-        URL url = new URL(urlString);
-        HttpURLConnection connection = (HttpURLConnection) 
url.openConnection();
-        connection.setRequestMethod("GET");
-        return readResponse(connection);
+        conn.setRequestMethod(method);
+        conn.connect();
+        return conn;
     }
 
     private String readResponse(HttpURLConnection connection) throws 
IOException {
@@ -155,7 +173,7 @@ public void simulateMetricIncrement() {
     }
 
     private void validateMetricResponse(String response) throws IOException {
-        assertThat(response, containsString("# TYPE cc counter"));
-        assertThat(response, containsString("cc 10.0"));
+        assertThat(response, containsString("# TYPE cc_total counter"));
+        assertThat(response, containsString("cc_total 10.0"));
     }
-}
\ No newline at end of file
+}
diff --git 
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProviderConfigTest.java
 
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProviderConfigTest.java
index aa9cd2c71..c151854b0 100644
--- 
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProviderConfigTest.java
+++ 
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProviderConfigTest.java
@@ -88,15 +88,14 @@ public void testValidHttpsAndHttpConfig() throws 
MetricsProviderLifeCycleExcepti
         provider.start();
     }
 
-
     @Test
     public void testInvalidSslConfig() throws 
MetricsProviderLifeCycleException {
         assertThrows(MetricsProviderLifeCycleException.class, () -> {
             PrometheusMetricsProvider provider = new 
PrometheusMetricsProvider();
             Properties configuration = new Properties();
             String testDataPath = System.getProperty("test.data.dir", 
"src/test/resources/data");
-            configuration.setProperty("httpsPort", "0");
-            //keystore missing
+            configuration.setProperty("httpsPort", "50514");
+            // keystore missing
             configuration.setProperty("ssl.keyStore.password", "testpass");
             configuration.setProperty("ssl.trustStore.location", testDataPath 
+ "/ssl/server_truststore.jks");
             configuration.setProperty("ssl.trustStore.password", "testpass");
diff --git 
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProviderTest.java
 
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProviderTest.java
index 59115e31d..2d36b0017 100644
--- 
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProviderTest.java
+++ 
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProviderTest.java
@@ -26,13 +26,10 @@
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.lang.reflect.Field;
-import java.net.HttpURLConnection;
-import java.net.URL;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -42,6 +39,9 @@
 import java.util.Properties;
 import java.util.concurrent.atomic.AtomicInteger;
 import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
+import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import org.apache.zookeeper.metrics.Counter;
@@ -52,16 +52,13 @@
 import org.apache.zookeeper.metrics.Summary;
 import org.apache.zookeeper.metrics.SummarySet;
 import org.apache.zookeeper.server.util.QuotaMetricsUtils;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.ServerConnector;
 import org.hamcrest.CoreMatchers;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 /**
- * Tests about Prometheus Metrics Provider. Please note that we are not testing
- * Prometheus but only our integration.
+ * Tests about Prometheus Metrics Provider. Please note that we are not 
testing Prometheus but only our integration.
  */
 public class PrometheusMetricsProviderTest extends PrometheusMetricsTestBase {
 
@@ -72,7 +69,7 @@ public class PrometheusMetricsProviderTest extends 
PrometheusMetricsTestBase {
     public void setup() throws Exception {
         provider = new PrometheusMetricsProvider();
         Properties configuration = new Properties();
-        configuration.setProperty("numWorkerThreads", "0"); // sync behavior 
for test
+        configuration.setProperty("numWorkerThreads", "1"); // sync behavior 
for test
         configuration.setProperty("httpHost", "127.0.0.1"); // local host for 
test
         configuration.setProperty("httpPort", "0"); // ephemeral port
         configuration.setProperty("exportJvmInfo", "false");
@@ -91,13 +88,12 @@ public void tearDown() {
     public void testCounters() throws Exception {
         Counter counter = provider.getRootContext().getCounter("cc");
         counter.add(10);
-        int[] count = {0};
+        int[] count = { 0 };
         provider.dump((k, v) -> {
             assertEquals("cc", k);
             assertEquals(10, ((Number) v).intValue());
             count[0]++;
-        }
-        );
+        });
         assertEquals(1, count[0]);
         count[0] = 0;
 
@@ -108,16 +104,15 @@ public void testCounters() throws Exception {
             assertEquals("cc", k);
             assertEquals(10, ((Number) v).intValue());
             count[0]++;
-        }
-        );
+        });
         assertEquals(1, count[0]);
 
         // we always must get the same object
         assertSame(counter, provider.getRootContext().getCounter("cc"));
 
         String res = callServlet();
-        assertThat(res, CoreMatchers.containsString("# TYPE cc counter"));
-        assertThat(res, CoreMatchers.containsString("cc 10.0"));
+        assertThat(res, CoreMatchers.containsString("# TYPE cc_total 
counter"));
+        assertThat(res, CoreMatchers.containsString("cc_total 10.0"));
     }
 
     @Test
@@ -125,7 +120,7 @@ public void testCounterSet_single() throws Exception {
         // create and register a CounterSet
         final String name = 
QuotaMetricsUtils.QUOTA_EXCEEDED_ERROR_PER_NAMESPACE;
         final CounterSet counterSet = 
provider.getRootContext().getCounterSet(name);
-        final String[] keys = {"ns1", "ns2"};
+        final String[] keys = { "ns1", "ns2" };
         final int count = 3;
 
         // update the CounterSet multiple times
@@ -142,10 +137,10 @@ public void testCounterSet_single() throws Exception {
         validateWithDump(expectedMetricsMap);
 
         // validate with servlet call
-        final List<String> expectedNames = 
Collections.singletonList(String.format("# TYPE %s count", name));
+        final List<String> expectedNames = 
Collections.singletonList(String.format("# TYPE %s_total counter", name));
         final List<String> expectedMetrics = new ArrayList<>();
         for (final String key : keys) {
-            expectedMetrics.add(String.format("%s{key=\"%s\",} %s", name, key, 
count * 3.0));
+            expectedMetrics.add(String.format("%s_total{key=\"%s\"} %s", name, 
key, count * 3.0));
         }
         validateWithServletCall(expectedNames, expectedMetrics);
 
@@ -157,9 +152,9 @@ public void testCounterSet_single() throws Exception {
     public void testCounterSet_multiple() throws Exception {
         final String name = 
QuotaMetricsUtils.QUOTA_EXCEEDED_ERROR_PER_NAMESPACE;
 
-        final String[] names = new String[]{name + "_1", name + "_2"};
-        final String[] keys = new String[]{"ns21", "ns22"};
-        final int[] counts = new int[] {3, 5};
+        final String[] names = new String[] { name + "_1", name + "_2" };
+        final String[] keys = new String[] { "ns21", "ns22" };
+        final int[] counts = new int[] { 3, 5 };
 
         final int length = names.length;
         final CounterSet[] counterSets = new CounterSet[length];
@@ -187,16 +182,15 @@ public void testCounterSet_multiple() throws Exception {
         final List<String> expectedNames = new ArrayList<>();
         final List<String> expectedMetrics = new ArrayList<>();
         for (int i = 0; i < length; i++) {
-            expectedNames.add(String.format("# TYPE %s count", names[i]));
-            expectedMetrics.add(String.format("%s{key=\"%s\",} %s", names[i], 
keys[i], counts[i]  * 1.0));
+            expectedNames.add(String.format("# TYPE %s_total counter", 
names[i]));
+            expectedMetrics.add(String.format("%s_total{key=\"%s\"} %s", 
names[i], keys[i], counts[i] * 1.0));
         }
         validateWithServletCall(expectedNames, expectedMetrics);
     }
 
     @Test
     public void testCounterSet_registerWithNullName() {
-        assertThrows(NullPointerException.class,
-                () -> provider.getRootContext().getCounterSet(null));
+        assertThrows(NullPointerException.class, () -> 
provider.getRootContext().getCounterSet(null));
     }
 
     @Test
@@ -222,8 +216,8 @@ public void testCounterSet_nullKey() {
 
     @Test
     public void testGauge() throws Exception {
-        int[] values = {78, -89};
-        int[] callCounts = {0, 0};
+        int[] values = { 78, -89 };
+        int[] callCounts = { 0, 0 };
         Gauge gauge0 = () -> {
             callCounts[0]++;
             return values[0];
@@ -234,13 +228,12 @@ public void testGauge() throws Exception {
         };
         provider.getRootContext().registerGauge("gg", gauge0);
 
-        int[] count = {0};
+        int[] count = { 0 };
         provider.dump((k, v) -> {
             assertEquals("gg", k);
             assertEquals(values[0], ((Number) v).intValue());
             count[0]++;
-        }
-        );
+        });
         assertEquals(1, callCounts[0]);
         assertEquals(0, callCounts[1]);
         assertEquals(1, count[0]);
@@ -252,8 +245,7 @@ public void testGauge() throws Exception {
         provider.getRootContext().unregisterGauge("gg");
         provider.dump((k, v) -> {
             count[0]++;
-        }
-        );
+        });
         assertEquals(2, callCounts[0]);
         assertEquals(0, callCounts[1]);
         assertEquals(0, count[0]);
@@ -266,8 +258,7 @@ public void testGauge() throws Exception {
             assertEquals("gg", k);
             assertEquals(values[1], ((Number) v).intValue());
             count[0]++;
-        }
-        );
+        });
         assertEquals(2, callCounts[0]);
         assertEquals(1, callCounts[1]);
         assertEquals(1, count[0]);
@@ -285,8 +276,7 @@ public void testGauge() throws Exception {
 
         provider.dump((k, v) -> {
             count[0]++;
-        }
-        );
+        });
         assertEquals(1, count[0]);
         assertEquals(3, callCounts[0]);
         assertEquals(2, callCounts[1]);
@@ -294,41 +284,37 @@ public void testGauge() throws Exception {
 
     @Test
     public void testBasicSummary() throws Exception {
-        Summary summary = provider.getRootContext()
-                .getSummary("cc", MetricsContext.DetailLevel.BASIC);
+        Summary summary = provider.getRootContext().getSummary("cc", 
MetricsContext.DetailLevel.BASIC);
         summary.add(10);
         summary.add(10);
-        int[] count = {0};
+        int[] count = { 0 };
         provider.dump((k, v) -> {
             count[0]++;
             int value = ((Number) v).intValue();
 
             switch (k) {
-                case "cc{quantile=\"0.5\"}":
-                    assertEquals(10, value);
-                    break;
-                case "cc_count":
-                    assertEquals(2, value);
-                    break;
-                case "cc_sum":
-                    assertEquals(20, value);
-                    break;
-                default:
-                    fail("unespected key " + k);
-                    break;
+            case "cc_avg":
+                assertEquals(10, value);
+                break;
+            case "cc_count":
+                assertEquals(2, value);
+                break;
+            case "cc_sum":
+                assertEquals(20, value);
+                break;
+            default:
+                fail("unespected key " + k);
+                break;
             }
-        }
-        );
+        });
         assertEquals(3, count[0]);
         count[0] = 0;
 
         // we always must get the same object
-        assertSame(summary, provider.getRootContext()
-                .getSummary("cc", MetricsContext.DetailLevel.BASIC));
+        assertSame(summary, provider.getRootContext().getSummary("cc", 
MetricsContext.DetailLevel.BASIC));
 
         try {
-            provider.getRootContext()
-                    .getSummary("cc", MetricsContext.DetailLevel.ADVANCED);
+            provider.getRootContext().getSummary("cc", 
MetricsContext.DetailLevel.ADVANCED);
             fail("Can't get the same summary with a different DetailLevel");
         } catch (IllegalArgumentException err) {
             assertThat(err.getMessage(), containsString("Already registered"));
@@ -337,53 +323,55 @@ public void testBasicSummary() throws Exception {
         String res = callServlet();
         assertThat(res, containsString("# TYPE cc summary"));
         assertThat(res, CoreMatchers.containsString("cc_sum 20.0"));
-        assertThat(res, CoreMatchers.containsString("cc_count 2.0"));
-        assertThat(res, CoreMatchers.containsString("cc{quantile=\"0.5\",} 
10.0"));
+        assertThat(res, CoreMatchers.containsString("cc_count 2"));
+        assertThat(res, CoreMatchers.containsString("cc{quantile=\"0.5\"} 
10.0"));
     }
 
     @Test
     public void testAdvancedSummary() throws Exception {
-        Summary summary = provider.getRootContext()
-                .getSummary("cc", MetricsContext.DetailLevel.ADVANCED);
+        Summary summary = provider.getRootContext().getSummary("cc", 
MetricsContext.DetailLevel.ADVANCED);
         summary.add(10);
         summary.add(10);
-        int[] count = {0};
+        int[] count = { 0 };
         provider.dump((k, v) -> {
             count[0]++;
             int value = ((Number) v).intValue();
 
             switch (k) {
-                case "cc{quantile=\"0.5\"}":
-                    assertEquals(10, value);
-                    break;
-                case "cc{quantile=\"0.9\"}":
-                    assertEquals(10, value);
-                    break;
-                case "cc{quantile=\"0.99\"}":
-                    assertEquals(10, value);
-                    break;
-                case "cc_count":
-                    assertEquals(2, value);
-                    break;
-                case "cc_sum":
-                    assertEquals(20, value);
-                    break;
-                default:
-                    fail("unespected key " + k);
-                    break;
+            case "cc{quantile=\"0.5\"}":
+                assertEquals(10, value);
+                break;
+            case "cc{quantile=\"0.9\"}":
+                assertEquals(10, value);
+                break;
+            case "cc{quantile=\"0.95\"}":
+                assertEquals(10, value);
+                break;
+            case "cc{quantile=\"0.99\"}":
+                assertEquals(10, value);
+                break;
+            case "cc_count":
+                assertEquals(2, value);
+                break;
+            case "cc_sum":
+                assertEquals(20, value);
+                break;
+            case "cc_avg":
+                assertEquals(10, value);
+                break;
+            default:
+                fail("unespected key " + k);
+                break;
             }
-        }
-        );
-        assertEquals(5, count[0]);
+        });
+        assertEquals(6, count[0]);
         count[0] = 0;
 
         // we always must get the same object
-        assertSame(summary, provider.getRootContext()
-                .getSummary("cc", MetricsContext.DetailLevel.ADVANCED));
+        assertSame(summary, provider.getRootContext().getSummary("cc", 
MetricsContext.DetailLevel.ADVANCED));
 
         try {
-            provider.getRootContext()
-                    .getSummary("cc", MetricsContext.DetailLevel.BASIC);
+            provider.getRootContext().getSummary("cc", 
MetricsContext.DetailLevel.BASIC);
             fail("Can't get the same summary with a different DetailLevel");
         } catch (IllegalArgumentException err) {
             assertThat(err.getMessage(), containsString("Already registered"));
@@ -392,27 +380,35 @@ public void testAdvancedSummary() throws Exception {
         String res = callServlet();
         assertThat(res, containsString("# TYPE cc summary"));
         assertThat(res, CoreMatchers.containsString("cc_sum 20.0"));
-        assertThat(res, CoreMatchers.containsString("cc_count 2.0"));
-        assertThat(res, CoreMatchers.containsString("cc{quantile=\"0.5\",} 
10.0"));
-        assertThat(res, CoreMatchers.containsString("cc{quantile=\"0.9\",} 
10.0"));
-        assertThat(res, CoreMatchers.containsString("cc{quantile=\"0.99\",} 
10.0"));
+        assertThat(res, CoreMatchers.containsString("cc_count 2"));
+        assertThat(res, CoreMatchers.containsString("cc{quantile=\"0.5\"} 
10.0"));
+        assertThat(res, CoreMatchers.containsString("cc{quantile=\"0.95\"} 
10.0"));
+        assertThat(res, CoreMatchers.containsString("cc{quantile=\"0.99\"} 
10.0"));
     }
 
     /**
-     * Using TRACE method to visit metrics provider, the response should be 
403 forbidden.
+     * Using the TRACE method to visit the metrics provider, the response 
should be 405 Method Not Allowed. This unit
+     * test replaces the old test which was tightly coupled to the Jetty 
server implementation.
      */
     @Test
-    public void testTraceCall() throws IOException, IllegalAccessException, 
NoSuchFieldException {
-        Field privateServerField = 
provider.getClass().getDeclaredField("server");
-        privateServerField.setAccessible(true);
-        Server server = (Server) privateServerField.get(provider);
-        int port = ((ServerConnector) 
server.getConnectors()[0]).getLocalPort();
-
-        String metricsUrl = String.format(URL_FORMAT, port);
-        HttpURLConnection conn = (HttpURLConnection) new 
URL(metricsUrl).openConnection();
-        conn.setRequestMethod("TRACE");
-        conn.connect();
-        assertEquals(HttpURLConnection.HTTP_FORBIDDEN, conn.getResponseCode());
+    public void testTraceCall() throws ServletException, IOException {
+        final HttpServlet servlet = provider.getServlet();
+        final HttpServletRequest request = mock(HttpServletRequest.class);
+        final HttpServletResponse response = mock(HttpServletResponse.class);
+
+        // Configure the mock to return an empty enumeration for headers.
+        
when(request.getHeaderNames()).thenReturn(Collections.enumeration(Collections.emptyList()));
+        when(request.getMethod()).thenReturn("TRACE");
+
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        final ServletOutputStream servletOutputStream = 
createMockServletOutputStream(outputStream);
+
+        when(response.getOutputStream()).thenReturn(servletOutputStream);
+
+        servlet.service(request, response);
+
+        // Verify that the servlet set the response status to 405 Method Not 
Allowed.
+        verify(response).sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
     }
 
     @Test
@@ -428,8 +424,8 @@ public void testSummary_asyncAndExceedMaxQueueSize() throws 
Exception {
             metricsProvider = new PrometheusMetricsProvider();
             metricsProvider.configure(config);
             metricsProvider.start();
-            final Summary summary =
-                    metricsProvider.getRootContext().getSummary("cc", 
MetricsContext.DetailLevel.ADVANCED);
+            final Summary summary = 
metricsProvider.getRootContext().getSummary("cc",
+                    MetricsContext.DetailLevel.ADVANCED);
 
             // make sure no error is thrown
             for (int i = 0; i < 10; i++) {
@@ -437,7 +433,7 @@ public void testSummary_asyncAndExceedMaxQueueSize() throws 
Exception {
             }
         } finally {
             if (metricsProvider != null) {
-               metricsProvider.stop();
+                metricsProvider.stop();
             }
         }
     }
@@ -445,12 +441,11 @@ public void testSummary_asyncAndExceedMaxQueueSize() 
throws Exception {
     @Test
     public void testSummarySet() throws Exception {
         final String name = "ss";
-        final String[] keys = {"ns1", "ns2"};
+        final String[] keys = { "ns1", "ns2" };
         final double count = 3.0;
 
         // create and register a SummarySet
-        final SummarySet summarySet = provider.getRootContext()
-                .getSummarySet(name, MetricsContext.DetailLevel.BASIC);
+        final SummarySet summarySet = 
provider.getRootContext().getSummarySet(name, MetricsContext.DetailLevel.BASIC);
 
         // update the SummarySet multiple times
         for (int i = 0; i < count; i++) {
@@ -460,7 +455,7 @@ public void testSummarySet() throws Exception {
         // validate with dump call
         final Map<String, Number> expectedMetricsMap = new HashMap<>();
         for (final String key : keys) {
-            
expectedMetricsMap.put(String.format("%s{key=\"%s\",quantile=\"0.5\"}", name, 
key), 1.0);
+            expectedMetricsMap.put(String.format("%s_avg{key=\"%s\"}", name, 
key), 1.0);
             expectedMetricsMap.put(String.format("%s_count{key=\"%s\"}", name, 
key), count);
             expectedMetricsMap.put(String.format("%s_sum{key=\"%s\"}", name, 
key), count);
         }
@@ -470,43 +465,84 @@ public void testSummarySet() throws Exception {
         final List<String> expectedNames = 
Collections.singletonList(String.format("# TYPE %s summary", name));
         final List<String> expectedMetrics = new ArrayList<>();
         for (final String key : keys) {
-            
expectedMetrics.add(String.format("%s{key=\"%s\",quantile=\"0.5\",} %s", name, 
key, 1.0));
-            expectedMetrics.add(String.format("%s_count{key=\"%s\",} %s", 
name, key, count));
-            expectedMetrics.add(String.format("%s_sum{key=\"%s\",} %s", name, 
key, count));
+            expectedMetrics.add(String.format("%s{key=\"%s\",quantile=\"0.5\"} 
%s", name, key, 1.0));
+            expectedMetrics.add(String.format("%s_count{key=\"%s\"} %d", name, 
key, (int) count));
+            expectedMetrics.add(String.format("%s_sum{key=\"%s\"} %s", name, 
key, count));
         }
         validateWithServletCall(expectedNames, expectedMetrics);
 
         // validate registering with same name, no overwriting
-        assertSame(summarySet, provider.getRootContext()
-                .getSummarySet(name, MetricsContext.DetailLevel.BASIC));
+        assertSame(summarySet, provider.getRootContext().getSummarySet(name, 
MetricsContext.DetailLevel.BASIC));
 
         // validate registering with different DetailLevel, not allowed
         try {
-            provider.getRootContext()
-                    .getSummarySet(name, MetricsContext.DetailLevel.ADVANCED);
+            provider.getRootContext().getSummarySet(name, 
MetricsContext.DetailLevel.ADVANCED);
             fail("Can't get the same summarySet with a different DetailLevel");
         } catch (final IllegalArgumentException e) {
             assertThat(e.getMessage(), containsString("Already registered"));
         }
     }
 
+    /**
+     * Helper method to create a mock ServletOutputStream that writes to a 
provided ByteArrayOutputStream.
+     */
+    private ServletOutputStream createMockServletOutputStream(final 
ByteArrayOutputStream outputStream) {
+        return new ServletOutputStream() {
+            @Override
+            public boolean isReady() {
+                return true;
+            }
+
+            @Override
+            public void setWriteListener(WriteListener writeListener) {
+            }
+
+            @Override
+            public void write(int b) throws IOException {
+                outputStream.write(b);
+            }
+        };
+    }
+
+    /**
+     * A utility method to simulate a GET request to the 
PrometheusMetricsServlet and return the response body as a
+     * String using Mockito mocks for the base servlet interfaces.
+     * <p>
+     * This method demonstrates how to test a servlet by mocking the request 
and response objects and capturing the
+     * output.
+     * </p>
+     *
+     * @return The content of the servlet's response body.
+     *
+     * @throws ServletException
+     *             if a servlet-related error occurs.
+     * @throws IOException
+     *             if an I/O error occurs.
+     */
     private String callServlet() throws ServletException, IOException {
-        // we are not performing an HTTP request
-        // but we are calling directly the servlet
-        StringWriter writer = new StringWriter();
-        HttpServletResponse response = mock(HttpServletResponse.class);
-        when(response.getWriter()).thenReturn(new PrintWriter(writer));
-        HttpServletRequest req = mock(HttpServletRequest.class);
-        provider.getServlet().doGet(req, response);
-        String res = writer.toString();
-        return res;
+        final HttpServlet servlet = provider.getServlet();
+        final HttpServletRequest request = mock(HttpServletRequest.class);
+        final HttpServletResponse response = mock(HttpServletResponse.class);
+
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        final ServletOutputStream servletOutputStream = 
createMockServletOutputStream(outputStream);
+
+        // Configure the mock response to return our custom output stream.
+        when(response.getOutputStream()).thenReturn(servletOutputStream);
+        when(request.getMethod()).thenReturn("GET");
+        servlet.service(request, response);
+
+        // Verify that the servlet set the HTTP status code to OK.
+        verify(response).setStatus(HttpServletResponse.SC_OK);
+
+        return outputStream.toString();
     }
 
     @Test
     public void testGaugeSet_singleGaugeSet() throws Exception {
         final String name = QuotaMetricsUtils.QUOTA_BYTES_LIMIT_PER_NAMESPACE;
-        final Number[] values = {10.0, 100.0};
-        final String[] keys = {"ns11", "ns12"};
+        final Number[] values = { 10.0, 100.0 };
+        final String[] keys = { "ns11", "ns12" };
         final Map<String, Number> metricsMap = new HashMap<>();
         for (int i = 0; i < values.length; i++) {
             metricsMap.put(keys[i], values[i]);
@@ -528,7 +564,7 @@ public void testGaugeSet_singleGaugeSet() throws Exception {
         final List<String> expectedNames = 
Collections.singletonList(String.format("# TYPE %s gauge", name));
         final List<String> expectedMetrics = new ArrayList<>();
         for (int i = 0; i < values.length; i++) {
-            expectedMetrics.add(String.format("%s{key=\"%s\",} %s", name, 
keys[i], values[i]));
+            expectedMetrics.add(String.format("%s{key=\"%s\"} %s", name, 
keys[i], values[i]));
         }
         validateWithServletCall(expectedNames, expectedMetrics);
         assertEquals(2, callCount.get());
@@ -548,13 +584,11 @@ public void testGaugeSet_singleGaugeSet() throws 
Exception {
 
     @Test
     public void testGaugeSet_multipleGaugeSets() throws Exception {
-        final String[] names = new String[] {
-                QuotaMetricsUtils.QUOTA_COUNT_LIMIT_PER_NAMESPACE,
-                QuotaMetricsUtils.QUOTA_COUNT_USAGE_PER_NAMESPACE
-        };
+        final String[] names = new String[] { 
QuotaMetricsUtils.QUOTA_COUNT_LIMIT_PER_NAMESPACE,
+                QuotaMetricsUtils.QUOTA_COUNT_USAGE_PER_NAMESPACE };
 
-        final Number[] values = new Number[] {20.0, 200.0};
-        final String[] keys = new String[]{"ns21", "ns22"};
+        final Number[] values = new Number[] { 20.0, 200.0 };
+        final String[] keys = new String[] { "ns21", "ns22" };
         final int count = names.length;
         final AtomicInteger[] callCounts = new AtomicInteger[count];
 
@@ -581,7 +615,7 @@ public void testGaugeSet_multipleGaugeSets() throws 
Exception {
         final List<String> expectedMetrics = new ArrayList<>();
         for (int i = 0; i < count; i++) {
             expectedNames.add(String.format("# TYPE %s gauge", names[i]));
-            expectedMetrics.add(String.format("%s{key=\"%s\",} %s", names[i], 
keys[i], values[i]));
+            expectedMetrics.add(String.format("%s{key=\"%s\"} %s", names[i], 
keys[i], values[i]));
         }
         validateWithServletCall(expectedNames, expectedMetrics);
         for (int i = 0; i < count; i++) {
@@ -609,14 +643,12 @@ public void testGaugeSet_multipleGaugeSets() throws 
Exception {
 
     @Test
     public void testGaugeSet_overwriteRegister() {
-        final String[] names = new String[] {
-                QuotaMetricsUtils.QUOTA_COUNT_LIMIT_PER_NAMESPACE,
-                QuotaMetricsUtils.QUOTA_COUNT_USAGE_PER_NAMESPACE
-        };
+        final String[] names = new String[] { 
QuotaMetricsUtils.QUOTA_COUNT_LIMIT_PER_NAMESPACE,
+                QuotaMetricsUtils.QUOTA_COUNT_USAGE_PER_NAMESPACE };
 
         final int count = names.length;
-        final Number[] values = new Number[]{30.0, 300.0};
-        final String[] keys = new String[] {"ns31", "ns32"};
+        final Number[] values = new Number[] { 30.0, 300.0 };
+        final String[] keys = new String[] { "ns31", "ns32" };
         final AtomicInteger[] callCounts = new AtomicInteger[count];
 
         // create and register the GaugeSets
@@ -656,22 +688,18 @@ public void testGaugeSet_nullKey() {
 
     @Test
     public void testGaugeSet_registerWithNullGaugeSet() {
-        assertThrows(NullPointerException.class,
-                () -> provider.getRootContext().registerGaugeSet("name", 
null));
+        assertThrows(NullPointerException.class, () -> 
provider.getRootContext().registerGaugeSet("name", null));
 
-        assertThrows(NullPointerException.class,
-                () -> provider.getRootContext().registerGaugeSet(null, 
HashMap::new));
+        assertThrows(NullPointerException.class, () -> 
provider.getRootContext().registerGaugeSet(null, HashMap::new));
     }
 
     @Test
     public void testGaugeSet_unregisterNull() {
-        assertThrows(NullPointerException.class,
-                () -> provider.getRootContext().unregisterGaugeSet(null));
+        assertThrows(NullPointerException.class, () -> 
provider.getRootContext().unregisterGaugeSet(null));
     }
 
-    private void createAndRegisterGaugeSet(final String name,
-                                           final Map<String, Number> 
metricsMap,
-                                           final AtomicInteger callCount) {
+    private void createAndRegisterGaugeSet(final String name, final 
Map<String, Number> metricsMap,
+            final AtomicInteger callCount) {
         final GaugeSet gaugeSet = () -> {
             callCount.addAndGet(1);
             return metricsMap;
@@ -686,8 +714,8 @@ private void validateWithDump(final Map<String, Number> 
expectedMetrics) {
         expectedMetrics.forEach((key, value) -> assertEquals(value, 
returnedMetrics.get(key)));
     }
 
-    private void validateWithServletCall(final List<String> expectedNames,
-                                         final List<String> expectedMetrics) 
throws Exception {
+    private void validateWithServletCall(final List<String> expectedNames, 
final List<String> expectedMetrics)
+            throws Exception {
         final String response = callServlet();
         if (expectedNames.isEmpty() && expectedMetrics.isEmpty()) {
             assertTrue(response.isEmpty());
diff --git 
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsTestBase.java
 
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsTestBase.java
index 9ed4995fb..efd8f38a8 100644
--- 
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsTestBase.java
+++ 
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsTestBase.java
@@ -18,32 +18,27 @@
 
 package org.apache.zookeeper.metrics.prometheus;
 
-import io.prometheus.client.CollectorRegistry;
-import io.prometheus.client.hotspot.DefaultExports;
+import io.prometheus.metrics.instrumentation.jvm.JvmMetrics;
+import io.prometheus.metrics.model.registry.PrometheusRegistry;
 import java.lang.reflect.Field;
+import java.util.Set;
 import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
 
 /**
  * The base test for prometheus metrics unit tests.
  */
 public abstract class PrometheusMetricsTestBase {
 
-    @BeforeEach
-    void setUp() throws Exception {
-        CollectorRegistry.defaultRegistry.clear();
-        resetDefaultExportsInitializedFlag();
-    }
-
     @AfterEach
     void tearDown() throws Exception {
-        CollectorRegistry.defaultRegistry.clear();
-        resetDefaultExportsInitializedFlag();
-    }
-
-    protected void resetDefaultExportsInitializedFlag() throws Exception {
-        Field initializedField = 
DefaultExports.class.getDeclaredField("initialized");
-        initializedField.setAccessible(true);
-        initializedField.set(null, false);
+        PrometheusRegistry.defaultRegistry.clear();
+        // JvmMetrics uses a static Set to track which registries it has been
+        // registered with. We need to clear this set via reflection to allow
+        // re-initialization in subsequent tests.
+        Field registeredField = 
JvmMetrics.class.getDeclaredField("REGISTERED");
+        registeredField.setAccessible(true);
+        @SuppressWarnings("unchecked")
+        Set<?> registeredSet = (Set<?>) registeredField.get(null);
+        registeredSet.clear();
     }
-}
+}
\ No newline at end of file
diff --git 
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/resources/data/ssl/client_keystore.jks
 
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/resources/data/ssl/client_keystore.jks
index 8636f41dd..56c0bdb7b 100644
Binary files 
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/resources/data/ssl/client_keystore.jks
 and 
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/resources/data/ssl/client_keystore.jks
 differ
diff --git 
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/resources/data/ssl/client_truststore.jks
 
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/resources/data/ssl/client_truststore.jks
index 3e5893d0b..23fc75232 100644
Binary files 
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/resources/data/ssl/client_truststore.jks
 and 
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/resources/data/ssl/client_truststore.jks
 differ
diff --git 
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/resources/data/ssl/server_keystore.jks
 
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/resources/data/ssl/server_keystore.jks
index d524c6268..2cfdbfc6d 100644
Binary files 
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/resources/data/ssl/server_keystore.jks
 and 
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/resources/data/ssl/server_keystore.jks
 differ
diff --git 
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/resources/data/ssl/server_truststore.jks
 
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/resources/data/ssl/server_truststore.jks
index faa322836..33d022ef0 100644
Binary files 
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/resources/data/ssl/server_truststore.jks
 and 
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/resources/data/ssl/server_truststore.jks
 differ

Reply via email to