From 086bf18389282d1a7484e243d6af49890cec4fd7 Mon Sep 17 00:00:00 2001 From: Aleksandr Podkutin Date: Sun, 23 Jun 2019 02:20:05 -0500 Subject: [PATCH] Refactor/fix Prometheus metrics for multiple connection pools, add unit tests (#1331) * Refactor/fix Prometheus metrics for multiple connection pools Changes: * Fix "Collector already registered that provides name: hikaricp_connection_timeout_total" error * Register only one HikariCPCollector instance in one CollectorRegistry instance * Add ability to remove metrics when connection pool is shutting down * Add/update unit tests * Refactor/add unit tests - metrics package * Re-format curly braces to be inline with the whole code base --- .../MicrometerMetricsTrackerFactory.java | 3 +- .../metrics/prometheus/HikariCPCollector.java | 21 +- .../prometheus/PrometheusMetricsTracker.java | 87 +++--- .../PrometheusMetricsTrackerFactory.java | 53 ++-- .../CodaHaleMetricsTrackerTest.java | 27 +- .../MicrometerMetricsTrackerTest.java | 18 +- .../prometheus/HikariCPCollectorTest.java | 93 ++++++- .../PrometheusMetricsTrackerFactoryTest.java | 44 ++- .../PrometheusMetricsTrackerTest.java | 251 ++++++++++++++---- .../zaxxer/hikari/mocks/StubPoolStats.java | 20 ++ 10 files changed, 454 insertions(+), 163 deletions(-) create mode 100644 src/test/java/com/zaxxer/hikari/mocks/StubPoolStats.java diff --git a/src/main/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerFactory.java b/src/main/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerFactory.java index 4072927a..b2e9a24a 100644 --- a/src/main/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerFactory.java +++ b/src/main/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerFactory.java @@ -5,7 +5,8 @@ import com.zaxxer.hikari.metrics.MetricsTrackerFactory; import com.zaxxer.hikari.metrics.PoolStats; import io.micrometer.core.instrument.MeterRegistry; -public class MicrometerMetricsTrackerFactory implements MetricsTrackerFactory { +public class MicrometerMetricsTrackerFactory implements MetricsTrackerFactory +{ private final MeterRegistry registry; diff --git a/src/main/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollector.java b/src/main/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollector.java index 41b06836..38706c4d 100644 --- a/src/main/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollector.java +++ b/src/main/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollector.java @@ -12,13 +12,14 @@ * 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 com.zaxxer.hikari.metrics.prometheus; import com.zaxxer.hikari.metrics.PoolStats; import io.prometheus.client.Collector; import io.prometheus.client.GaugeMetricFamily; + import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -26,14 +27,16 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; -class HikariCPCollector extends Collector { +class HikariCPCollector extends Collector +{ private static final List LABEL_NAMES = Collections.singletonList("pool"); private final Map poolStatsMap = new ConcurrentHashMap<>(); @Override - public List collect() { + public List collect() + { return Arrays.asList( createGauge("hikaricp_active_connections", "Active connections", PoolStats::getActiveConnections), @@ -50,13 +53,19 @@ class HikariCPCollector extends Collector { ); } - protected HikariCPCollector add(String name, PoolStats poolStats) { + void add(String name, PoolStats poolStats) + { poolStatsMap.put(name, poolStats); - return this; + } + + void remove(String name) + { + poolStatsMap.remove(name); } private GaugeMetricFamily createGauge(String metric, String help, - Function metricValueFunction) { + Function metricValueFunction) + { GaugeMetricFamily metricFamily = new GaugeMetricFamily(metric, help, LABEL_NAMES); poolStatsMap.forEach((k, v) -> metricFamily.addMetric( Collections.singletonList(k), diff --git a/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTracker.java b/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTracker.java index 8adc702e..5d03c1ad 100644 --- a/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTracker.java +++ b/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTracker.java @@ -12,66 +12,69 @@ * 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 com.zaxxer.hikari.metrics.prometheus; import com.zaxxer.hikari.metrics.IMetricsTracker; +import com.zaxxer.hikari.metrics.prometheus.PrometheusMetricsTrackerFactory.RegistrationStatus; import io.prometheus.client.CollectorRegistry; import io.prometheus.client.Counter; import io.prometheus.client.Summary; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; +import static com.zaxxer.hikari.metrics.prometheus.PrometheusMetricsTrackerFactory.RegistrationStatus.REGISTERED; + class PrometheusMetricsTracker implements IMetricsTracker { - private final Counter CONNECTION_TIMEOUT_COUNTER = Counter.build() + private final static Counter CONNECTION_TIMEOUT_COUNTER = Counter.build() .name("hikaricp_connection_timeout_total") .labelNames("pool") .help("Connection timeout total count") .create(); - private final Summary ELAPSED_ACQUIRED_SUMMARY = - registerSummary("hikaricp_connection_acquired_nanos", "Connection acquired time (ns)"); + private final static Summary ELAPSED_ACQUIRED_SUMMARY = + createSummary("hikaricp_connection_acquired_nanos", "Connection acquired time (ns)"); - private final Summary ELAPSED_BORROWED_SUMMARY = - registerSummary("hikaricp_connection_usage_millis", "Connection usage (ms)"); + private final static Summary ELAPSED_USAGE_SUMMARY = + createSummary("hikaricp_connection_usage_millis", "Connection usage (ms)"); - private final Summary ELAPSED_CREATION_SUMMARY = - registerSummary("hikaricp_connection_creation_millis", "Connection creation (ms)"); + private final static Summary ELAPSED_CREATION_SUMMARY = + createSummary("hikaricp_connection_creation_millis", "Connection creation (ms)"); - private final Counter.Child connectionTimeoutCounterChild; + private final static Map registrationStatuses = new ConcurrentHashMap<>(); - private Summary registerSummary(String name, String help) { - return Summary.build() - .name(name) - .labelNames("pool") - .help(help) - .quantile(0.5, 0.05) - .quantile(0.95, 0.01) - .quantile(0.99, 0.001) - .maxAgeSeconds(TimeUnit.MINUTES.toSeconds(5)) - .ageBuckets(5) - .create(); - } + private final String poolName; + private final HikariCPCollector hikariCPCollector; + + private final Counter.Child connectionTimeoutCounterChild; private final Summary.Child elapsedAcquiredSummaryChild; - private final Summary.Child elapsedBorrowedSummaryChild; + private final Summary.Child elapsedUsageSummaryChild; private final Summary.Child elapsedCreationSummaryChild; - PrometheusMetricsTracker(String poolName, CollectorRegistry collectorRegistry) { + PrometheusMetricsTracker(String poolName, CollectorRegistry collectorRegistry, HikariCPCollector hikariCPCollector) + { registerMetrics(collectorRegistry); + this.poolName = poolName; + this.hikariCPCollector = hikariCPCollector; this.connectionTimeoutCounterChild = CONNECTION_TIMEOUT_COUNTER.labels(poolName); this.elapsedAcquiredSummaryChild = ELAPSED_ACQUIRED_SUMMARY.labels(poolName); - this.elapsedBorrowedSummaryChild = ELAPSED_BORROWED_SUMMARY.labels(poolName); + this.elapsedUsageSummaryChild = ELAPSED_USAGE_SUMMARY.labels(poolName); this.elapsedCreationSummaryChild = ELAPSED_CREATION_SUMMARY.labels(poolName); } - private void registerMetrics(CollectorRegistry collectorRegistry){ - CONNECTION_TIMEOUT_COUNTER.register(collectorRegistry); - ELAPSED_ACQUIRED_SUMMARY.register(collectorRegistry); - ELAPSED_BORROWED_SUMMARY.register(collectorRegistry); - ELAPSED_CREATION_SUMMARY.register(collectorRegistry); + private void registerMetrics(CollectorRegistry collectorRegistry) + { + if (registrationStatuses.putIfAbsent(collectorRegistry, REGISTERED) == null) { + CONNECTION_TIMEOUT_COUNTER.register(collectorRegistry); + ELAPSED_ACQUIRED_SUMMARY.register(collectorRegistry); + ELAPSED_USAGE_SUMMARY.register(collectorRegistry); + ELAPSED_CREATION_SUMMARY.register(collectorRegistry); + } } @Override @@ -83,7 +86,7 @@ class PrometheusMetricsTracker implements IMetricsTracker @Override public void recordConnectionUsageMillis(long elapsedBorrowedMillis) { - elapsedBorrowedSummaryChild.observe(elapsedBorrowedMillis); + elapsedUsageSummaryChild.observe(elapsedBorrowedMillis); } @Override @@ -97,4 +100,28 @@ class PrometheusMetricsTracker implements IMetricsTracker { connectionTimeoutCounterChild.inc(); } + + private static Summary createSummary(String name, String help) + { + return Summary.build() + .name(name) + .labelNames("pool") + .help(help) + .quantile(0.5, 0.05) + .quantile(0.95, 0.01) + .quantile(0.99, 0.001) + .maxAgeSeconds(TimeUnit.MINUTES.toSeconds(5)) + .ageBuckets(5) + .create(); + } + + @Override + public void close() + { + hikariCPCollector.remove(poolName); + CONNECTION_TIMEOUT_COUNTER.remove(poolName); + ELAPSED_ACQUIRED_SUMMARY.remove(poolName); + ELAPSED_USAGE_SUMMARY.remove(poolName); + ELAPSED_CREATION_SUMMARY.remove(poolName); + } } diff --git a/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactory.java b/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactory.java index 2086d972..f0dbb6c3 100644 --- a/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactory.java +++ b/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactory.java @@ -12,59 +12,78 @@ * 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 com.zaxxer.hikari.metrics.prometheus; import com.zaxxer.hikari.metrics.IMetricsTracker; import com.zaxxer.hikari.metrics.MetricsTrackerFactory; import com.zaxxer.hikari.metrics.PoolStats; +import io.prometheus.client.Collector; import io.prometheus.client.CollectorRegistry; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static com.zaxxer.hikari.metrics.prometheus.PrometheusMetricsTrackerFactory.RegistrationStatus.REGISTERED; + /** *
{@code
  * HikariConfig config = new HikariConfig();
  * config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory());
  * }
