#241 add Dropwizard HealthCheck support. Still needs documentation.

pull/253/head
Brett Wooldridge 10 years ago
parent 778ff0650f
commit 66b64153e1

@ -13,14 +13,14 @@
<parent>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP-parent</artifactId>
<version>2.3.0-SNAPSHOT</version>
<version>2.3.2-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.2.6-SNAPSHOT</version>
<version>2.3.2-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

@ -81,6 +81,8 @@ public abstract class AbstractHikariConfig implements HikariConfigMBean
private IConnectionCustomizer customizer;
private ThreadFactory threadFactory;
private Object metricRegistry;
private Object healthCheckRegistry;
private Properties healthCheckProperties;
/**
* Default constructor
@ -88,6 +90,7 @@ public abstract class AbstractHikariConfig implements HikariConfigMBean
public AbstractHikariConfig()
{
dataSourceProperties = new Properties();
healthCheckProperties = new Properties();
connectionTimeout = CONNECTION_TIMEOUT;
validationTimeout = VALIDATION_TIMEOUT;
@ -499,6 +502,44 @@ public abstract class AbstractHikariConfig implements HikariConfigMBean
this.metricRegistry = metricRegistry;
}
/**
* Get the Codahale HealthCheckRegistry, could be null.
*
* @return the Codahale HealthCheckRegistry instance
*/
public Object getHealthCheckRegistry()
{
return healthCheckRegistry;
}
/**
* Set a Codahale HealthCheckRegistry to use for HikariCP.
*
* @param healthCheckRegistry the Codahale HealthCheckRegistry to set
*/
public void setHealthCheckRegistry(Object healthCheckRegistry)
{
if (healthCheckRegistry != null && !healthCheckRegistry.getClass().getName().contains("HealthCheckRegistry")) {
throw new IllegalArgumentException("Class must be an instance of com.codahale.metrics.health.HealthCheckRegistry");
}
this.healthCheckRegistry = healthCheckRegistry;
}
public Properties getHealthCheckProperties()
{
return healthCheckProperties;
}
public void setHealthCheckProperties(Properties healthCheckProperties)
{
this.healthCheckProperties.putAll(healthCheckProperties);
}
public void addHealthCheckProperty(String key, String value)
{
healthCheckProperties.setProperty(key, value);
}
public boolean isReadOnly()
{
return isReadOnly;

@ -0,0 +1,138 @@
/*
* Copyright (C) 2014 Brett Wooldridge
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zaxxer.hikari.metrics;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import java.util.SortedMap;
import java.util.concurrent.TimeUnit;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricFilter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import com.codahale.metrics.health.HealthCheck;
import com.codahale.metrics.health.HealthCheckRegistry;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.pool.BaseHikariPool;
/**
* Provides Dropwizard HealthChecks. Two health checks are provided:
* <ul>
* <li>ConnectivityCheck</li>
* <li>Connection99Percent</li>
* </ul>
* The ConnectivityCheck will use the <code>connectionTimeout</code>, unless the health check property
* <code>connectivityCheckTimeoutMs</code> is defined. However, if either the <code>connectionTimeout</code>
* or the <code>connectivityCheckTimeoutMs</code> is 0 (infinite), a timeout of 10 seconds will be used.
* <p>
* The Connection99Percent health check will only be registered if the health check property
* <code>expected99thPercentileMs</code> is defined and greater than 0.
*
* @author Brett Wooldridge
*/
public final class CodahaleHealthChecker
{
/**
* Register Dropwizard health checks.
*
* @param pool the pool to register health checks for
* @param registry the HealthCheckRegistry into which checks will be registered
*/
public static void registerHealthChecks(final BaseHikariPool pool, final HealthCheckRegistry registry)
{
final HikariConfig hikariConfig = pool.getConfiguration();
final Properties healthCheckProperties = hikariConfig.getHealthCheckProperties();
final MetricRegistry metricRegistry = (MetricRegistry) hikariConfig.getMetricRegistry();
final long checkTimeoutMs = Long.parseLong(healthCheckProperties.getProperty("connectivityCheckTimeoutMs", String.valueOf(hikariConfig.getConnectionTimeout())));
registry.register(MetricRegistry.name(hikariConfig.getPoolName(), "pool", "ConnectivityCheck"), new ConnectivityHealthCheck(pool, checkTimeoutMs));
final long expected99thPercentile = Long.parseLong(healthCheckProperties.getProperty("expected99thPercentileMs", "0"));
if (metricRegistry != null && expected99thPercentile > 0) {
SortedMap<String,Timer> timers = metricRegistry.getTimers(new MetricFilter() {
@Override
public boolean matches(String name, Metric metric)
{
return name.equals(MetricRegistry.name(hikariConfig.getPoolName(), "pool", "Wait"));
}
});
if (!timers.isEmpty()) {
final Timer timer = timers.entrySet().iterator().next().getValue();
registry.register(MetricRegistry.name(hikariConfig.getPoolName(), "pool", "Connection99Percent"), new Connection99Percent(timer, expected99thPercentile));
}
}
}
private CodahaleHealthChecker()
{
// private constructor
}
private static class ConnectivityHealthCheck extends HealthCheck
{
private final BaseHikariPool pool;
private final long checkTimeoutMs;
ConnectivityHealthCheck(final BaseHikariPool pool, final long checkTimeoutMs)
{
this.pool = pool;
this.checkTimeoutMs = (checkTimeoutMs > 0 && checkTimeoutMs != Integer.MAX_VALUE ? checkTimeoutMs : TimeUnit.SECONDS.toMillis(10));
}
/** {@inheritDoc} */
@Override
protected Result check() throws Exception
{
Connection connection = null;
try {
connection = pool.getConnection(checkTimeoutMs);
return Result.healthy();
}
catch (SQLException e) {
return Result.unhealthy(e);
}
finally {
if (connection != null) {
connection.close();
}
}
}
}
private static class Connection99Percent extends HealthCheck
{
private final Timer waitTimer;
private final long expected99thPercentile;
Connection99Percent(final Timer waitTimer, final long expected99thPercentile)
{
this.waitTimer = waitTimer;
this.expected99thPercentile = expected99thPercentile;
}
/** {@inheritDoc} */
@Override
protected Result check() throws Exception
{
final long the99thPercentile = TimeUnit.NANOSECONDS.toMillis(Math.round(waitTimer.getSnapshot().get99thPercentile()));
return the99thPercentile <= expected99thPercentile ? Result.healthy() : Result.unhealthy("99th percentile connection wait time of %dms exceeds the threshold %dms", the99thPercentile, expected99thPercentile);
}
}
}

@ -29,6 +29,7 @@ import static com.zaxxer.hikari.util.UtilityElf.setRemoveOnCancelPolicy;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLTimeoutException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
@ -43,17 +44,19 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.health.HealthCheckRegistry;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.IConnectionCustomizer;
import com.zaxxer.hikari.metrics.CodaHaleMetricsTracker;
import com.zaxxer.hikari.metrics.CodahaleHealthChecker;
import com.zaxxer.hikari.metrics.MetricsTracker;
import com.zaxxer.hikari.metrics.MetricsTracker.MetricsContext;
import com.zaxxer.hikari.proxy.ConnectionProxy;
import com.zaxxer.hikari.proxy.IHikariConnectionProxy;
import com.zaxxer.hikari.proxy.ProxyFactory;
import com.zaxxer.hikari.util.ConcurrentBag;
import com.zaxxer.hikari.util.IBagStateListener;
import com.zaxxer.hikari.util.DefaultThreadFactory;
import com.zaxxer.hikari.util.IBagStateListener;
import com.zaxxer.hikari.util.LeakTask;
/**
@ -159,6 +162,10 @@ public abstract class BaseHikariPool implements HikariPoolMBean, IBagStateListen
this.houseKeepingExecutorService.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
this.leakTask = (configuration.getLeakDetectionThreshold() == 0) ? LeakTask.NO_LEAK : new LeakTask(configuration.getLeakDetectionThreshold(), houseKeepingExecutorService);
if (configuration.getHealthCheckRegistry() != null) {
CodahaleHealthChecker.registerHealthChecks(this, (HealthCheckRegistry) configuration.getHealthCheckRegistry());
}
setRemoveOnCancelPolicy(houseKeepingExecutorService);
poolUtils.setLoginTimeout(dataSource, connectionTimeout);
registerMBeans(configuration, this);
@ -166,15 +173,27 @@ public abstract class BaseHikariPool implements HikariPoolMBean, IBagStateListen
}
/**
* Get a connection from the pool, or timeout trying.
* Get a connection from the pool, or timeout after connectionTimeout milliseconds.
*
* @return a java.sql.Connection instance
* @throws SQLException thrown if a timeout occurs trying to obtain a connection
*/
public final Connection getConnection() throws SQLException
{
return getConnection(connectionTimeout);
}
/**
* Get a connection from the pool, or timeout after the specified number of milliseconds.
*
* @param hardTimeout the maximum time to wait for a connection from the pool
* @return a java.sql.Connection instance
* @throws SQLException thrown if a timeout occurs trying to obtain a connection
*/
public final Connection getConnection(final long hardTimeout) throws SQLException
{
suspendResumeLock.acquire();
long timeout = connectionTimeout;
long timeout = hardTimeout;
final long start = System.currentTimeMillis();
final MetricsContext metricsContext = (isRecordMetrics ? metricsTracker.recordConnectionRequest(start) : MetricsTracker.NO_CONTEXT);
@ -188,10 +207,11 @@ public abstract class BaseHikariPool implements HikariPoolMBean, IBagStateListen
final long now = System.currentTimeMillis();
if (bagEntry.evicted || (now - bagEntry.lastAccess > ALIVE_BYPASS_WINDOW && !isConnectionAlive(bagEntry.connection))) {
closeConnection(bagEntry); // Throw away the dead connection and try again
timeout = connectionTimeout - elapsedTimeMs(start);
timeout = hardTimeout - elapsedTimeMs(start);
}
else {
metricsContext.setConnectionLastOpen(bagEntry, now);
metricsContext.stop();
return ProxyFactory.getProxyConnection((HikariPool) this, bagEntry, leakTask.start());
}
}
@ -202,13 +222,13 @@ public abstract class BaseHikariPool implements HikariPoolMBean, IBagStateListen
}
finally {
suspendResumeLock.release();
metricsContext.stop();
}
logPoolState("Timeout failure ");
throw new SQLException(String.format("Timeout after %dms of waiting for a connection.", elapsedTimeMs(start)), lastConnectionFailure.getAndSet(null));
throw new SQLTimeoutException(String.format("Timeout after %dms of waiting for a connection.", elapsedTimeMs(start)), lastConnectionFailure.getAndSet(null));
}
/**
* Release a connection back to the pool, or permanently close it if it is broken.
*
@ -511,6 +531,16 @@ public abstract class BaseHikariPool implements HikariPoolMBean, IBagStateListen
return configuration.getConnectionCustomizer();
}
/**
* @param healthCheckRegistry
*/
private void registerHealthChecks(Object healthCheckRegistry)
{
if (healthCheckRegistry != null) {
}
}
public final void logPoolState(String... prefix)
{
if (LOGGER.isDebugEnabled()) {

@ -123,8 +123,7 @@
<Bundle-Name>HikariCP-java6</Bundle-Name>
<Export-Package>
com.zaxxer.hikari,
com.zaxxer.hikari.hibernate,
com.zaxxer.hikari.metrics
com.zaxxer.hikari.hibernate
</Export-Package>
<Private-Package>com.zaxxer.hikari.*</Private-Package>
<_exportcontents>
@ -142,6 +141,7 @@
javax.sql.rowset.serial,
javax.sql.rowset.spi,
com.codahale.metrics;resolution:=optional,
com.codahale.metrics.health;resolution:=optional,
org.slf4j;version="[1.6,2)",
org.hibernate;resolution:=optional,
org.hibernate.cfg;resolution:=optional,

@ -18,6 +18,8 @@ package com.zaxxer.hikari;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.SortedMap;
import java.util.concurrent.TimeUnit;
import org.junit.Assert;
import org.junit.Test;
@ -27,6 +29,8 @@ import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricFilter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import com.codahale.metrics.health.HealthCheck.Result;
import com.codahale.metrics.health.HealthCheckRegistry;
import com.zaxxer.hikari.util.UtilityElf;
/**
@ -106,4 +110,42 @@ public class TestMetrics
ds.close();
}
}
@Test
public void testHealthChecks() throws Exception
{
MetricRegistry metricRegistry = new MetricRegistry();
HealthCheckRegistry healthRegistry = new HealthCheckRegistry();
HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(10);
config.setMetricRegistry(metricRegistry);
config.setHealthCheckRegistry(healthRegistry);
config.setPoolName("test");
config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
config.addHealthCheckProperty("connectivityCheckTimeoutMs", "1000");
config.addHealthCheckProperty("expected99thPercentileMs", "10");
HikariDataSource ds = new HikariDataSource(config);
try {
UtilityElf.quietlySleep(TimeUnit.SECONDS.toMillis(2));
Connection connection = ds.getConnection();
connection.close();
connection = ds.getConnection();
connection.close();
SortedMap<String, Result> healthChecks = healthRegistry.runHealthChecks();
Result connectivityResult = healthChecks.get("test.pool.ConnectivityCheck");
Assert.assertTrue(connectivityResult.isHealthy());
Result slaResult = healthChecks.get("test.pool.Connection99Percent");
Assert.assertTrue(slaResult.isHealthy());
}
finally {
ds.close();
}
}
}

@ -66,6 +66,7 @@
<skipTests>${skip.unit.tests}</skipTests>
<classpathDependencyExcludes>
<classpathDependencyExclude>io.dropwizard.metrics:metrics-core</classpathDependencyExclude>
<classpathDependencyExclude>io.dropwizard.metrics:metrics-healthchecks</classpathDependencyExclude>
</classpathDependencyExcludes>
</configuration>
</plugin>
@ -92,8 +93,7 @@
<exclude>**/com/zaxxer/hikari/proxy/StatementProxy</exclude>
<exclude>**/com/zaxxer/hikari/proxy/CallableStatementProxy</exclude>
<exclude>**/com/zaxxer/hikari/proxy/PreparedStatementProxy</exclude>
<exclude>**/com/zaxxer/hikari/metrics/CodaHaleMetricsTracker</exclude>
<exclude>**/com/zaxxer/hikari/metrics/CodaHaleMetricsTracker.Context</exclude>
<exclude>**/com/zaxxer/hikari/metrics/**</exclude>
</excludes>
</configuration>
</execution>
@ -115,8 +115,7 @@
<exclude>**/com/zaxxer/hikari/proxy/StatementProxy.class</exclude>
<exclude>**/com/zaxxer/hikari/proxy/CallableStatementProxy.class</exclude>
<exclude>**/com/zaxxer/hikari/proxy/PreparedStatementProxy.class</exclude>
<exclude>**/com/zaxxer/hikari/metrics/CodaHaleMetricsTracker.class</exclude>
<exclude>**/com/zaxxer/hikari/metrics/CodaHaleMetricsTracker.Context.class</exclude>
<exclude>**/com/zaxxer/hikari/metrics/**</exclude>
</excludes>
</configuration>
</execution>
@ -190,8 +189,7 @@
<Bundle-Name>HikariCP</Bundle-Name>
<Export-Package>
com.zaxxer.hikari,
com.zaxxer.hikari.hibernate,
com.zaxxer.hikari.metrics
com.zaxxer.hikari.hibernate
</Export-Package>
<Private-Package>com.zaxxer.hikari.*</Private-Package>
<_exportcontents>
@ -209,6 +207,7 @@
javax.sql.rowset.serial,
javax.sql.rowset.spi,
com.codahale.metrics;resolution:=optional,
com.codahale.metrics.health;resolution:=optional,
org.slf4j;version="[1.6,2)",
org.hibernate;resolution:=optional,
org.hibernate.cfg;resolution:=optional,

@ -94,6 +94,13 @@
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-healthchecks</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>simple-jndi</groupId>
<artifactId>simple-jndi</artifactId>

Loading…
Cancel
Save