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