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
pull/1401/head
Aleksandr Podkutin 6 years ago committed by Brett Wooldridge
parent c509ec1a3f
commit 086bf18389

@ -5,7 +5,8 @@ import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
import com.zaxxer.hikari.metrics.PoolStats; import com.zaxxer.hikari.metrics.PoolStats;
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MeterRegistry;
public class MicrometerMetricsTrackerFactory implements MetricsTrackerFactory { public class MicrometerMetricsTrackerFactory implements MetricsTrackerFactory
{
private final MeterRegistry registry; private final MeterRegistry registry;

@ -12,13 +12,14 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.zaxxer.hikari.metrics.prometheus; package com.zaxxer.hikari.metrics.prometheus;
import com.zaxxer.hikari.metrics.PoolStats; import com.zaxxer.hikari.metrics.PoolStats;
import io.prometheus.client.Collector; import io.prometheus.client.Collector;
import io.prometheus.client.GaugeMetricFamily; import io.prometheus.client.GaugeMetricFamily;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -26,14 +27,16 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function; import java.util.function.Function;
class HikariCPCollector extends Collector { class HikariCPCollector extends Collector
{
private static final List<String> LABEL_NAMES = Collections.singletonList("pool"); private static final List<String> LABEL_NAMES = Collections.singletonList("pool");
private final Map<String, PoolStats> poolStatsMap = new ConcurrentHashMap<>(); private final Map<String, PoolStats> poolStatsMap = new ConcurrentHashMap<>();
@Override @Override
public List<MetricFamilySamples> collect() { public List<MetricFamilySamples> collect()
{
return Arrays.asList( return Arrays.asList(
createGauge("hikaricp_active_connections", "Active connections", createGauge("hikaricp_active_connections", "Active connections",
PoolStats::getActiveConnections), 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); poolStatsMap.put(name, poolStats);
return this; }
void remove(String name)
{
poolStatsMap.remove(name);
} }
private GaugeMetricFamily createGauge(String metric, String help, private GaugeMetricFamily createGauge(String metric, String help,
Function<PoolStats, Integer> metricValueFunction) { Function<PoolStats, Integer> metricValueFunction)
{
GaugeMetricFamily metricFamily = new GaugeMetricFamily(metric, help, LABEL_NAMES); GaugeMetricFamily metricFamily = new GaugeMetricFamily(metric, help, LABEL_NAMES);
poolStatsMap.forEach((k, v) -> metricFamily.addMetric( poolStatsMap.forEach((k, v) -> metricFamily.addMetric(
Collections.singletonList(k), Collections.singletonList(k),

@ -12,66 +12,69 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.zaxxer.hikari.metrics.prometheus; package com.zaxxer.hikari.metrics.prometheus;
import com.zaxxer.hikari.metrics.IMetricsTracker; import com.zaxxer.hikari.metrics.IMetricsTracker;
import com.zaxxer.hikari.metrics.prometheus.PrometheusMetricsTrackerFactory.RegistrationStatus;
import io.prometheus.client.CollectorRegistry; import io.prometheus.client.CollectorRegistry;
import io.prometheus.client.Counter; import io.prometheus.client.Counter;
import io.prometheus.client.Summary; import io.prometheus.client.Summary;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static com.zaxxer.hikari.metrics.prometheus.PrometheusMetricsTrackerFactory.RegistrationStatus.REGISTERED;
class PrometheusMetricsTracker implements IMetricsTracker 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") .name("hikaricp_connection_timeout_total")
.labelNames("pool") .labelNames("pool")
.help("Connection timeout total count") .help("Connection timeout total count")
.create(); .create();
private final Summary ELAPSED_ACQUIRED_SUMMARY = private final static Summary ELAPSED_ACQUIRED_SUMMARY =
registerSummary("hikaricp_connection_acquired_nanos", "Connection acquired time (ns)"); createSummary("hikaricp_connection_acquired_nanos", "Connection acquired time (ns)");
private final Summary ELAPSED_BORROWED_SUMMARY = private final static Summary ELAPSED_USAGE_SUMMARY =
registerSummary("hikaricp_connection_usage_millis", "Connection usage (ms)"); createSummary("hikaricp_connection_usage_millis", "Connection usage (ms)");
private final Summary ELAPSED_CREATION_SUMMARY = private final static Summary ELAPSED_CREATION_SUMMARY =
registerSummary("hikaricp_connection_creation_millis", "Connection creation (ms)"); createSummary("hikaricp_connection_creation_millis", "Connection creation (ms)");
private final Counter.Child connectionTimeoutCounterChild; private final static Map<CollectorRegistry, RegistrationStatus> registrationStatuses = new ConcurrentHashMap<>();
private Summary registerSummary(String name, String help) { private final String poolName;
return Summary.build() private final HikariCPCollector hikariCPCollector;
.name(name)
.labelNames("pool") private final Counter.Child connectionTimeoutCounterChild;
.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 Summary.Child elapsedAcquiredSummaryChild; private final Summary.Child elapsedAcquiredSummaryChild;
private final Summary.Child elapsedBorrowedSummaryChild; private final Summary.Child elapsedUsageSummaryChild;
private final Summary.Child elapsedCreationSummaryChild; private final Summary.Child elapsedCreationSummaryChild;
PrometheusMetricsTracker(String poolName, CollectorRegistry collectorRegistry) { PrometheusMetricsTracker(String poolName, CollectorRegistry collectorRegistry, HikariCPCollector hikariCPCollector)
{
registerMetrics(collectorRegistry); registerMetrics(collectorRegistry);
this.poolName = poolName;
this.hikariCPCollector = hikariCPCollector;
this.connectionTimeoutCounterChild = CONNECTION_TIMEOUT_COUNTER.labels(poolName); this.connectionTimeoutCounterChild = CONNECTION_TIMEOUT_COUNTER.labels(poolName);
this.elapsedAcquiredSummaryChild = ELAPSED_ACQUIRED_SUMMARY.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); this.elapsedCreationSummaryChild = ELAPSED_CREATION_SUMMARY.labels(poolName);
} }
private void registerMetrics(CollectorRegistry collectorRegistry){ private void registerMetrics(CollectorRegistry collectorRegistry)
CONNECTION_TIMEOUT_COUNTER.register(collectorRegistry); {
ELAPSED_ACQUIRED_SUMMARY.register(collectorRegistry); if (registrationStatuses.putIfAbsent(collectorRegistry, REGISTERED) == null) {
ELAPSED_BORROWED_SUMMARY.register(collectorRegistry); CONNECTION_TIMEOUT_COUNTER.register(collectorRegistry);
ELAPSED_CREATION_SUMMARY.register(collectorRegistry); ELAPSED_ACQUIRED_SUMMARY.register(collectorRegistry);
ELAPSED_USAGE_SUMMARY.register(collectorRegistry);
ELAPSED_CREATION_SUMMARY.register(collectorRegistry);
}
} }
@Override @Override
@ -83,7 +86,7 @@ class PrometheusMetricsTracker implements IMetricsTracker
@Override @Override
public void recordConnectionUsageMillis(long elapsedBorrowedMillis) public void recordConnectionUsageMillis(long elapsedBorrowedMillis)
{ {
elapsedBorrowedSummaryChild.observe(elapsedBorrowedMillis); elapsedUsageSummaryChild.observe(elapsedBorrowedMillis);
} }
@Override @Override
@ -97,4 +100,28 @@ class PrometheusMetricsTracker implements IMetricsTracker
{ {
connectionTimeoutCounterChild.inc(); 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);
}
} }

@ -12,59 +12,78 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.zaxxer.hikari.metrics.prometheus; package com.zaxxer.hikari.metrics.prometheus;
import com.zaxxer.hikari.metrics.IMetricsTracker; import com.zaxxer.hikari.metrics.IMetricsTracker;
import com.zaxxer.hikari.metrics.MetricsTrackerFactory; import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
import com.zaxxer.hikari.metrics.PoolStats; import com.zaxxer.hikari.metrics.PoolStats;
import io.prometheus.client.Collector;
import io.prometheus.client.CollectorRegistry; import io.prometheus.client.CollectorRegistry;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static com.zaxxer.hikari.metrics.prometheus.PrometheusMetricsTrackerFactory.RegistrationStatus.REGISTERED;
/** /**
* <pre>{@code * <pre>{@code
* HikariConfig config = new HikariConfig(); * HikariConfig config = new HikariConfig();
* config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory()); * config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory());
* }</pre> * }</pre>
* or
* <pre>{@code
* config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(new CollectorRegistry()));
* }</pre>
* *
* Note: the internal {@see io.prometheus.client.Summary} requires heavy locks. Consider using * 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. * {@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<CollectorRegistry, RegistrationStatus> 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 * Default Constructor. The Hikari metrics are registered to the default
* collector registry ({@code CollectorRegistry.defaultRegistry}). * collector registry ({@code CollectorRegistry.defaultRegistry}).
*/ */
public PrometheusMetricsTrackerFactory() { public PrometheusMetricsTrackerFactory()
this.collectorRegistry = CollectorRegistry.defaultRegistry; {
this(CollectorRegistry.defaultRegistry);
} }
/** /**
* Constructor that allows to pass in a {@link CollectorRegistry} to which the * Constructor that allows to pass in a {@link CollectorRegistry} to which the
* Hikari metrics are registered. * Hikari metrics are registered.
*/ */
public PrometheusMetricsTrackerFactory(CollectorRegistry collectorRegistry) { public PrometheusMetricsTrackerFactory(CollectorRegistry collectorRegistry)
{
this.collectorRegistry = collectorRegistry; this.collectorRegistry = collectorRegistry;
} }
@Override @Override
public IMetricsTracker create(String poolName, PoolStats poolStats) { public IMetricsTracker create(String poolName, PoolStats poolStats)
getCollector().add(poolName, poolStats); {
return new PrometheusMetricsTracker(poolName, this.collectorRegistry); registerCollector(this.collector, this.collectorRegistry);
this.collector.add(poolName, poolStats);
return new PrometheusMetricsTracker(poolName, this.collectorRegistry, this.collector);
} }
/** private void registerCollector(Collector collector, CollectorRegistry collectorRegistry)
* initialize and register collector if it isn't initialized yet {
*/ if (registrationStatuses.putIfAbsent(collectorRegistry, REGISTERED) == null) {
private HikariCPCollector getCollector() { collector.register(collectorRegistry);
if (collector == null) {
collector = new HikariCPCollector().register(this.collectorRegistry);
} }
return collector;
} }
} }

@ -1,18 +1,18 @@
package com.zaxxer.hikari.metrics.dropwizard; package com.zaxxer.hikari.metrics.dropwizard;
import static org.mockito.Mockito.verify; import com.codahale.metrics.MetricRegistry;
import com.zaxxer.hikari.mocks.StubPoolStats;
import com.zaxxer.hikari.metrics.PoolStats;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import com.codahale.metrics.MetricRegistry; import static org.mockito.Mockito.verify;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class CodaHaleMetricsTrackerTest { public class CodaHaleMetricsTrackerTest
{
@Mock @Mock
public MetricRegistry mockMetricRegistry; public MetricRegistry mockMetricRegistry;
@ -20,12 +20,14 @@ public class CodaHaleMetricsTrackerTest {
private CodaHaleMetricsTracker testee; private CodaHaleMetricsTracker testee;
@Before @Before
public void setup() { public void setup()
testee = new CodaHaleMetricsTracker("mypool", poolStats(), mockMetricRegistry); {
testee = new CodaHaleMetricsTracker("mypool", new StubPoolStats(0), mockMetricRegistry);
} }
@Test @Test
public void close() throws Exception { public void close()
{
testee.close(); testee.close();
verify(mockMetricRegistry).remove("mypool.pool.Wait"); verify(mockMetricRegistry).remove("mypool.pool.Wait");
@ -39,13 +41,4 @@ public class CodaHaleMetricsTrackerTest {
verify(mockMetricRegistry).remove("mypool.pool.MaxConnections"); verify(mockMetricRegistry).remove("mypool.pool.MaxConnections");
verify(mockMetricRegistry).remove("mypool.pool.MinConnections"); verify(mockMetricRegistry).remove("mypool.pool.MinConnections");
} }
private PoolStats poolStats() {
return new PoolStats(0) {
@Override
protected void update() {
// do nothing
}
};
}
} }

@ -1,30 +1,28 @@
package com.zaxxer.hikari.metrics.micrometer; 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.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
public class MicrometerMetricsTrackerTest { public class MicrometerMetricsTrackerTest
{
private MeterRegistry mockMeterRegistry = new SimpleMeterRegistry(); private MeterRegistry mockMeterRegistry = new SimpleMeterRegistry();
private MicrometerMetricsTracker testee; private MicrometerMetricsTracker testee;
@Before @Before
public void setup(){ public void setup()
testee = new MicrometerMetricsTracker("mypool", new PoolStats(1000L) { {
@Override testee = new MicrometerMetricsTracker("mypool", new StubPoolStats(1000L), mockMeterRegistry);
protected void update() {
// nothing
}
}, mockMeterRegistry);
} }
@Test @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.acquire").tag("pool", "mypool").timer());
Assert.assertNotNull(mockMeterRegistry.find("hikaricp.connections.usage").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()); Assert.assertNotNull(mockMeterRegistry.find("hikaricp.connections.creation").tag("pool", "mypool").timer());

@ -19,10 +19,14 @@ package com.zaxxer.hikari.metrics.prometheus;
import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
import static com.zaxxer.hikari.util.UtilityElf.quietlySleep; import static com.zaxxer.hikari.util.UtilityElf.quietlySleep;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import java.sql.Connection; 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.Before;
import org.junit.Test; import org.junit.Test;
@ -32,18 +36,20 @@ import com.zaxxer.hikari.mocks.StubConnection;
import io.prometheus.client.CollectorRegistry; import io.prometheus.client.CollectorRegistry;
public class HikariCPCollectorTest { public class HikariCPCollectorTest
{
private CollectorRegistry collectorRegistry; private CollectorRegistry collectorRegistry;
@Before @Before
public void setupCollectorRegistry(){ public void setupCollectorRegistry()
{
this.collectorRegistry = new CollectorRegistry(); this.collectorRegistry = new CollectorRegistry();
} }
@Test @Test
public void noConnection() throws Exception { public void noConnection()
{
HikariConfig config = newHikariConfig(); HikariConfig config = newHikariConfig();
config.setMinimumIdle(0); config.setMinimumIdle(0);
config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(this.collectorRegistry)); config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(this.collectorRegistry));
@ -64,7 +70,8 @@ public class HikariCPCollectorTest {
} }
@Test @Test
public void noConnectionWithoutPoolName() throws Exception { public void noConnectionWithoutPoolName()
{
HikariConfig config = new HikariConfig(); HikariConfig config = new HikariConfig();
config.setMinimumIdle(0); config.setMinimumIdle(0);
config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(this.collectorRegistry)); config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(this.collectorRegistry));
@ -86,7 +93,8 @@ public class HikariCPCollectorTest {
} }
@Test @Test
public void connection1() throws Exception { public void connection1() throws Exception
{
HikariConfig config = newHikariConfig(); HikariConfig config = newHikariConfig();
config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(this.collectorRegistry)); config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(this.collectorRegistry));
config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
@ -111,7 +119,8 @@ public class HikariCPCollectorTest {
} }
@Test @Test
public void connectionClosed() throws Exception { public void connectionClosed() throws Exception
{
HikariConfig config = newHikariConfig(); HikariConfig config = newHikariConfig();
config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(this.collectorRegistry)); config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(this.collectorRegistry));
config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); 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<Collector.MetricFamilySamples> 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[] labelNames = {"pool"};
String[] labelValues = {poolName}; String[] labelValues = {poolName};
return this.collectorRegistry.getSampleValue(name, labelNames, labelValues); 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;
}
};
}
} }

@ -1,6 +1,7 @@
package com.zaxxer.hikari.metrics.prometheus; package com.zaxxer.hikari.metrics.prometheus;
import com.zaxxer.hikari.metrics.PoolStats; import com.zaxxer.hikari.metrics.PoolStats;
import com.zaxxer.hikari.mocks.StubPoolStats;
import io.prometheus.client.Collector; import io.prometheus.client.Collector;
import io.prometheus.client.CollectorRegistry; import io.prometheus.client.CollectorRegistry;
import org.junit.After; import org.junit.After;
@ -13,30 +14,35 @@ import java.util.List;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
public class PrometheusMetricsTrackerFactoryTest { public class PrometheusMetricsTrackerFactoryTest
{
@After
public void clearCollectorRegistry()
{
CollectorRegistry.defaultRegistry.clear();
}
@Test @Test
public void registersToProvidedCollectorRegistry() { public void registersToProvidedCollectorRegistry()
{
CollectorRegistry collectorRegistry = new CollectorRegistry(); CollectorRegistry collectorRegistry = new CollectorRegistry();
PrometheusMetricsTrackerFactory factory = new PrometheusMetricsTrackerFactory(collectorRegistry); PrometheusMetricsTrackerFactory factory = new PrometheusMetricsTrackerFactory(collectorRegistry);
factory.create("testpool-1", poolStats()); factory.create("testpool-1", new StubPoolStats(0));
assertHikariMetricsAreNotPresent(CollectorRegistry.defaultRegistry); assertHikariMetricsAreNotPresent(CollectorRegistry.defaultRegistry);
assertHikariMetricsArePresent(collectorRegistry); assertHikariMetricsArePresent(collectorRegistry);
} }
@Test @Test
public void registersToDefaultCollectorRegistry() { public void registersToDefaultCollectorRegistry()
{
PrometheusMetricsTrackerFactory factory = new PrometheusMetricsTrackerFactory(); PrometheusMetricsTrackerFactory factory = new PrometheusMetricsTrackerFactory();
factory.create("testpool-2", poolStats()); factory.create("testpool-2", new StubPoolStats(0));
assertHikariMetricsArePresent(CollectorRegistry.defaultRegistry); assertHikariMetricsArePresent(CollectorRegistry.defaultRegistry);
} }
@After private void assertHikariMetricsArePresent(CollectorRegistry collectorRegistry)
public void clearCollectorRegistry(){ {
CollectorRegistry.defaultRegistry.clear();
}
private void assertHikariMetricsArePresent(CollectorRegistry collectorRegistry) {
List<String> registeredMetrics = toMetricNames(collectorRegistry.metricFamilySamples()); List<String> registeredMetrics = toMetricNames(collectorRegistry.metricFamilySamples());
assertTrue(registeredMetrics.contains("hikaricp_active_connections")); assertTrue(registeredMetrics.contains("hikaricp_active_connections"));
assertTrue(registeredMetrics.contains("hikaricp_idle_connections")); assertTrue(registeredMetrics.contains("hikaricp_idle_connections"));
@ -46,7 +52,8 @@ public class PrometheusMetricsTrackerFactoryTest {
assertTrue(registeredMetrics.contains("hikaricp_min_connections")); assertTrue(registeredMetrics.contains("hikaricp_min_connections"));
} }
private void assertHikariMetricsAreNotPresent(CollectorRegistry collectorRegistry) { private void assertHikariMetricsAreNotPresent(CollectorRegistry collectorRegistry)
{
List<String> registeredMetrics = toMetricNames(collectorRegistry.metricFamilySamples()); List<String> registeredMetrics = toMetricNames(collectorRegistry.metricFamilySamples());
assertFalse(registeredMetrics.contains("hikaricp_active_connections")); assertFalse(registeredMetrics.contains("hikaricp_active_connections"));
assertFalse(registeredMetrics.contains("hikaricp_idle_connections")); assertFalse(registeredMetrics.contains("hikaricp_idle_connections"));
@ -56,21 +63,12 @@ public class PrometheusMetricsTrackerFactoryTest {
assertFalse(registeredMetrics.contains("hikaricp_min_connections")); assertFalse(registeredMetrics.contains("hikaricp_min_connections"));
} }
private List<String> toMetricNames(Enumeration<Collector.MetricFamilySamples> enumeration) { private List<String> toMetricNames(Enumeration<Collector.MetricFamilySamples> enumeration)
{
List<String> list = new ArrayList<>(); List<String> list = new ArrayList<>();
while (enumeration.hasMoreElements()) { while (enumeration.hasMoreElements()) {
list.add(enumeration.nextElement().name); list.add(enumeration.nextElement().name);
} }
return list; return list;
} }
private PoolStats poolStats() {
return new PoolStats(0) {
@Override
protected void update() {
// do nothing
}
};
}
} }

@ -12,12 +12,14 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.zaxxer.hikari.metrics.prometheus; package com.zaxxer.hikari.metrics.prometheus;
import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.metrics.IMetricsTracker;
import com.zaxxer.hikari.mocks.StubPoolStats;
import io.prometheus.client.CollectorRegistry; import io.prometheus.client.CollectorRegistry;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -28,31 +30,37 @@ import java.sql.SQLTransientConnectionException;
import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat; 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 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_NAME = "quantile";
private static final String[] QUANTILE_LABEL_VALUES = new String[]{"0.5", "0.95", "0.99"}; private static final String[] QUANTILE_LABEL_VALUES = new String[]{"0.5", "0.95", "0.99"};
@Before @Before
public void setupCollectorRegistry(){ public void setupCollectorRegistry()
this.collectorRegistry = new CollectorRegistry(); {
this.defaultCollectorRegistry = new CollectorRegistry();
this.customCollectorRegistry = new CollectorRegistry();
} }
@Test @Test
public void recordConnectionTimeout() throws Exception { public void recordConnectionTimeout() throws Exception
{
HikariConfig config = newHikariConfig(); HikariConfig config = newHikariConfig();
config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(collectorRegistry)); config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(defaultCollectorRegistry));
config.setJdbcUrl("jdbc:h2:mem:"); config.setJdbcUrl("jdbc:h2:mem:");
config.setMaximumPoolSize(2); config.setMaximumPoolSize(2);
config.setConnectionTimeout(250); config.setConnectionTimeout(250);
String[] labelNames = {POOL_LABEL_NAME};
String[] labelValues = {config.getPoolName()}; String[] labelValues = {config.getPoolName()};
try (HikariDataSource hikariDataSource = new HikariDataSource(config)) { try (HikariDataSource hikariDataSource = new HikariDataSource(config)) {
@ -63,88 +71,229 @@ public class PrometheusMetricsTrackerTest {
} }
} }
Double total = collectorRegistry.getSampleValue( Double total = defaultCollectorRegistry.getSampleValue(
"hikaricp_connection_timeout_total", "hikaricp_connection_timeout_total", LABEL_NAMES, labelValues
labelNames,
labelValues
); );
assertThat(total, is(1.0)); assertThat(total, is(1.0));
} }
} }
@Test @Test
public void connectionAcquisitionMetrics() { public void connectionAcquisitionMetrics()
{
checkSummaryMetricFamily("hikaricp_connection_acquired_nanos"); checkSummaryMetricFamily("hikaricp_connection_acquired_nanos");
} }
@Test @Test
public void connectionUsageMetrics() { public void connectionUsageMetrics()
{
checkSummaryMetricFamily("hikaricp_connection_usage_millis"); checkSummaryMetricFamily("hikaricp_connection_usage_millis");
} }
@Test @Test
public void connectionCreationMetrics() { public void connectionCreationMetrics()
{
checkSummaryMetricFamily("hikaricp_connection_creation_millis"); checkSummaryMetricFamily("hikaricp_connection_creation_millis");
} }
@Test @Test
public void testMultiplePoolName() throws Exception { public void testMultiplePoolNameWithOneCollectorRegistry()
String[] labelNames = {POOL_LABEL_NAME}; {
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(); HikariConfig configSecondPool = newHikariConfig();
config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(collectorRegistry)); configSecondPool.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(defaultCollectorRegistry));
config.setPoolName("first"); configSecondPool.setPoolName("second");
config.setJdbcUrl("jdbc:h2:mem:"); configSecondPool.setJdbcUrl("jdbc:h2:mem:");
config.setMaximumPoolSize(2); configSecondPool.setMaximumPoolSize(4);
config.setConnectionTimeout(250); configSecondPool.setConnectionTimeout(250);
String[] labelValues1 = {config.getPoolName()};
try (HikariDataSource ignored = new HikariDataSource(config)) { String[] labelValuesFirstPool = {configFirstPool.getPoolName()};
assertThat(collectorRegistry.getSampleValue( String[] labelValuesSecondPool = {configSecondPool.getPoolName()};
"hikaricp_connection_timeout_total",
labelNames, try (HikariDataSource ignoredFirstPool = new HikariDataSource(configFirstPool)) {
labelValues1), is(0.0)); assertThat(defaultCollectorRegistry.getSampleValue(
"hikaricp_connection_timeout_total", LABEL_NAMES, labelValuesFirstPool),
CollectorRegistry collectorRegistry2 = new CollectorRegistry(); is(0.0));
HikariConfig config2 = newHikariConfig();
config2.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(collectorRegistry2)); try (HikariDataSource ignoredSecondPool = new HikariDataSource(configSecondPool)) {
config2.setPoolName("second"); assertThat(defaultCollectorRegistry.getSampleValue(
config2.setJdbcUrl("jdbc:h2:mem:"); "hikaricp_connection_timeout_total", LABEL_NAMES, labelValuesSecondPool),
config2.setMaximumPoolSize(4); is(0.0));
config2.setConnectionTimeout(250); }
String[] labelValues2 = {config2.getPoolName()}; }
}
try (HikariDataSource ignored2 = new HikariDataSource(config2)) {
assertThat(collectorRegistry2.getSampleValue( @Test
"hikaricp_connection_timeout_total", public void testMultiplePoolNameWithDifferentCollectorRegistries()
labelNames, {
labelValues2), is(0.0)); 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(); HikariConfig config = newHikariConfig();
config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(collectorRegistry)); config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(defaultCollectorRegistry));
config.setJdbcUrl("jdbc:h2:mem:"); config.setJdbcUrl("jdbc:h2:mem:");
try (HikariDataSource ignored = new HikariDataSource(config)) { try (HikariDataSource ignored = new HikariDataSource(config)) {
Double count = collectorRegistry.getSampleValue( Double count = defaultCollectorRegistry.getSampleValue(
metricName + "_count", metricName + "_count",
new String[]{POOL_LABEL_NAME}, LABEL_NAMES,
new String[]{config.getPoolName()} new String[]{config.getPoolName()}
); );
assertNotNull(count); assertNotNull(count);
Double sum = collectorRegistry.getSampleValue( Double sum = defaultCollectorRegistry.getSampleValue(
metricName + "_sum", metricName + "_sum",
new String[]{POOL_LABEL_NAME}, LABEL_NAMES,
new String[]{config.getPoolName()} new String[]{config.getPoolName()}
); );
assertNotNull(sum); assertNotNull(sum);
for (String quantileLabelValue : QUANTILE_LABEL_VALUES) { for (String quantileLabelValue : QUANTILE_LABEL_VALUES) {
Double quantileValue = collectorRegistry.getSampleValue( Double quantileValue = defaultCollectorRegistry.getSampleValue(
metricName, metricName,
new String[]{POOL_LABEL_NAME, QUANTILE_LABEL_NAME}, new String[]{POOL_LABEL_NAME, QUANTILE_LABEL_NAME},
new String[]{config.getPoolName(), quantileLabelValue} new String[]{config.getPoolName(), quantileLabelValue}

@ -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
}
}
Loading…
Cancel
Save