From eeec5415c6648a7e2d20e5d2e8189f5dfc15d6d8 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Mon, 24 Mar 2014 19:07:09 +0900 Subject: [PATCH] Start implementing [optional] metrics for HikariCP. --- .../java/com/zaxxer/hikari/HikariConfig.java | 14 +++- .../java/com/zaxxer/hikari/HikariPool.java | 25 ++++--- .../metrics/CodaHaleMetricsTracker.java | 66 +++++++++++++++++++ .../zaxxer/hikari/metrics/MetricsTracker.java | 44 +++++++++++++ .../zaxxer/hikari/proxy/ConnectionProxy.java | 9 +++ .../hikari/proxy/IHikariConnectionProxy.java | 21 ++++-- 6 files changed, 163 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/zaxxer/hikari/metrics/CodaHaleMetricsTracker.java create mode 100644 src/main/java/com/zaxxer/hikari/metrics/MetricsTracker.java diff --git a/src/main/java/com/zaxxer/hikari/HikariConfig.java b/src/main/java/com/zaxxer/hikari/HikariConfig.java index 7650cc04..6286d96e 100644 --- a/src/main/java/com/zaxxer/hikari/HikariConfig.java +++ b/src/main/java/com/zaxxer/hikari/HikariConfig.java @@ -69,8 +69,9 @@ public class HikariConfig implements HikariConfigMBean private boolean isReadOnly; private boolean isInitializationFailFast; private boolean isJdbc4connectionTest; - private boolean isRegisterMbeans; private boolean isIsolateInternalQueries; + private boolean isRecordMetrics; + private boolean isRegisterMbeans; private DataSource dataSource; private Properties dataSourceProperties; private IConnectionCustomizer connectionCustomizer; @@ -96,6 +97,7 @@ public class HikariConfig implements HikariConfigMBean maxPoolSize = 10; maxLifetime = MAX_LIFETIME; poolName = "HikariPool-" + poolNumber++; + isRecordMetrics = false; transactionIsolation = -1; } @@ -429,6 +431,16 @@ public class HikariConfig implements HikariConfigMBean this.isReadOnly = readOnly; } + public boolean isRecordMetrics() + { + return isRecordMetrics; + } + + public void setRecordMetrics(boolean recordMetrics) + { + this.isRecordMetrics = recordMetrics; + } + public boolean isRegisterMbeans() { return isRegisterMbeans; diff --git a/src/main/java/com/zaxxer/hikari/HikariPool.java b/src/main/java/com/zaxxer/hikari/HikariPool.java index 0e50284b..b0ebab40 100644 --- a/src/main/java/com/zaxxer/hikari/HikariPool.java +++ b/src/main/java/com/zaxxer/hikari/HikariPool.java @@ -31,6 +31,9 @@ import javax.sql.DataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.zaxxer.hikari.metrics.CodaHaleMetricsTracker; +import com.zaxxer.hikari.metrics.MetricsTracker; +import com.zaxxer.hikari.metrics.MetricsTracker.Context; import com.zaxxer.hikari.proxy.IHikariConnectionProxy; import com.zaxxer.hikari.proxy.ProxyFactory; import com.zaxxer.hikari.util.ConcurrentBag; @@ -55,6 +58,7 @@ public final class HikariPool implements HikariPoolMBean, IBagStateListener private final HikariConfig configuration; private final ConcurrentBag connectionBag; private final ThreadPoolExecutor addConnectionExecutor; + private final MetricsTracker metricsTracker; private final boolean isAutoCommit; private final boolean isIsolateInternalQueries; @@ -103,6 +107,7 @@ public final class HikariPool implements HikariPoolMBean, IBagStateListener this.isJdbc4ConnectionTest = configuration.isJdbc4ConnectionTest(); this.leakDetectionThreshold = configuration.getLeakDetectionThreshold(); this.transactionIsolation = configuration.getTransactionIsolation(); + this.metricsTracker = (configuration.isRecordMetrics() ? new CodaHaleMetricsTracker(configuration.getPoolName()) : new MetricsTracker()); this.dataSource = initializeDataSource(); @@ -132,10 +137,11 @@ public final class HikariPool implements HikariPoolMBean, IBagStateListener */ Connection getConnection() throws SQLException { + final long start = System.currentTimeMillis(); + final Context context = metricsTracker.recordConnectionRequest(start); + long timeout = configuration.getConnectionTimeout(); try { - long timeout = configuration.getConnectionTimeout(); - final long start = System.currentTimeMillis(); do { IHikariConnectionProxy connectionProxy = connectionBag.borrow(timeout, TimeUnit.MILLISECONDS); @@ -161,16 +167,17 @@ public final class HikariPool implements HikariPoolMBean, IBagStateListener } while (timeout > 0); - String msg = String.format("Timeout of %dms encountered waiting for connection.", configuration.getConnectionTimeout()); - LOGGER.warn(msg); logPoolState("Timeout failure "); - - throw new SQLException(msg); + throw new SQLException(String.format("Timeout of %dms encountered waiting for connection.", configuration.getConnectionTimeout())); } catch (InterruptedException e) { return null; } + finally + { + context.stop(); + } } /** @@ -181,6 +188,8 @@ public final class HikariPool implements HikariPoolMBean, IBagStateListener */ public void releaseConnection(IHikariConnectionProxy connectionProxy) { + metricsTracker.recordConnectionUsage(System.currentTimeMillis() - connectionProxy.getLastOpenTime()); + if (!connectionProxy.isBrokenConnection() && !isShutdown) { connectionBag.requite(connectionProxy); @@ -241,7 +250,7 @@ public final class HikariPool implements HikariPoolMBean, IBagStateListener continue; } - if (minIdle == 0) + if (minIdle == 0) // This break is here so we only add one connection when demanded { break; } @@ -314,7 +323,7 @@ public final class HikariPool implements HikariPoolMBean, IBagStateListener if (totalConnections.incrementAndGet() > configuration.getMaximumPoolSize()) { totalConnections.decrementAndGet(); - return false; + return true; } connection = (username == null && password == null) ? dataSource.getConnection() : dataSource.getConnection(username, password); diff --git a/src/main/java/com/zaxxer/hikari/metrics/CodaHaleMetricsTracker.java b/src/main/java/com/zaxxer/hikari/metrics/CodaHaleMetricsTracker.java new file mode 100644 index 00000000..3bf21404 --- /dev/null +++ b/src/main/java/com/zaxxer/hikari/metrics/CodaHaleMetricsTracker.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2013,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 com.codahale.metrics.Histogram; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; +import com.zaxxer.hikari.HikariPool; + +public final class CodaHaleMetricsTracker extends MetricsTracker +{ + private MetricRegistry registry; + private Timer connectionObtainTimer; + private Histogram connectionUsage; + + public CodaHaleMetricsTracker(String poolName) + { + registry = new MetricRegistry(); + connectionObtainTimer = registry.timer(MetricRegistry.name(HikariPool.class, "connection", "wait")); + connectionUsage = registry.histogram(MetricRegistry.name(HikariPool.class, "connection", "usage")); + } + + @Override + public Context recordConnectionRequest(long requestTime) + { + return new Context(connectionObtainTimer); + } + + @Override + public void recordConnectionUsage(long usageMilleseconds) + { + connectionUsage.update(usageMilleseconds); + } + + public static final class Context extends MetricsTracker.Context + { + Timer.Context innerContext; + + Context(Timer timer) + { + innerContext = timer.time(); + } + + public void stop() + { + if (innerContext != null) + { + innerContext.stop(); + } + } + } +} diff --git a/src/main/java/com/zaxxer/hikari/metrics/MetricsTracker.java b/src/main/java/com/zaxxer/hikari/metrics/MetricsTracker.java new file mode 100644 index 00000000..1dfb5ead --- /dev/null +++ b/src/main/java/com/zaxxer/hikari/metrics/MetricsTracker.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2013,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; + + +/** + * This class does absolutely nothing. + * + * @author Brett Wooldridge + */ +public class MetricsTracker +{ + private static final Context NO_CONTEXT = new Context(); + + public static class Context + { + public void stop() + { + } + } + + public Context recordConnectionRequest(long start) + { + return NO_CONTEXT; + } + + public void recordConnectionUsage(long usageMilleseconds) + { + } +} diff --git a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java index 51885ed5..75647341 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java +++ b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java @@ -62,6 +62,7 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy private boolean isReadOnlyDirty; private boolean isTransactionIsolationDirty; private volatile long lastAccess; + private long uncloseTime; private StackTraceElement[] leakTrace; private TimerTask leakTask; @@ -163,6 +164,13 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy return lastAccess; } + /** {@inheritDoc} */ + @Override + public long getLastOpenTime() + { + return uncloseTime; + } + /** {@inheritDoc} */ @Override public final boolean isBrokenConnection() @@ -218,6 +226,7 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy public final void unclose() { isClosed = false; + uncloseTime = System.currentTimeMillis(); } /** {@inheritDoc} */ diff --git a/src/main/java/com/zaxxer/hikari/proxy/IHikariConnectionProxy.java b/src/main/java/com/zaxxer/hikari/proxy/IHikariConnectionProxy.java index 80f57580..5504cb22 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/IHikariConnectionProxy.java +++ b/src/main/java/com/zaxxer/hikari/proxy/IHikariConnectionProxy.java @@ -37,7 +37,7 @@ public interface IHikariConnectionProxy extends Connection, IBagManagable * @param houseKeepingTimer the timer to run the leak detection task with */ void captureStack(long leakThreshold, Timer houseKeepingTimer); - + /** * Check if the provided SQLException contains a SQLSTATE that indicates * a disconnection from the server. @@ -45,21 +45,28 @@ public interface IHikariConnectionProxy extends Connection, IBagManagable * @param sqle the SQLException to check */ void checkException(SQLException sqle); - + /** * Get the creation timestamp of the connection. * * @return the creation timestamp */ long getCreationTime(); - + /** * Get the last access timestamp of the connection. * * @return the last access timestamp */ long getLastAccess(); - + + /** + * Get the timestamp of when the connection was removed from the pool for use. + * + * @return the timestamp the connection started to be used in the most recent request + */ + long getLastOpenTime(); + /** * Return the broken state of the connection. If checkException() detected * a broken connection, this method will return true, otherwise false. @@ -67,7 +74,7 @@ public interface IHikariConnectionProxy extends Connection, IBagManagable * @return the broken state of the connection */ boolean isBrokenConnection(); - + /** * Actually close the underlying delegate Connection. * @@ -81,12 +88,12 @@ public interface IHikariConnectionProxy extends Connection, IBagManagable * @throws SQLException thrown if there is an error resetting any of the state */ void resetConnectionState() throws SQLException; - + /** * Make the Connection available for use again by marking it as not closed. */ void unclose(); - + /** * Called by Statement and its subclasses when they are closed to remove them * from the tracking list.