diff --git a/pom.xml b/pom.xml index c73bd736..a62a5ecf 100644 --- a/pom.xml +++ b/pom.xml @@ -47,6 +47,7 @@ 0.11.4.1 2.5.3 3.1.2 + 0.0.14 1.10.19 4.8.0 2.4.5 @@ -137,6 +138,13 @@ provided true + + io.prometheus + simpleclient + ${simpleclient.version} + provided + true + simple-jndi simple-jndi diff --git a/src/main/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollector.java b/src/main/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollector.java new file mode 100644 index 00000000..093d8c73 --- /dev/null +++ b/src/main/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollector.java @@ -0,0 +1,39 @@ +package com.zaxxer.hikari.metrics.prometheus; + +import com.zaxxer.hikari.metrics.PoolStats; +import io.prometheus.client.Collector; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +class HikariCPCollector extends Collector { + private final PoolStats poolStats; + private final List labelNames; + private final List labelValues; + + HikariCPCollector(String poolName, PoolStats poolStats) { + this.poolStats = poolStats; + this.labelNames = Collections.singletonList("pool"); + this.labelValues = Collections.singletonList(poolName); + } + + @Override + public List collect() { + return Arrays.asList( + createSample("hikaricp_active_connections", "Active connections", poolStats.getActiveConnections()), + createSample("hikaricp_idle_connections", "Idle connections", poolStats.getIdleConnections()), + createSample("hikaricp_pending_threads", "Pending threads", poolStats.getPendingThreads()), + createSample("hikaricp_connections", "The number of current connections", poolStats.getTotalConnections()) + ); + } + + private MetricFamilySamples createSample(String name, String helpMessage, double value) { + List 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); + } +}