samples = Collections.singletonList(new MetricFamilySamples.Sample(name,
+ labelNames,
+ labelValues,
+ value));
+
+ return new MetricFamilySamples(name, Type.GAUGE, helpMessage, samples);
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTracker.java b/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTracker.java
new file mode 100644
index 00000000..8cdcfefc
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTracker.java
@@ -0,0 +1,52 @@
+package com.zaxxer.hikari.metrics.prometheus;
+
+import com.zaxxer.hikari.metrics.MetricsTracker;
+import io.prometheus.client.Counter;
+import io.prometheus.client.Summary;
+
+class PrometheusMetricsTracker extends MetricsTracker {
+ private final Counter.Child connectionTimeoutCounter;
+ private final Summary.Child elapsedAcquiredSummary;
+ private final Summary.Child elapsedBorrowedSummary;
+
+ PrometheusMetricsTracker(String poolName) {
+ super();
+
+ Counter counter = Counter.build()
+ .name("hikaricp_connection_timeout_count")
+ .labelNames("pool")
+ .help("Connection timeout count")
+ .register();
+
+ this.connectionTimeoutCounter = counter.labels(poolName);
+
+ Summary elapsedAcquiredSummary = Summary.build()
+ .name("hikaricp_connection_acquired_nanos")
+ .labelNames("pool")
+ .help("Connection acquired time")
+ .register();
+ this.elapsedAcquiredSummary = elapsedAcquiredSummary.labels(poolName);
+
+ Summary elapsedBorrowedSummary = Summary.build()
+ .name("hikaricp_connection_usage_millis")
+ .labelNames("pool")
+ .help("Connection usage")
+ .register();
+ this.elapsedBorrowedSummary = elapsedBorrowedSummary.labels(poolName);
+ }
+
+ @Override
+ public void recordConnectionAcquiredNanos(long elapsedAcquiredNanos) {
+ elapsedAcquiredSummary.observe(elapsedAcquiredNanos);
+ }
+
+ @Override
+ public void recordConnectionUsageMillis(long elapsedBorrowedMillis) {
+ elapsedBorrowedSummary.observe(elapsedBorrowedMillis);
+ }
+
+ @Override
+ public void recordConnectionTimeout() {
+ connectionTimeoutCounter.inc();
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactory.java b/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactory.java
new file mode 100644
index 00000000..cb9d5e53
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactory.java
@@ -0,0 +1,19 @@
+package com.zaxxer.hikari.metrics.prometheus;
+
+import com.zaxxer.hikari.metrics.MetricsTracker;
+import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
+import com.zaxxer.hikari.metrics.PoolStats;
+
+/**
+ * {@code
+ * HikariConfig config = new HikariConfig();
+ * config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory());
+ * }
+ */
+public class PrometheusMetricsTrackerFactory implements MetricsTrackerFactory {
+ @Override
+ public MetricsTracker create(String poolName, PoolStats poolStats) {
+ new HikariCPCollector(poolName, poolStats).register();
+ return new PrometheusMetricsTracker(poolName);
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java b/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java
new file mode 100644
index 00000000..0a966df3
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java
@@ -0,0 +1,85 @@
+package com.zaxxer.hikari.metrics.prometheus;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import io.prometheus.client.CollectorRegistry;
+import org.junit.Test;
+
+import java.sql.Connection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class HikariCPCollectorTest {
+ @Test
+ public void noConnection() throws Exception {
+ HikariConfig config = new HikariConfig();
+ config.setPoolName("no_connection");
+ config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory());
+ config.setJdbcUrl("jdbc:h2:mem:");
+
+ new HikariDataSource(config);
+
+ assertThat(getValue("hikaricp_active_connections", "no_connection"), is(0.0));
+ assertThat(getValue("hikaricp_idle_connections", "no_connection"), is(0.0));
+ assertThat(getValue("hikaricp_pending_threads", "no_connection"), is(0.0));
+ assertThat(getValue("hikaricp_connections", "no_connection"), is(0.0));
+ }
+
+ @Test
+ public void noConnectionWithoutPoolName() throws Exception {
+ HikariConfig config = new HikariConfig();
+ config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory());
+ config.setJdbcUrl("jdbc:h2:mem:");
+
+ new HikariDataSource(config);
+
+ assertThat(getValue("hikaricp_active_connections", "HikariPool-1"), is(0.0));
+ assertThat(getValue("hikaricp_idle_connections", "HikariPool-1"), is(0.0));
+ assertThat(getValue("hikaricp_pending_threads", "HikariPool-1"), is(0.0));
+ assertThat(getValue("hikaricp_connections", "HikariPool-1"), is(0.0));
+ }
+
+ @Test
+ public void connection1() throws Exception {
+ HikariConfig config = new HikariConfig();
+ config.setPoolName("connection1");
+ config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory());
+ config.setJdbcUrl("jdbc:h2:mem:");
+
+ HikariDataSource ds = new HikariDataSource(config);
+ Connection connection1 = ds.getConnection();
+
+ assertThat(getValue("hikaricp_active_connections", "connection1"), is(1.0));
+ assertThat(getValue("hikaricp_idle_connections", "connection1"), is(0.0));
+ assertThat(getValue("hikaricp_pending_threads", "connection1"), is(0.0));
+ assertThat(getValue("hikaricp_connections", "connection1"), is(1.0));
+
+ connection1.close();
+ }
+
+ @Test
+ public void connectionClosed() throws Exception {
+ HikariConfig config = new HikariConfig();
+ config.setPoolName("connectionClosed");
+ config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory());
+ config.setJdbcUrl("jdbc:h2:mem:");
+ config.setMaximumPoolSize(20);
+
+ HikariDataSource ds = new HikariDataSource(config);
+ Connection connection1 = ds.getConnection();
+ connection1.close();
+
+ assertThat(getValue("hikaricp_active_connections", "connectionClosed"), is(0.0));
+ assertThat(getValue("hikaricp_idle_connections", "connectionClosed"), is(1.0));
+ assertThat(getValue("hikaricp_pending_threads", "connectionClosed"), is(0.0));
+ assertThat(getValue("hikaricp_connections", "connectionClosed"), is(1.0));
+ }
+
+ private double getValue(String name, String poolName) {
+ String[] labelNames = {"pool"};
+ String[] labelValues = {poolName};
+ return CollectorRegistry.defaultRegistry.getSampleValue(name, labelNames, labelValues);
+ }
+
+}
diff --git a/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java b/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java
new file mode 100644
index 00000000..b438124e
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java
@@ -0,0 +1,60 @@
+package com.zaxxer.hikari.metrics.prometheus;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import io.prometheus.client.CollectorRegistry;
+import org.junit.Test;
+
+import java.sql.Connection;
+import java.sql.SQLTransientConnectionException;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+public class PrometheusMetricsTrackerTest {
+ @Test
+ public void recordConnectionTimeout() throws Exception {
+ String poolName = "record";
+
+ HikariConfig config = new HikariConfig();
+ config.setPoolName(poolName);
+ config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory());
+ config.setJdbcUrl("jdbc:h2:mem:");
+ config.setMaximumPoolSize(1);
+ config.setConnectionTimeout(250);
+
+ String[] labelNames = {"pool"};
+ String[] labelValues = {poolName};
+
+ HikariDataSource hikariDataSource = new HikariDataSource(config);
+ Connection connection = hikariDataSource.getConnection();
+ try {
+ hikariDataSource.getConnection();
+ } catch (SQLTransientConnectionException ignored) {
+ }
+ connection.close();
+
+ assertThat(CollectorRegistry.defaultRegistry.getSampleValue(
+ "hikaricp_connection_timeout_count",
+ labelNames,
+ labelValues), is(1.0));
+ assertThat(CollectorRegistry.defaultRegistry.getSampleValue(
+ "hikaricp_connection_acquired_nanos_count",
+ labelNames,
+ labelValues), is(equalTo(1.0)));
+ assertTrue(CollectorRegistry.defaultRegistry.getSampleValue(
+ "hikaricp_connection_acquired_nanos_sum",
+ labelNames,
+ labelValues) > 0.0);
+ assertThat(CollectorRegistry.defaultRegistry.getSampleValue(
+ "hikaricp_connection_usage_millis_count",
+ labelNames,
+ labelValues), is(equalTo(1.0)));
+ assertTrue(CollectorRegistry.defaultRegistry.getSampleValue(
+ "hikaricp_connection_usage_millis_sum",
+ labelNames,
+ labelValues) > 0.0);
+ }
+}