diff --git a/btm/pom.xml b/btm/pom.xml
index 153f4e73..4d6b53d8 100644
--- a/btm/pom.xml
+++ b/btm/pom.xml
@@ -69,6 +69,13 @@
provided
true
+
+ com.codahale.metrics
+ metrics-core
+ ${codahale.metrics.version}
+ provided
+ true
+
diff --git a/btm/src/main/java/bitronix/tm/Configuration.java b/btm/src/main/java/bitronix/tm/Configuration.java
index c6d6bf5d..4ad4d89e 100644
--- a/btm/src/main/java/bitronix/tm/Configuration.java
+++ b/btm/src/main/java/bitronix/tm/Configuration.java
@@ -77,6 +77,7 @@ public class Configuration implements Service {
private volatile String resourceConfigurationFilename;
private volatile boolean conservativeJournaling;
private volatile String jdbcProxyFactoryClass;
+ private volatile String metricsFactoryClass;
protected Configuration() {
try {
@@ -125,6 +126,7 @@ protected Configuration() {
resourceConfigurationFilename = getString(properties, "bitronix.tm.resource.configuration", null);
conservativeJournaling = getBoolean(properties, "bitronix.tm.conservativeJournaling", false);
jdbcProxyFactoryClass = getString(properties, "bitronix.tm.jdbcProxyFactoryClass", "auto");
+ metricsFactoryClass = getString(properties, "bitronix.tm.metricsFactoryClass", "auto");
} catch (IOException ex) {
throw new InitializationException("error loading configuration", ex);
}
@@ -683,6 +685,24 @@ public void setJdbcProxyFactoryClass(String jdbcProxyFactoryClass) {
this.jdbcProxyFactoryClass = jdbcProxyFactoryClass;
}
+ /**
+ * Get the factory class for creating metrics instances.
+ * The default value is "auto", set it to "none" if you don't want metrics.
+ *
+ * @return the name of the factory class
+ */
+ public String getMetricsFactoryClass() {
+ return metricsFactoryClass;
+ }
+
+ /**
+ * Set the name of the factory class for creating metrics instances.
+ *
+ * @param metricsFactoryClass the name of the metrics factory class
+ */
+ public void setMetricsFactoryClass(String metricsFactoryClass) {
+ this.metricsFactoryClass = metricsFactoryClass;
+ }
/**
* {@link bitronix.tm.resource.ResourceLoader} configuration file name. {@link bitronix.tm.resource.ResourceLoader}
diff --git a/btm/src/main/java/bitronix/tm/metric/CodahaleMetrics.java b/btm/src/main/java/bitronix/tm/metric/CodahaleMetrics.java
new file mode 100644
index 00000000..b0cffac2
--- /dev/null
+++ b/btm/src/main/java/bitronix/tm/metric/CodahaleMetrics.java
@@ -0,0 +1,75 @@
+package bitronix.tm.metric;
+
+import bitronix.tm.TransactionManagerServices;
+import com.codahale.metrics.JmxReporter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Slf4jReporter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * CodahaleMetrics - com.codahale.metrics based Metrics implementation.
+ *
+ * It may report the cummulated metrics to both the current log or platform JMX server.
+ *
+ * @author Vlad Mihalcea
+ */
+public class CodahaleMetrics implements Metrics {
+
+ private final static Logger log = LoggerFactory.getLogger(CodahaleMetrics.class);
+
+ private final String domain;
+
+ private final MetricRegistry metricRegistry;
+ private final Slf4jReporter logReporter;
+ private final JmxReporter jmxReporter;
+
+ public CodahaleMetrics(String domain) {
+ this.domain = domain;
+ this.metricRegistry = new MetricRegistry();
+ this.logReporter = Slf4jReporter
+ .forRegistry(metricRegistry)
+ .outputTo(log)
+ .build();
+ if (!TransactionManagerServices.getConfiguration().isDisableJmx()) {
+ jmxReporter = JmxReporter
+ .forRegistry(metricRegistry)
+ .inDomain(domain)
+ .build();
+ } else {
+ jmxReporter = null;
+ }
+ }
+
+ public CodahaleMetrics(Class> clazz, String uniqueName) {
+ this(MetricRegistry.name(clazz, uniqueName));
+ }
+
+ public String getDomain() {
+ return domain;
+ }
+
+ public void updateHistogram(String name, long value) {
+ metricRegistry.histogram(name).update(value);
+ }
+
+ public void updateTimer(String name, long value, TimeUnit timeUnit) {
+ metricRegistry.timer(name).update(value, timeUnit);
+ }
+
+ public void start() {
+ logReporter.start(5, TimeUnit.MINUTES);
+ if (jmxReporter != null) {
+ jmxReporter.start();
+ }
+ }
+
+ public void stop() {
+ logReporter.stop();
+ if (jmxReporter != null) {
+ jmxReporter.stop();
+ }
+ }
+}
diff --git a/btm/src/main/java/bitronix/tm/metric/CodahaleMetricsFactory.java b/btm/src/main/java/bitronix/tm/metric/CodahaleMetricsFactory.java
new file mode 100644
index 00000000..f740df66
--- /dev/null
+++ b/btm/src/main/java/bitronix/tm/metric/CodahaleMetricsFactory.java
@@ -0,0 +1,17 @@
+package bitronix.tm.metric;
+
+/**
+ * CodahaleMetricsFactory - com.codahale.metrics based MetricsFactory implementation.
+ *
+ * @author Vlad Mihalcea
+ */
+public class CodahaleMetricsFactory implements MetricsFactory {
+
+ public Metrics metrics(String domain) {
+ return new CodahaleMetrics(domain);
+ }
+
+ public Metrics metrics(Class> clazz, String uniqueName) {
+ return new CodahaleMetrics(clazz, uniqueName);
+ }
+}
diff --git a/btm/src/main/java/bitronix/tm/metric/Metrics.java b/btm/src/main/java/bitronix/tm/metric/Metrics.java
new file mode 100644
index 00000000..486d8571
--- /dev/null
+++ b/btm/src/main/java/bitronix/tm/metric/Metrics.java
@@ -0,0 +1,46 @@
+package bitronix.tm.metric;
+
+import java.io.Serializable;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Metrics - This interface defines a metrics basic behavior. This is required to isolate Bitronix from
+ * external Metrics dependencies.
+ *
+ * @author Vlad Mihalcea
+ */
+public interface Metrics extends Serializable {
+
+ /**
+ * Each metrics must define the unique domain it represents.
+ *
+ * @return metrics domain
+ */
+ String getDomain();
+
+ /**
+ * Update the given histogram
+ *
+ * @param name histogram name
+ * @param value histogram instant value
+ */
+ void updateHistogram(String name, long value);
+
+ /**
+ * Update the given timer
+ *
+ * @param name timer name
+ * @param value timer instant value
+ */
+ void updateTimer(String name, long value, TimeUnit timeUnit);
+
+ /**
+ * Start metrics reporting.
+ */
+ void start();
+
+ /**
+ * Stop metrics reporting.
+ */
+ void stop();
+}
diff --git a/btm/src/main/java/bitronix/tm/metric/MetricsAware.java b/btm/src/main/java/bitronix/tm/metric/MetricsAware.java
new file mode 100644
index 00000000..a68da40c
--- /dev/null
+++ b/btm/src/main/java/bitronix/tm/metric/MetricsAware.java
@@ -0,0 +1,21 @@
+package bitronix.tm.metric;
+
+/**
+ * MetricsAware - Interface to obtaining the current associated Metrics
+ *
+ * @author Vlad Mihalcea
+ */
+public interface MetricsAware {
+
+ /**
+ * Initialize and bind a metrics to the current object.
+ */
+ void initializeMetrics();
+
+ /**
+ * Get the current object metrics.
+ *
+ * @return current object metrics.
+ */
+ Metrics getMetrics();
+}
diff --git a/btm/src/main/java/bitronix/tm/metric/MetricsFactory.java b/btm/src/main/java/bitronix/tm/metric/MetricsFactory.java
new file mode 100644
index 00000000..252e4339
--- /dev/null
+++ b/btm/src/main/java/bitronix/tm/metric/MetricsFactory.java
@@ -0,0 +1,92 @@
+package bitronix.tm.metric;
+
+import bitronix.tm.TransactionManagerServices;
+import bitronix.tm.internal.BitronixRuntimeException;
+import bitronix.tm.utils.ClassLoaderUtils;
+
+/**
+ * MetricsFactory - Factory for Metrics instances.
+ *
+ * By default (the "auto" mode) it tries to locate the Codahale metrics registry, and therefore the Instance will
+ * reference a CodahaleMetricsFactory instance.
+ *
+ * If you choose the "none" mode, you won;t get any MetricsFactory.
+ *
+ * If you choose a custom factory class name, then you may use your own Metrics implementation.
+ *
+ * @author Vlad Mihalcea
+ */
+public interface MetricsFactory {
+
+ /**
+ * Instance resolving utility.
+ */
+ public static class Instance {
+
+ /* Classes should use MetricsFactory.INSTANCE to access the factory */
+ static MetricsFactory INSTANCE = Initializer.initialize(
+ TransactionManagerServices.getConfiguration().getMetricsFactoryClass());
+
+ /**
+ * Check if there is any initialized instance.
+ *
+ * @return is any instance available.
+ */
+ public static boolean exists() {
+ return INSTANCE != null;
+ }
+
+ /**
+ * Get the initialized instance.
+ *
+ * @return the initialized instance.
+ */
+ public static MetricsFactory get() {
+ return INSTANCE;
+ }
+ }
+
+ /**
+ * Get the domain related metrics.
+ *
+ * @param domain domain
+ * @return domain related metrics
+ */
+ Metrics metrics(String domain);
+
+ /**
+ * Get a Class bound domain metrics, embedding an uniqueName.
+ *
+ * @param clazz class as the metrics domain context
+ * @param uniqueName unique name to ensure domain uniqueness
+ * @return domain related metrics
+ */
+ Metrics metrics(Class> clazz, String uniqueName);
+
+ /**
+ * Initializer class used to initialize the factory.
+ */
+ class Initializer {
+ static MetricsFactory initialize(String metricFactoryClass) {
+ if ("none".equals(metricFactoryClass)) {
+ return null;
+ }
+ if ("auto".equals(metricFactoryClass)) {
+ try {
+ ClassLoaderUtils.loadClass("com.codahale.metrics.MetricRegistry");
+ return new CodahaleMetricsFactory();
+ } catch (ClassNotFoundException cnfe) {
+ //DO NOTHING
+ }
+ } else if (!metricFactoryClass.isEmpty()) {
+ try {
+ Class> clazz = ClassLoaderUtils.loadClass(metricFactoryClass);
+ return (MetricsFactory) clazz.newInstance();
+ } catch (Exception ex) {
+ throw new BitronixRuntimeException("error initializing MetricsFactory", ex);
+ }
+ }
+ return null;
+ }
+ }
+}
diff --git a/btm/src/main/java/bitronix/tm/resource/common/ResourceBean.java b/btm/src/main/java/bitronix/tm/resource/common/ResourceBean.java
index 6b3ed73e..f17ce5d8 100644
--- a/btm/src/main/java/bitronix/tm/resource/common/ResourceBean.java
+++ b/btm/src/main/java/bitronix/tm/resource/common/ResourceBean.java
@@ -15,6 +15,11 @@
*/
package bitronix.tm.resource.common;
+import bitronix.tm.metric.Metrics;
+import bitronix.tm.metric.MetricsAware;
+import bitronix.tm.metric.MetricsFactory;
+import bitronix.tm.utils.ManagementRegistrar;
+
import java.io.Serializable;
import java.util.Properties;
@@ -25,7 +30,7 @@
* @author Ludovic Orban
*/
@SuppressWarnings("serial")
-public abstract class ResourceBean implements Serializable {
+public abstract class ResourceBean implements Serializable, MetricsAware {
private volatile String className;
private volatile String uniqueName;
@@ -49,6 +54,8 @@ public abstract class ResourceBean implements Serializable {
private volatile transient int createdResourcesCounter;
+ private volatile transient Metrics metrics;
+
/**
* Initialize all properties with their default values.
*/
@@ -362,4 +369,22 @@ public boolean isDisabled() {
public int incCreatedResourcesCounter() {
return this.createdResourcesCounter++;
}
+
+ /**
+ * Initialize a resource metrics, using the class and the unique name to build the metrics domain.
+ */
+ public void initializeMetrics() {
+ if (metrics == null && MetricsFactory.Instance.exists()) {
+ metrics = MetricsFactory.Instance.get()
+ .metrics(getClass(), ManagementRegistrar.makeValidName(getUniqueName()));
+ }
+ }
+
+ /**
+ * Get the current associated metrics.
+ * @return current associated metrics.
+ */
+ public Metrics getMetrics() {
+ return metrics;
+ }
}
diff --git a/btm/src/main/java/bitronix/tm/resource/common/XAPool.java b/btm/src/main/java/bitronix/tm/resource/common/XAPool.java
index c1e23b0a..1431abd6 100644
--- a/btm/src/main/java/bitronix/tm/resource/common/XAPool.java
+++ b/btm/src/main/java/bitronix/tm/resource/common/XAPool.java
@@ -55,6 +55,11 @@ public class XAPool implements StateChangeListener {
private final static Logger log = LoggerFactory.getLogger(XAPool.class);
+ public static interface Metrics {
+ String CONNECTION_ACQUIRING_TIME = "connectionAcquiringTime";
+ String CONNECTIONS_IN_USE_HISTOGRAM = "connectionsInUseHistogram";
+ }
+
/**
* The stateTransitionLock makes sure that transitions of XAStatefulHolders from one state to another
* (movement from one pool to another) are atomic. A ReentrantReadWriteLock allows any number of
@@ -241,6 +246,7 @@ public void stateChanging(XAStatefulHolder source, int currentState, int futureS
case XAStatefulHolder.STATE_IN_POOL:
// no-op. calling availablePool.remove(source) here is reduncant because it was
// already removed when availablePool.poll() was called.
+ updateConnectionsInUseHistogram();
break;
case XAStatefulHolder.STATE_ACCESSIBLE:
if (log.isDebugEnabled()) { log.debug("removed " + source + " from the accessible pool"); }
@@ -266,6 +272,7 @@ public void stateChanged(XAStatefulHolder source, int oldState, int newState) {
case XAStatefulHolder.STATE_IN_POOL:
if (log.isDebugEnabled()) { log.debug("added " + source + " to the available pool"); }
availablePool.add(source);
+ updateConnectionsInUseHistogram();
break;
case XAStatefulHolder.STATE_ACCESSIBLE:
if (log.isDebugEnabled()) { log.debug("added " + source + " to the accessible pool"); }
@@ -309,7 +316,12 @@ private XAStatefulHolder getInPool(long remainingTimeMs) throws Exception {
if (log.isDebugEnabled()) { log.debug("getting IN_POOL connection from " + this + ", waiting if necessary"); }
try {
+ long start = MonotonicClock.currentTimeMillis();
XAStatefulHolder xaStatefulHolder = availablePool.poll(remainingTimeMs, TimeUnit.MILLISECONDS);
+ if (bean.getMetrics() != null) {
+ long end = MonotonicClock.currentTimeMillis();
+ bean.getMetrics().updateTimer(Metrics.CONNECTION_ACQUIRING_TIME, end - start, TimeUnit.MILLISECONDS);
+ }
if (xaStatefulHolder == null) {
if (TransactionManagerServices.isTransactionManagerRunning())
TransactionManagerServices.getTransactionManager().dumpTransactionContexts();
@@ -667,6 +679,15 @@ private void putSharedXAStatefulHolder(final XAStatefulHolder xaStatefulHolder)
threadLocal.set(xaStatefulHolder);
}
+ /**
+ * Update pool size histogram.
+ */
+ private void updateConnectionsInUseHistogram() {
+ if (bean.getMetrics() != null) {
+ bean.getMetrics().updateHistogram(Metrics.CONNECTIONS_IN_USE_HISTOGRAM, (totalPoolSize() - inPoolSize()));
+ }
+ }
+
private final class SharedStatefulHolderCleanupSynchronization implements Synchronization {
private final Uid gtrid;
diff --git a/btm/src/main/java/bitronix/tm/resource/jdbc/PoolingDataSource.java b/btm/src/main/java/bitronix/tm/resource/jdbc/PoolingDataSource.java
index 3c533658..93221672 100644
--- a/btm/src/main/java/bitronix/tm/resource/jdbc/PoolingDataSource.java
+++ b/btm/src/main/java/bitronix/tm/resource/jdbc/PoolingDataSource.java
@@ -86,6 +86,10 @@ public synchronized void init() {
buildXAPool();
this.jmxName = "bitronix.tm:type=JDBC,UniqueName=" + ManagementRegistrar.makeValidName(getUniqueName());
ManagementRegistrar.register(jmxName, this);
+ initializeMetrics();
+ if (getMetrics() != null) {
+ getMetrics().start();
+ }
} catch (Exception ex) {
throw new ResourceConfigurationException("cannot create JDBC datasource named " + getUniqueName(), ex);
}
@@ -355,6 +359,9 @@ public void close() {
ManagementRegistrar.unregister(jmxName);
jmxName = null;
+ if (getMetrics() != null) {
+ getMetrics().stop();
+ }
ResourceRegistrar.unregister(this);
}
diff --git a/btm/src/test/java/bitronix/tm/ConfigurationTest.java b/btm/src/test/java/bitronix/tm/ConfigurationTest.java
index 91c6068e..32f34ba8 100644
--- a/btm/src/test/java/bitronix/tm/ConfigurationTest.java
+++ b/btm/src/test/java/bitronix/tm/ConfigurationTest.java
@@ -98,7 +98,7 @@ public void testToString() {
" jndiTransactionSynchronizationRegistryName=java:comp/TransactionSynchronizationRegistry," +
" jndiUserTransactionName=java:comp/UserTransaction, journal=disk," +
" logPart1Filename=target/btm1.tlog, logPart2Filename=target/btm2.tlog, maxLogSizeInMb=2," +
- " resourceConfigurationFilename=null, serverId=null, skipCorruptedLogs=false, synchronousJmxRegistration=false," +
+ " metricsFactoryClass=auto, resourceConfigurationFilename=null, serverId=null, skipCorruptedLogs=false, synchronousJmxRegistration=false," +
" warnAboutZeroResourceTransaction=true]";
assertEquals(expectation, new Configuration().toString());
diff --git a/btm/src/test/java/bitronix/tm/metric/MetricsFactoryTest.java b/btm/src/test/java/bitronix/tm/metric/MetricsFactoryTest.java
new file mode 100644
index 00000000..f968dddc
--- /dev/null
+++ b/btm/src/test/java/bitronix/tm/metric/MetricsFactoryTest.java
@@ -0,0 +1,32 @@
+package bitronix.tm.metric;
+
+import junit.framework.TestCase;
+
+/**
+ * MetricsFactoryTest - MetricsFactory Test
+ *
+ * @author Vlad Mihalcea
+ */
+public class MetricsFactoryTest extends TestCase {
+
+ public void testInitialize() {
+
+ MetricsFactory metricsFactory;
+
+ metricsFactory = MetricsFactoryTestUtil.defaultInitialize();
+ assertEquals(CodahaleMetricsFactory.class, metricsFactory.getClass());
+ assertTrue(MetricsFactory.Instance.exists());
+ assertEquals(metricsFactory.getClass(), MetricsFactory.Instance.get().getClass());
+
+ metricsFactory = MetricsFactoryTestUtil.initialize(MockitoMetricsFactory.class.getName());
+ assertTrue(MetricsFactory.Instance.exists());
+ assertEquals(MockitoMetricsFactory.class, metricsFactory.getClass());
+
+ metricsFactory = MetricsFactoryTestUtil.initialize("none");
+ assertNull(metricsFactory);
+ }
+
+ protected void tearDown() {
+ MetricsFactoryTestUtil.defaultInitialize();
+ }
+}
diff --git a/btm/src/test/java/bitronix/tm/metric/MetricsFactoryTestUtil.java b/btm/src/test/java/bitronix/tm/metric/MetricsFactoryTestUtil.java
new file mode 100644
index 00000000..8348154f
--- /dev/null
+++ b/btm/src/test/java/bitronix/tm/metric/MetricsFactoryTestUtil.java
@@ -0,0 +1,34 @@
+package bitronix.tm.metric;
+
+import bitronix.tm.TransactionManagerServices;
+
+/**
+ * MetricsFactoryTestUtil - MetricsFactory Test Utilities
+ *
+ * @author Vlad Mihalcea
+ */
+public class MetricsFactoryTestUtil {
+
+ /**
+ * Initialize the MetricsFactory instance with a different metricFactoryClass.
+ *
+ * @param metricFactoryClass MetricsFactory class
+ * @return MetricsFactory
+ */
+ public static MetricsFactory initialize(String metricFactoryClass) {
+ MetricsFactory.Instance.INSTANCE = MetricsFactory.Initializer.initialize(
+ metricFactoryClass);
+ return MetricsFactory.Instance.get();
+ }
+
+ /**
+ * Initialize the MetricsFactory instance with the default metricFactoryClass.
+ *
+ * @return MetricsFactory
+ */
+ public static MetricsFactory defaultInitialize() {
+ MetricsFactory.Instance.INSTANCE = MetricsFactory.Initializer.initialize(
+ TransactionManagerServices.getConfiguration().getMetricsFactoryClass());
+ return MetricsFactory.Instance.get();
+ }
+}
diff --git a/btm/src/test/java/bitronix/tm/metric/MockitoMetricsFactory.java b/btm/src/test/java/bitronix/tm/metric/MockitoMetricsFactory.java
new file mode 100644
index 00000000..160e196b
--- /dev/null
+++ b/btm/src/test/java/bitronix/tm/metric/MockitoMetricsFactory.java
@@ -0,0 +1,25 @@
+package bitronix.tm.metric;
+
+import org.mockito.Mockito;
+
+/**
+ * MockitoMetricsFactory - MetricsFactory for Mockito
+ *
+ * @author Vlad Mihalcea
+ */
+public class MockitoMetricsFactory implements MetricsFactory {
+
+ private MetricsFactory mockMetricsFactory = Mockito.mock(MetricsFactory.class);
+
+ public MetricsFactory getMockMetricsFactory() {
+ return mockMetricsFactory;
+ }
+
+ public Metrics metrics(String domain) {
+ return mockMetricsFactory.metrics(domain);
+ }
+
+ public Metrics metrics(Class> clazz, String uniqueName) {
+ return mockMetricsFactory.metrics(clazz, uniqueName);
+ }
+}
diff --git a/btm/src/test/java/bitronix/tm/mock/JdbcPoolTest.java b/btm/src/test/java/bitronix/tm/mock/JdbcPoolTest.java
index 6232c194..dec62c53 100644
--- a/btm/src/test/java/bitronix/tm/mock/JdbcPoolTest.java
+++ b/btm/src/test/java/bitronix/tm/mock/JdbcPoolTest.java
@@ -16,12 +16,17 @@
package bitronix.tm.mock;
import bitronix.tm.TransactionManagerServices;
+import bitronix.tm.metric.Metrics;
+import bitronix.tm.metric.MetricsFactory;
+import bitronix.tm.metric.MetricsFactoryTestUtil;
+import bitronix.tm.metric.MockitoMetricsFactory;
import bitronix.tm.mock.resource.jdbc.MockitoXADataSource;
import bitronix.tm.recovery.RecoveryException;
import bitronix.tm.resource.ResourceConfigurationException;
import bitronix.tm.resource.common.XAPool;
import bitronix.tm.resource.jdbc.PoolingDataSource;
import junit.framework.TestCase;
+import org.mockito.Mockito;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -36,6 +41,10 @@
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
+import java.util.concurrent.TimeUnit;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.*;
/**
*
@@ -45,13 +54,17 @@ public class JdbcPoolTest extends TestCase {
private final static Logger log = LoggerFactory.getLogger(JdbcPoolTest.class);
private PoolingDataSource pds;
+ private Metrics mockMetrics;
protected void setUp() throws Exception {
TransactionManagerServices.getConfiguration().setJournal("null").setGracefulShutdownInterval(2);
TransactionManagerServices.getTransactionManager();
+ MetricsFactoryTestUtil.initialize(MockitoMetricsFactory.class.getName());
+
MockitoXADataSource.setStaticCloseXAConnectionException(null);
MockitoXADataSource.setStaticGetXAConnectionException(null);
+ mockMetrics = initMockMetrics();
pds = new PoolingDataSource();
pds.setMinPoolSize(1);
@@ -68,7 +81,8 @@ protected void setUp() throws Exception {
protected void tearDown() throws Exception {
pds.close();
- TransactionManagerServices.getTransactionManager().shutdown();
+ TransactionManagerServices.getTransactionManager().shutdown();
+ MetricsFactoryTestUtil.defaultInitialize();
}
public void testObjectProperties() throws Exception {
@@ -133,6 +147,7 @@ public void testReEnteringRecovery() throws Exception {
}
public void testPoolGrowth() throws Exception {
+
if (log.isDebugEnabled()) { log.debug("*** Starting testPoolGrowth"); }
Field poolField = pds.getClass().getDeclaredField("pool");
poolField.setAccessible(true);
@@ -140,14 +155,20 @@ public void testPoolGrowth() throws Exception {
assertEquals(1, pool.inPoolSize());
assertEquals(1, pool.totalPoolSize());
+ verify(mockMetrics, never()).updateTimer(eq(XAPool.Metrics.CONNECTION_ACQUIRING_TIME), anyLong(), eq(TimeUnit.MILLISECONDS));
+ verify(mockMetrics, never()).updateHistogram(eq(XAPool.Metrics.CONNECTIONS_IN_USE_HISTOGRAM), anyLong());
Connection c1 = pds.getConnection();
assertEquals(0, pool.inPoolSize());
assertEquals(1, pool.totalPoolSize());
+ verify(mockMetrics, times(1)).updateTimer(eq(XAPool.Metrics.CONNECTION_ACQUIRING_TIME), anyLong(), eq(TimeUnit.MILLISECONDS));
+ verify(mockMetrics, times(1)).updateHistogram(eq(XAPool.Metrics.CONNECTIONS_IN_USE_HISTOGRAM), anyLong());
Connection c2 = pds.getConnection();
assertEquals(0, pool.inPoolSize());
assertEquals(2, pool.totalPoolSize());
+ verify(mockMetrics, times(2)).updateTimer(eq(XAPool.Metrics.CONNECTION_ACQUIRING_TIME), anyLong(), eq(TimeUnit.MILLISECONDS));
+ verify(mockMetrics, times(2)).updateHistogram(eq(XAPool.Metrics.CONNECTIONS_IN_USE_HISTOGRAM), anyLong());
try {
pds.getConnection();
@@ -155,11 +176,18 @@ public void testPoolGrowth() throws Exception {
} catch (SQLException ex) {
assertEquals("unable to get a connection from pool of a PoolingDataSource containing an XAPool of resource pds with 2 connection(s) (0 still available)", ex.getMessage());
}
+ verify(mockMetrics, times(3)).updateTimer(eq(XAPool.Metrics.CONNECTION_ACQUIRING_TIME), anyLong(), eq(TimeUnit.MILLISECONDS));
+ verify(mockMetrics, times(2)).updateHistogram(eq(XAPool.Metrics.CONNECTIONS_IN_USE_HISTOGRAM), anyLong());
c1.close();
+ verify(mockMetrics, times(3)).updateTimer(eq(XAPool.Metrics.CONNECTION_ACQUIRING_TIME), anyLong(), eq(TimeUnit.MILLISECONDS));
+ verify(mockMetrics, times(3)).updateHistogram(eq(XAPool.Metrics.CONNECTIONS_IN_USE_HISTOGRAM), anyLong());
c2.close();
+ verify(mockMetrics, times(3)).updateTimer(eq(XAPool.Metrics.CONNECTION_ACQUIRING_TIME), anyLong(), eq(TimeUnit.MILLISECONDS));
+ verify(mockMetrics, times(4)).updateHistogram(eq(XAPool.Metrics.CONNECTIONS_IN_USE_HISTOGRAM), anyLong());
assertEquals(2, pool.inPoolSize());
assertEquals(2, pool.totalPoolSize());
+
}
public void testPoolShrink() throws Exception {
@@ -454,6 +482,21 @@ public void testWrappers() throws Exception {
assertTrue(unwrappedCStmt.getClass().getName().contains("java.sql.CallableStatement") && unwrappedCStmt.getClass().getName().contains("EnhancerByMockito"));
}
+ public void testStartMetric() {
+ verify(mockMetrics, times(1)).start();
+ verify(mockMetrics, never()).stop();
+ pds.close();
+ verify(mockMetrics, times(1)).stop();
+ }
+
+ private Metrics initMockMetrics() {
+ MetricsFactory mockMetricsFactory = ((MockitoMetricsFactory) MetricsFactory.Instance.get()).getMockMetricsFactory();
+ Metrics mockMetrics = Mockito.mock(Metrics.class);
+ Mockito.reset(mockMetricsFactory, mockMetrics);
+ when(mockMetricsFactory.metrics(eq(PoolingDataSource.class), eq("pds"))).thenReturn(mockMetrics);
+ return mockMetrics;
+ }
+
private static boolean isWrapperFor(Object obj, Class param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method isWrapperForMethod = obj.getClass().getMethod("isWrapperFor", Class.class);
return (Boolean) isWrapperForMethod.invoke(obj, param);
diff --git a/pom.xml b/pom.xml
index cc59a36b..a4d7653e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -57,6 +57,8 @@
1.5
3.2.4.RELEASE
+ 3.0.1
+
UTF-8
UTF-8
@@ -182,6 +184,14 @@
${spring.version}
+
+ com.codahale.metrics
+ metrics-core
+ ${codahale.metrics.version}
+ provided
+ true
+
+
org.mockito
mockito-all