+ * or + *
{@code
+ * config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(new CollectorRegistry()));
+ * }
* * Note: the internal {@see io.prometheus.client.Summary} requires heavy locks. Consider using * {@see PrometheusHistogramMetricsTrackerFactory} if performance plays a role and you don't need the summary per se. */ -public class PrometheusMetricsTrackerFactory implements MetricsTrackerFactory { +public class PrometheusMetricsTrackerFactory implements MetricsTrackerFactory +{ - private HikariCPCollector collector; + private final static Map registrationStatuses = new ConcurrentHashMap<>(); - private CollectorRegistry collectorRegistry; + private final HikariCPCollector collector = new HikariCPCollector(); + + private final CollectorRegistry collectorRegistry; + + public enum RegistrationStatus + { + REGISTERED; + } /** * Default Constructor. The Hikari metrics are registered to the default * collector registry ({@code CollectorRegistry.defaultRegistry}). */ - public PrometheusMetricsTrackerFactory() { - this.collectorRegistry = CollectorRegistry.defaultRegistry; + public PrometheusMetricsTrackerFactory() + { + this(CollectorRegistry.defaultRegistry); } /** * Constructor that allows to pass in a {@link CollectorRegistry} to which the * Hikari metrics are registered. */ - public PrometheusMetricsTrackerFactory(CollectorRegistry collectorRegistry) { + public PrometheusMetricsTrackerFactory(CollectorRegistry collectorRegistry) + { this.collectorRegistry = collectorRegistry; } @Override - public IMetricsTracker create(String poolName, PoolStats poolStats) { - getCollector().add(poolName, poolStats); - return new PrometheusMetricsTracker(poolName, this.collectorRegistry); + public IMetricsTracker create(String poolName, PoolStats poolStats) + { + registerCollector(this.collector, this.collectorRegistry); + this.collector.add(poolName, poolStats); + return new PrometheusMetricsTracker(poolName, this.collectorRegistry, this.collector); } - /** - * initialize and register collector if it isn't initialized yet - */ - private HikariCPCollector getCollector() { - if (collector == null) { - collector = new HikariCPCollector().register(this.collectorRegistry); + private void registerCollector(Collector collector, CollectorRegistry collectorRegistry) + { + if (registrationStatuses.putIfAbsent(collectorRegistry, REGISTERED) == null) { + collector.register(collectorRegistry); } - return collector; } } diff --git a/src/test/java/com/zaxxer/hikari/metrics/dropwizard/CodaHaleMetricsTrackerTest.java b/src/test/java/com/zaxxer/hikari/metrics/dropwizard/CodaHaleMetricsTrackerTest.java index 6a7ed1a1..331182c7 100644 --- a/src/test/java/com/zaxxer/hikari/metrics/dropwizard/CodaHaleMetricsTrackerTest.java +++ b/src/test/java/com/zaxxer/hikari/metrics/dropwizard/CodaHaleMetricsTrackerTest.java @@ -1,18 +1,18 @@ package com.zaxxer.hikari.metrics.dropwizard; -import static org.mockito.Mockito.verify; - -import com.zaxxer.hikari.metrics.PoolStats; +import com.codahale.metrics.MetricRegistry; +import com.zaxxer.hikari.mocks.StubPoolStats; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import com.codahale.metrics.MetricRegistry; +import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) -public class CodaHaleMetricsTrackerTest { +public class CodaHaleMetricsTrackerTest +{ @Mock public MetricRegistry mockMetricRegistry; @@ -20,12 +20,14 @@ public class CodaHaleMetricsTrackerTest { private CodaHaleMetricsTracker testee; @Before - public void setup() { - testee = new CodaHaleMetricsTracker("mypool", poolStats(), mockMetricRegistry); + public void setup() + { + testee = new CodaHaleMetricsTracker("mypool", new StubPoolStats(0), mockMetricRegistry); } @Test - public void close() throws Exception { + public void close() + { testee.close(); verify(mockMetricRegistry).remove("mypool.pool.Wait"); @@ -39,13 +41,4 @@ public class CodaHaleMetricsTrackerTest { verify(mockMetricRegistry).remove("mypool.pool.MaxConnections"); verify(mockMetricRegistry).remove("mypool.pool.MinConnections"); } - - private PoolStats poolStats() { - return new PoolStats(0) { - @Override - protected void update() { - // do nothing - } - }; - } } diff --git a/src/test/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerTest.java b/src/test/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerTest.java index d137a918..5e265812 100644 --- a/src/test/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerTest.java +++ b/src/test/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerTest.java @@ -1,30 +1,28 @@ package com.zaxxer.hikari.metrics.micrometer; -import com.zaxxer.hikari.metrics.PoolStats; +import com.zaxxer.hikari.mocks.StubPoolStats; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -public class MicrometerMetricsTrackerTest { +public class MicrometerMetricsTrackerTest +{ private MeterRegistry mockMeterRegistry = new SimpleMeterRegistry(); private MicrometerMetricsTracker testee; @Before - public void setup(){ - testee = new MicrometerMetricsTracker("mypool", new PoolStats(1000L) { - @Override - protected void update() { - // nothing - } - }, mockMeterRegistry); + public void setup() + { + testee = new MicrometerMetricsTracker("mypool", new StubPoolStats(1000L), mockMeterRegistry); } @Test - public void close() throws Exception { + public void close() + { Assert.assertNotNull(mockMeterRegistry.find("hikaricp.connections.acquire").tag("pool", "mypool").timer()); Assert.assertNotNull(mockMeterRegistry.find("hikaricp.connections.usage").tag("pool", "mypool").timer()); Assert.assertNotNull(mockMeterRegistry.find("hikaricp.connections.creation").tag("pool", "mypool").timer()); diff --git a/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java b/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java index 22d4fa53..8d08792a 100644 --- a/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java +++ b/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java @@ -19,10 +19,14 @@ package com.zaxxer.hikari.metrics.prometheus; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static com.zaxxer.hikari.util.UtilityElf.quietlySleep; import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import java.sql.Connection; +import java.util.List; +import com.zaxxer.hikari.metrics.PoolStats; +import io.prometheus.client.Collector; import org.junit.Before; import org.junit.Test; @@ -32,18 +36,20 @@ import com.zaxxer.hikari.mocks.StubConnection; import io.prometheus.client.CollectorRegistry; -public class HikariCPCollectorTest { +public class HikariCPCollectorTest +{ private CollectorRegistry collectorRegistry; @Before - public void setupCollectorRegistry(){ + public void setupCollectorRegistry() + { this.collectorRegistry = new CollectorRegistry(); } - @Test - public void noConnection() throws Exception { + public void noConnection() + { HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(this.collectorRegistry)); @@ -64,7 +70,8 @@ public class HikariCPCollectorTest { } @Test - public void noConnectionWithoutPoolName() throws Exception { + public void noConnectionWithoutPoolName() + { HikariConfig config = new HikariConfig(); config.setMinimumIdle(0); config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(this.collectorRegistry)); @@ -86,7 +93,8 @@ public class HikariCPCollectorTest { } @Test - public void connection1() throws Exception { + public void connection1() throws Exception + { HikariConfig config = newHikariConfig(); config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(this.collectorRegistry)); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); @@ -111,7 +119,8 @@ public class HikariCPCollectorTest { } @Test - public void connectionClosed() throws Exception { + public void connectionClosed() throws Exception + { HikariConfig config = newHikariConfig(); config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(this.collectorRegistry)); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); @@ -135,10 +144,78 @@ public class HikariCPCollectorTest { } } - private double getValue(String name, String poolName) { + @Test + public void poolStatsRemovedAfterShutDown() throws Exception + { + HikariConfig config = new HikariConfig(); + config.setPoolName("shutDownPool"); + config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(this.collectorRegistry)); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + config.setMaximumPoolSize(1); + + StubConnection.slowCreate = true; + try (HikariDataSource ds = new HikariDataSource(config)) { + try (Connection connection1 = ds.getConnection()) { + // close immediately + } + + assertThat(getValue("hikaricp_active_connections", "shutDownPool"), is(0.0)); + assertThat(getValue("hikaricp_idle_connections", "shutDownPool"), is(1.0)); + assertThat(getValue("hikaricp_pending_threads", "shutDownPool"), is(0.0)); + assertThat(getValue("hikaricp_connections", "shutDownPool"), is(1.0)); + assertThat(getValue("hikaricp_max_connections", "shutDownPool"), is(1.0)); + assertThat(getValue("hikaricp_min_connections", "shutDownPool"), is(1.0)); + } + finally { + StubConnection.slowCreate = false; + } + + assertNull(getValue("hikaricp_active_connections", "shutDownPool")); + assertNull(getValue("hikaricp_idle_connections", "shutDownPool")); + assertNull(getValue("hikaricp_pending_threads", "shutDownPool")); + assertNull(getValue("hikaricp_connections", "shutDownPool")); + assertNull(getValue("hikaricp_max_connections", "shutDownPool")); + assertNull(getValue("hikaricp_min_connections", "shutDownPool")); + } + + @Test + public void testHikariCPCollectorGaugesMetricsInitialization() + { + HikariCPCollector hikariCPCollector = new HikariCPCollector(); + hikariCPCollector.add("collectorTestPool", poolStatsWithPredefinedValues()); + List metrics = hikariCPCollector.collect(); + hikariCPCollector.register(collectorRegistry); + + assertThat(metrics.size(), is(6)); + assertThat(metrics.stream().filter(metricFamilySamples -> metricFamilySamples.type == Collector.Type.GAUGE).count(), is(6L)); + assertThat(getValue("hikaricp_active_connections", "collectorTestPool"), is(58.0)); + assertThat(getValue("hikaricp_idle_connections", "collectorTestPool"), is(42.0)); + assertThat(getValue("hikaricp_pending_threads", "collectorTestPool"), is(1.0)); + assertThat(getValue("hikaricp_connections", "collectorTestPool"), is(100.0)); + assertThat(getValue("hikaricp_max_connections", "collectorTestPool"), is(100.0)); + assertThat(getValue("hikaricp_min_connections", "collectorTestPool"), is(3.0)); + } + + private Double getValue(String name, String poolName) + { String[] labelNames = {"pool"}; String[] labelValues = {poolName}; return this.collectorRegistry.getSampleValue(name, labelNames, labelValues); } + private PoolStats poolStatsWithPredefinedValues() + { + return new PoolStats(0) { + @Override + protected void update() { + totalConnections = 100; + idleConnections = 42; + activeConnections = 58; + pendingThreads = 1; + maxConnections = 100; + minConnections = 3; + } + }; + } + } diff --git a/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactoryTest.java b/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactoryTest.java index 51a05fe4..eb838111 100644 --- a/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactoryTest.java +++ b/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactoryTest.java @@ -1,6 +1,7 @@ package com.zaxxer.hikari.metrics.prometheus; import com.zaxxer.hikari.metrics.PoolStats; +import com.zaxxer.hikari.mocks.StubPoolStats; import io.prometheus.client.Collector; import io.prometheus.client.CollectorRegistry; import org.junit.After; @@ -13,30 +14,35 @@ import java.util.List; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -public class PrometheusMetricsTrackerFactoryTest { +public class PrometheusMetricsTrackerFactoryTest +{ + + @After + public void clearCollectorRegistry() + { + CollectorRegistry.defaultRegistry.clear(); + } @Test - public void registersToProvidedCollectorRegistry() { + public void registersToProvidedCollectorRegistry() + { CollectorRegistry collectorRegistry = new CollectorRegistry(); PrometheusMetricsTrackerFactory factory = new PrometheusMetricsTrackerFactory(collectorRegistry); - factory.create("testpool-1", poolStats()); + factory.create("testpool-1", new StubPoolStats(0)); assertHikariMetricsAreNotPresent(CollectorRegistry.defaultRegistry); assertHikariMetricsArePresent(collectorRegistry); } @Test - public void registersToDefaultCollectorRegistry() { + public void registersToDefaultCollectorRegistry() + { PrometheusMetricsTrackerFactory factory = new PrometheusMetricsTrackerFactory(); - factory.create("testpool-2", poolStats()); + factory.create("testpool-2", new StubPoolStats(0)); assertHikariMetricsArePresent(CollectorRegistry.defaultRegistry); } - @After - public void clearCollectorRegistry(){ - CollectorRegistry.defaultRegistry.clear(); - } - - private void assertHikariMetricsArePresent(CollectorRegistry collectorRegistry) { + private void assertHikariMetricsArePresent(CollectorRegistry collectorRegistry) + { List registeredMetrics = toMetricNames(collectorRegistry.metricFamilySamples()); assertTrue(registeredMetrics.contains("hikaricp_active_connections")); assertTrue(registeredMetrics.contains("hikaricp_idle_connections")); @@ -46,7 +52,8 @@ public class PrometheusMetricsTrackerFactoryTest { assertTrue(registeredMetrics.contains("hikaricp_min_connections")); } - private void assertHikariMetricsAreNotPresent(CollectorRegistry collectorRegistry) { + private void assertHikariMetricsAreNotPresent(CollectorRegistry collectorRegistry) + { List registeredMetrics = toMetricNames(collectorRegistry.metricFamilySamples()); assertFalse(registeredMetrics.contains("hikaricp_active_connections")); assertFalse(registeredMetrics.contains("hikaricp_idle_connections")); @@ -56,21 +63,12 @@ public class PrometheusMetricsTrackerFactoryTest { assertFalse(registeredMetrics.contains("hikaricp_min_connections")); } - private List toMetricNames(Enumeration enumeration) { + private List toMetricNames(Enumeration enumeration) + { List list = new ArrayList<>(); while (enumeration.hasMoreElements()) { list.add(enumeration.nextElement().name); } return list; } - - private PoolStats poolStats() { - return new PoolStats(0) { - @Override - protected void update() { - // do nothing - } - }; - } - } diff --git a/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java b/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java index 12713eb7..677d33f3 100644 --- a/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java +++ b/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java @@ -12,12 +12,14 @@ * 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 com.zaxxer.hikari.metrics.prometheus; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.metrics.IMetricsTracker; +import com.zaxxer.hikari.mocks.StubPoolStats; import io.prometheus.client.CollectorRegistry; import org.junit.Before; import org.junit.Test; @@ -28,31 +30,37 @@ import java.sql.SQLTransientConnectionException; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; -public class PrometheusMetricsTrackerTest { +public class PrometheusMetricsTrackerTest +{ - private CollectorRegistry collectorRegistry; + private CollectorRegistry defaultCollectorRegistry; + private CollectorRegistry customCollectorRegistry; private static final String POOL_LABEL_NAME = "pool"; + private static final String[] LABEL_NAMES = {POOL_LABEL_NAME}; private static final String QUANTILE_LABEL_NAME = "quantile"; private static final String[] QUANTILE_LABEL_VALUES = new String[]{"0.5", "0.95", "0.99"}; @Before - public void setupCollectorRegistry(){ - this.collectorRegistry = new CollectorRegistry(); + public void setupCollectorRegistry() + { + this.defaultCollectorRegistry = new CollectorRegistry(); + this.customCollectorRegistry = new CollectorRegistry(); } @Test - public void recordConnectionTimeout() throws Exception { + public void recordConnectionTimeout() throws Exception + { HikariConfig config = newHikariConfig(); - config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(collectorRegistry)); + config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(defaultCollectorRegistry)); config.setJdbcUrl("jdbc:h2:mem:"); config.setMaximumPoolSize(2); config.setConnectionTimeout(250); - String[] labelNames = {POOL_LABEL_NAME}; String[] labelValues = {config.getPoolName()}; try (HikariDataSource hikariDataSource = new HikariDataSource(config)) { @@ -63,88 +71,229 @@ public class PrometheusMetricsTrackerTest { } } - Double total = collectorRegistry.getSampleValue( - "hikaricp_connection_timeout_total", - labelNames, - labelValues + Double total = defaultCollectorRegistry.getSampleValue( + "hikaricp_connection_timeout_total", LABEL_NAMES, labelValues ); assertThat(total, is(1.0)); } } @Test - public void connectionAcquisitionMetrics() { + public void connectionAcquisitionMetrics() + { checkSummaryMetricFamily("hikaricp_connection_acquired_nanos"); } @Test - public void connectionUsageMetrics() { + public void connectionUsageMetrics() + { checkSummaryMetricFamily("hikaricp_connection_usage_millis"); } @Test - public void connectionCreationMetrics() { + public void connectionCreationMetrics() + { checkSummaryMetricFamily("hikaricp_connection_creation_millis"); } @Test - public void testMultiplePoolName() throws Exception { - String[] labelNames = {POOL_LABEL_NAME}; + public void testMultiplePoolNameWithOneCollectorRegistry() + { + HikariConfig configFirstPool = newHikariConfig(); + configFirstPool.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(defaultCollectorRegistry)); + configFirstPool.setPoolName("first"); + configFirstPool.setJdbcUrl("jdbc:h2:mem:"); + configFirstPool.setMaximumPoolSize(2); + configFirstPool.setConnectionTimeout(250); - HikariConfig config = newHikariConfig(); - config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(collectorRegistry)); - config.setPoolName("first"); - config.setJdbcUrl("jdbc:h2:mem:"); - config.setMaximumPoolSize(2); - config.setConnectionTimeout(250); - String[] labelValues1 = {config.getPoolName()}; + HikariConfig configSecondPool = newHikariConfig(); + configSecondPool.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(defaultCollectorRegistry)); + configSecondPool.setPoolName("second"); + configSecondPool.setJdbcUrl("jdbc:h2:mem:"); + configSecondPool.setMaximumPoolSize(4); + configSecondPool.setConnectionTimeout(250); - try (HikariDataSource ignored = new HikariDataSource(config)) { - assertThat(collectorRegistry.getSampleValue( - "hikaricp_connection_timeout_total", - labelNames, - labelValues1), is(0.0)); - - CollectorRegistry collectorRegistry2 = new CollectorRegistry(); - HikariConfig config2 = newHikariConfig(); - config2.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(collectorRegistry2)); - config2.setPoolName("second"); - config2.setJdbcUrl("jdbc:h2:mem:"); - config2.setMaximumPoolSize(4); - config2.setConnectionTimeout(250); - String[] labelValues2 = {config2.getPoolName()}; - - try (HikariDataSource ignored2 = new HikariDataSource(config2)) { - assertThat(collectorRegistry2.getSampleValue( - "hikaricp_connection_timeout_total", - labelNames, - labelValues2), is(0.0)); + String[] labelValuesFirstPool = {configFirstPool.getPoolName()}; + String[] labelValuesSecondPool = {configSecondPool.getPoolName()}; + + try (HikariDataSource ignoredFirstPool = new HikariDataSource(configFirstPool)) { + assertThat(defaultCollectorRegistry.getSampleValue( + "hikaricp_connection_timeout_total", LABEL_NAMES, labelValuesFirstPool), + is(0.0)); + + try (HikariDataSource ignoredSecondPool = new HikariDataSource(configSecondPool)) { + assertThat(defaultCollectorRegistry.getSampleValue( + "hikaricp_connection_timeout_total", LABEL_NAMES, labelValuesSecondPool), + is(0.0)); + } + } + } + + @Test + public void testMultiplePoolNameWithDifferentCollectorRegistries() + { + HikariConfig configFirstPool = newHikariConfig(); + configFirstPool.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(defaultCollectorRegistry)); + configFirstPool.setPoolName("first"); + configFirstPool.setJdbcUrl("jdbc:h2:mem:"); + configFirstPool.setMaximumPoolSize(2); + configFirstPool.setConnectionTimeout(250); + + HikariConfig configSecondPool = newHikariConfig(); + configSecondPool.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(customCollectorRegistry)); + configSecondPool.setPoolName("second"); + configSecondPool.setJdbcUrl("jdbc:h2:mem:"); + configSecondPool.setMaximumPoolSize(4); + configSecondPool.setConnectionTimeout(250); + + String[] labelValuesFirstPool = {configFirstPool.getPoolName()}; + String[] labelValuesSecondPool = {configSecondPool.getPoolName()}; + + try (HikariDataSource ignoredFirstPool = new HikariDataSource(configFirstPool)) { + assertThat(defaultCollectorRegistry.getSampleValue( + "hikaricp_connection_timeout_total", LABEL_NAMES, labelValuesFirstPool), + is(0.0)); + + try (HikariDataSource ignoredSecondPool = new HikariDataSource(configSecondPool)) { + assertThat(customCollectorRegistry.getSampleValue( + "hikaricp_connection_timeout_total", LABEL_NAMES, labelValuesSecondPool), + is(0.0)); + } + } + } + + @Test + public void testMetricsRemovedAfterShutDown() + { + HikariConfig configFirstPool = newHikariConfig(); + configFirstPool.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(defaultCollectorRegistry)); + configFirstPool.setPoolName("first"); + configFirstPool.setJdbcUrl("jdbc:h2:mem:"); + configFirstPool.setMaximumPoolSize(2); + configFirstPool.setConnectionTimeout(250); + + HikariConfig configSecondPool = newHikariConfig(); + configSecondPool.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(customCollectorRegistry)); + configSecondPool.setPoolName("second"); + configSecondPool.setJdbcUrl("jdbc:h2:mem:"); + configSecondPool.setMaximumPoolSize(4); + configSecondPool.setConnectionTimeout(250); + + String[] labelValuesFirstPool = {configFirstPool.getPoolName()}; + String[] labelValuesSecondPool = {configSecondPool.getPoolName()}; + + try (HikariDataSource ignoredFirstPool = new HikariDataSource(configFirstPool)) { + assertThat(defaultCollectorRegistry.getSampleValue( + "hikaricp_connection_timeout_total", LABEL_NAMES, labelValuesFirstPool), + is(0.0)); + + try (HikariDataSource ignoredSecondPool = new HikariDataSource(configSecondPool)) { + assertThat(customCollectorRegistry.getSampleValue( + "hikaricp_connection_timeout_total", LABEL_NAMES, labelValuesSecondPool), + is(0.0)); } + + assertNull(defaultCollectorRegistry.getSampleValue( + "hikaricp_connection_timeout_total", LABEL_NAMES, labelValuesSecondPool)); + + assertThat(defaultCollectorRegistry.getSampleValue( + "hikaricp_connection_timeout_total", LABEL_NAMES, labelValuesFirstPool), + is(0.0)); } } - private void checkSummaryMetricFamily(String metricName) { + @Test + public void testCloseMethod() + { + String[] labelValues = {"testPool"}; + PrometheusMetricsTrackerFactory prometheusFactory = new PrometheusMetricsTrackerFactory(defaultCollectorRegistry); + IMetricsTracker prometheusTracker = prometheusFactory.create("testPool", new StubPoolStats(0)); + + prometheusTracker.recordConnectionTimeout(); + prometheusTracker.recordConnectionAcquiredNanos(42L); + prometheusTracker.recordConnectionUsageMillis(111L); + prometheusTracker.recordConnectionCreatedMillis(101L); + + assertThat(defaultCollectorRegistry.getSampleValue( + "hikaricp_connection_timeout_total", LABEL_NAMES, labelValues), + is(1.0)); + assertThat(defaultCollectorRegistry.getSampleValue( + "hikaricp_connection_acquired_nanos_sum", LABEL_NAMES, labelValues), + is(42.0)); + assertThat(defaultCollectorRegistry.getSampleValue( + "hikaricp_connection_usage_millis_sum", LABEL_NAMES, labelValues), + is(111.0)); + assertThat(defaultCollectorRegistry.getSampleValue( + "hikaricp_connection_creation_millis_sum", LABEL_NAMES, labelValues), + is(101.0)); + assertThat(defaultCollectorRegistry.getSampleValue( + "hikaricp_active_connections", LABEL_NAMES, labelValues), + is(0.0)); + assertThat(defaultCollectorRegistry.getSampleValue( + "hikaricp_idle_connections", LABEL_NAMES, labelValues), + is(0.0)); + assertThat(defaultCollectorRegistry.getSampleValue( + "hikaricp_pending_threads", LABEL_NAMES, labelValues), + is(0.0)); + assertThat(defaultCollectorRegistry.getSampleValue( + "hikaricp_connections", LABEL_NAMES, labelValues), + is(0.0)); + assertThat(defaultCollectorRegistry.getSampleValue( + "hikaricp_max_connections", LABEL_NAMES, labelValues), + is(0.0)); + assertThat(defaultCollectorRegistry.getSampleValue( + "hikaricp_min_connections", LABEL_NAMES, labelValues), + is(0.0)); + + prometheusTracker.close(); + + assertNull(defaultCollectorRegistry.getSampleValue( + "hikaricp_connection_timeout_total", LABEL_NAMES, labelValues)); + assertNull(defaultCollectorRegistry.getSampleValue( + "hikaricp_connection_acquired_nanos_sum", LABEL_NAMES, labelValues)); + assertNull(defaultCollectorRegistry.getSampleValue( + "hikaricp_connection_usage_millis_sum", LABEL_NAMES, labelValues)); + assertNull(defaultCollectorRegistry.getSampleValue( + "hikaricp_connection_creation_millis_sum", LABEL_NAMES, labelValues)); + assertNull(defaultCollectorRegistry.getSampleValue( + "hikaricp_active_connections", LABEL_NAMES, labelValues)); + assertNull(defaultCollectorRegistry.getSampleValue( + "hikaricp_idle_connections", LABEL_NAMES, labelValues)); + assertNull(defaultCollectorRegistry.getSampleValue( + "hikaricp_pending_threads", LABEL_NAMES, labelValues)); + assertNull(defaultCollectorRegistry.getSampleValue( + "hikaricp_connections", LABEL_NAMES, labelValues)); + assertNull(defaultCollectorRegistry.getSampleValue( + "hikaricp_connections", LABEL_NAMES, labelValues)); + assertNull(defaultCollectorRegistry.getSampleValue( + "hikaricp_max_connections", LABEL_NAMES, labelValues)); + assertNull(defaultCollectorRegistry.getSampleValue( + "hikaricp_min_connections", LABEL_NAMES, labelValues)); + } + + private void checkSummaryMetricFamily(String metricName) + { HikariConfig config = newHikariConfig(); - config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(collectorRegistry)); + config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(defaultCollectorRegistry)); config.setJdbcUrl("jdbc:h2:mem:"); try (HikariDataSource ignored = new HikariDataSource(config)) { - Double count = collectorRegistry.getSampleValue( + Double count = defaultCollectorRegistry.getSampleValue( metricName + "_count", - new String[]{POOL_LABEL_NAME}, + LABEL_NAMES, new String[]{config.getPoolName()} ); assertNotNull(count); - Double sum = collectorRegistry.getSampleValue( + Double sum = defaultCollectorRegistry.getSampleValue( metricName + "_sum", - new String[]{POOL_LABEL_NAME}, + LABEL_NAMES, new String[]{config.getPoolName()} ); assertNotNull(sum); for (String quantileLabelValue : QUANTILE_LABEL_VALUES) { - Double quantileValue = collectorRegistry.getSampleValue( + Double quantileValue = defaultCollectorRegistry.getSampleValue( metricName, new String[]{POOL_LABEL_NAME, QUANTILE_LABEL_NAME}, new String[]{config.getPoolName(), quantileLabelValue} diff --git a/src/test/java/com/zaxxer/hikari/mocks/StubPoolStats.java b/src/test/java/com/zaxxer/hikari/mocks/StubPoolStats.java new file mode 100644 index 00000000..356b24ee --- /dev/null +++ b/src/test/java/com/zaxxer/hikari/mocks/StubPoolStats.java @@ -0,0 +1,20 @@ +package com.zaxxer.hikari.mocks; + +import com.zaxxer.hikari.metrics.PoolStats; + +public class StubPoolStats extends PoolStats +{ + + public StubPoolStats(long timeoutMs) + { + super(timeoutMs); + } + + @Override + protected void update() + { + // Do nothing + } + + +}