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