From 473428cc247daa25b488411fe2b5911685678aba Mon Sep 17 00:00:00 2001 From: fhanik Date: Wed, 8 Jul 2009 22:34:40 +0000 Subject: [PATCH] remove dependency on the modeler so that the pool can run outside of tomcat and still have jmx query notifications. git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@792340 13f79535-47bb-0310-9956-ffa450edef68 --- modules/jdbc-pool/build.properties.default | 1 - modules/jdbc-pool/build.xml | 1 - modules/jdbc-pool/doc/jdbc-pool.xml | 10 +- .../apache/tomcat/jdbc/pool/DataSourceProxy.java | 11 ++- .../jdbc/pool/interceptor/SlowQueryReportJmx.java | 109 ++++++++++++++------- .../pool/interceptor/SlowQueryReportJmxMBean.java | 23 +++++ modules/jdbc-pool/resources/MANIFEST.MF | 3 +- .../apache/tomcat/jdbc/test/CreateTestTable.java | 12 ++- .../tomcat/jdbc/test/TestSlowQueryReport.java | 80 ++++++++++++++- 9 files changed, 202 insertions(+), 48 deletions(-) create mode 100644 modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReportJmxMBean.java diff --git a/modules/jdbc-pool/build.properties.default b/modules/jdbc-pool/build.properties.default index b0d740aa0..b882384ae 100644 --- a/modules/jdbc-pool/build.properties.default +++ b/modules/jdbc-pool/build.properties.default @@ -82,7 +82,6 @@ dbcp.loc=http://archive.apache.org/dist/commons/dbcp/binaries/commons-dbcp-1.2.z tomcat.home=${base.path}/apache-tomcat-6.0.20 tomcat.dbcp.jar=${tomcat.home}/lib/tomcat-dbcp.jar -tomcat.coyote.jar=${tomcat.home}/lib/tomcat-coyote.jar tomcat.juli.jar=${tomcat.home}/bin/tomcat-juli.jar tomcat.loc=http://archive.apache.org/dist/tomcat/tomcat-6/v6.0.20/bin/apache-tomcat-6.0.20.zip diff --git a/modules/jdbc-pool/build.xml b/modules/jdbc-pool/build.xml index 2b744b40d..79a5233a8 100644 --- a/modules/jdbc-pool/build.xml +++ b/modules/jdbc-pool/build.xml @@ -51,7 +51,6 @@ - diff --git a/modules/jdbc-pool/doc/jdbc-pool.xml b/modules/jdbc-pool/doc/jdbc-pool.xml index 055716bb0..aa1863f10 100644 --- a/modules/jdbc-pool/doc/jdbc-pool.xml +++ b/modules/jdbc-pool/doc/jdbc-pool.xml @@ -444,8 +444,15 @@

Extends the SlowQueryReport and in addition to log entries it issues JMX notification for monitoring tools to react to. Inherits all the attributes from its parent class. This class uses Tomcat's JMX engine so it wont work outside of the Tomcat container. + By default, JMX notifications are sent through the ConnectionPool mbean if it is enabled. + The SlowQueryReportJmx can also register an MBean if notifyPool=false

+ +

(boolean as String) Set to false if you want JMX notifications to go to the SlowQueryReportJmx MBean + The default value is true. +

+
@@ -687,9 +694,8 @@

Other examples of Tomcat configuration for JDBC usage can be found in the Tomcat documentation.

Building is pretty simple. The pool has a dependency on tomcat-juli.jar and in case you want the SlowQueryReportJmx - it also requires the tomcat-coyote.jar library.

- javac -classpath tomcat-juli.jar:tomcat-coyote.jar \ + javac -classpath tomcat-juli.jar \ -d . \ org/apache/tomcat/jdbc/pool/*.java \ org/apache/tomcat/jdbc/pool/interceptor/*.java \ diff --git a/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java b/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java index 5c0a32f28..430bad376 100644 --- a/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java +++ b/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java @@ -21,6 +21,9 @@ import java.sql.Connection; import java.sql.SQLException; import java.util.Iterator; import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; @@ -187,7 +190,13 @@ public class DataSourceProxy { protected void finalize() throws Throwable { //terminate the pool? - close(true); + ThreadPoolExecutor closer = new ThreadPoolExecutor(0,1,1000,TimeUnit.MILLISECONDS,new LinkedBlockingQueue()); + final Runnable r = new Runnable() { + public void run(){ + close(true); + } + }; + closer.execute(r); } public int getPoolSize() throws SQLException{ diff --git a/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReportJmx.java b/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReportJmx.java index a29590bbf..11c675e9d 100644 --- a/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReportJmx.java +++ b/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReportJmx.java @@ -16,19 +16,26 @@ */ package org.apache.tomcat.jdbc.pool.interceptor; +import java.lang.management.ManagementFactory; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; -import javax.management.DynamicMBean; import javax.management.InstanceAlreadyExistsException; import javax.management.InstanceNotFoundException; +import javax.management.ListenerNotFoundException; import javax.management.MBeanException; +import javax.management.MBeanNotificationInfo; +import javax.management.MBeanRegistrationException; import javax.management.MalformedObjectNameException; import javax.management.NotCompliantMBeanException; import javax.management.Notification; +import javax.management.NotificationBroadcasterSupport; +import javax.management.NotificationEmitter; +import javax.management.NotificationFilter; +import javax.management.NotificationListener; import javax.management.ObjectName; import javax.management.RuntimeOperationsException; import javax.management.openmbean.CompositeData; @@ -40,16 +47,14 @@ import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.jdbc.pool.ConnectionPool; import org.apache.tomcat.jdbc.pool.PooledConnection; -import org.apache.tomcat.util.modeler.BaseModelMBean; -import org.apache.tomcat.util.modeler.ManagedBean; -import org.apache.tomcat.util.modeler.Registry; +import org.apache.tomcat.jdbc.pool.PoolProperties.InterceptorProperty; /** * Publishes data to JMX and provides notifications * when failures happen. * @author fhanik * */ -public class SlowQueryReportJmx extends SlowQueryReport { +public class SlowQueryReportJmx extends SlowQueryReport implements NotificationEmitter, SlowQueryReportJmxMBean{ public static final String SLOW_QUERY_NOTIFICATION = "SLOW QUERY"; public static final String FAILED_QUERY_NOTIFICATION = "FAILED QUERY"; @@ -58,8 +63,35 @@ public class SlowQueryReportJmx extends SlowQueryReport { protected static Log log = LogFactory.getLog(SlowQueryReportJmx.class); - protected static ConcurrentHashMap mbeans = - new ConcurrentHashMap(); + protected static ConcurrentHashMap mbeans = + new ConcurrentHashMap(); + + + //==============================JMX STUFF======================== + protected volatile NotificationBroadcasterSupport notifier = new NotificationBroadcasterSupport(); + + public void addNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) throws IllegalArgumentException { + notifier.addNotificationListener(listener, filter, handback); + } + + + public MBeanNotificationInfo[] getNotificationInfo() { + return notifier.getNotificationInfo(); + } + + public void removeNotificationListener(NotificationListener listener) throws ListenerNotFoundException { + notifier.removeNotificationListener(listener); + + } + + public void removeNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) throws ListenerNotFoundException { + notifier.removeNotificationListener(listener, filter, handback); + + } + + + + //==============================JMX STUFF======================== protected String poolName = null; @@ -92,6 +124,7 @@ public class SlowQueryReportJmx extends SlowQueryReport { if (parent!=null) { poolName = parent.getName(); pool = parent; + registerJmx(); } } @@ -109,7 +142,6 @@ public class SlowQueryReportJmx extends SlowQueryReport { this.pool = pool; super.poolStarted(pool); this.poolName = pool.getName(); - registerJmx(); } @Override @@ -121,7 +153,6 @@ public class SlowQueryReportJmx extends SlowQueryReport { protected void notifyJmx(String query, String type) { try { - DynamicMBean mbean = mbeans.get(poolName); long sequence = notifySequence.incrementAndGet(); if (isNotifyPool()) { @@ -129,25 +160,21 @@ public class SlowQueryReportJmx extends SlowQueryReport { this.pool.getJmxPool().notify(type, query); } } else { - if (mbean!=null && mbean instanceof BaseModelMBean) { + if (notifier!=null) { Notification notification = new Notification(type, - mbean, + this, sequence, System.currentTimeMillis(), query); - BaseModelMBean bmbean = (BaseModelMBean)mbean; - bmbean.sendNotification(notification); + + notifier.sendNotification(notification); } } } catch (RuntimeOperationsException e) { if (log.isDebugEnabled()) { log.debug("Unable to send failed query notification.",e); } - } catch (MBeanException e) { - if (log.isDebugEnabled()) { - log.debug("Unable to send failed query notification.",e); - } } } @@ -221,16 +248,14 @@ public class SlowQueryReportJmx extends SlowQueryReport { protected void deregisterJmx() { try { - DynamicMBean mbean = null; - if ((mbean=mbeans.remove(poolName))!=null) { - Registry registry = Registry.getRegistry(null, null); - ManagedBean managed = registry.findManagedBean(this.getClass().getName()); - if (managed!=null) { - ObjectName oname = new ObjectName(ConnectionPool.POOL_JMX_TYPE_PREFIX+getClass().getName()+",name=" + poolName); - registry.unregisterComponent(oname); - registry.removeManagedBean(managed); - } + if (mbeans.remove(poolName)!=null) { + ObjectName oname = getObjectName(getClass(),poolName); + ManagementFactory.getPlatformMBeanServer().unregisterMBean(oname); } + } catch (MBeanRegistrationException e) { + log.debug("Jmx deregistration failed.",e); + } catch (InstanceNotFoundException e) { + log.debug("Jmx deregistration failed.",e); } catch (MalformedObjectNameException e) { log.warn("Jmx deregistration failed.",e); } catch (RuntimeOperationsException e) { @@ -238,6 +263,12 @@ public class SlowQueryReportJmx extends SlowQueryReport { } } + + + public static ObjectName getObjectName(Class clazz, String poolName) throws MalformedObjectNameException { + ObjectName oname = new ObjectName(ConnectionPool.POOL_JMX_TYPE_PREFIX+clazz.getName()+",name=" + poolName); + return oname; + } protected void registerJmx() { try { @@ -245,23 +276,15 @@ public class SlowQueryReportJmx extends SlowQueryReport { if (isNotifyPool()) { } else if (getCompositeType()!=null) { - ObjectName oname = new ObjectName(ConnectionPool.POOL_JMX_TYPE_PREFIX+"SlowQueryReport"+",name=" + poolName); - Registry registry = Registry.getRegistry(null, null); - registry.loadDescriptors(getClass().getPackage().getName(),getClass().getClassLoader()); - ManagedBean managed = registry.findManagedBean(this.getClass().getName()); - DynamicMBean mbean = managed!=null?managed.createMBean(this):null; - if (mbean!=null && mbeans.putIfAbsent(poolName, mbean)==null) { - registry.getMBeanServer().registerMBean( mbean, oname); - } else if (mbean==null){ - log.warn(SlowQueryReport.class.getName()+ "- No JMX support, unable to initiate Tomcat JMX. This requires the system to run inside the Tomcat container."); + ObjectName oname = getObjectName(getClass(),poolName); + if (mbeans.putIfAbsent(poolName, this)==null) { + ManagementFactory.getPlatformMBeanServer().registerMBean(this, oname); } } else { log.warn(SlowQueryReport.class.getName()+ "- No JMX support, composite type was not found."); } } catch (MalformedObjectNameException e) { log.error("Jmx registration failed, no JMX data will be exposed for the query stats.",e); - } catch (InstanceNotFoundException e) { - log.error("Jmx registration failed, no JMX data will be exposed for the query stats.",e); } catch (RuntimeOperationsException e) { log.error("Jmx registration failed, no JMX data will be exposed for the query stats.",e); } catch (MBeanException e) { @@ -272,4 +295,16 @@ public class SlowQueryReportJmx extends SlowQueryReport { log.error("Jmx registration failed, no JMX data will be exposed for the query stats.",e); } } + + @Override + public void setProperties(Map properties) { + super.setProperties(properties); + final String threshold = "notifyPool"; + InterceptorProperty p1 = properties.get(threshold); + if (p1!=null) { + this.setNotifyPool(Boolean.parseBoolean(p1.getValue())); + } + } + + } diff --git a/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReportJmxMBean.java b/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReportJmxMBean.java new file mode 100644 index 000000000..908eb1c53 --- /dev/null +++ b/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReportJmxMBean.java @@ -0,0 +1,23 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.tomcat.jdbc.pool.interceptor; + +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.OpenDataException; + +public interface SlowQueryReportJmxMBean { + public CompositeData[] getSlowQueriesCD() throws OpenDataException; +} diff --git a/modules/jdbc-pool/resources/MANIFEST.MF b/modules/jdbc-pool/resources/MANIFEST.MF index a63bfe12f..37c0f3ae8 100644 --- a/modules/jdbc-pool/resources/MANIFEST.MF +++ b/modules/jdbc-pool/resources/MANIFEST.MF @@ -19,5 +19,4 @@ Import-Package: javax.management.openmbean;version="0", javax.naming;version="0", javax.sql;version="0", - org.apache.juli.logging;version="[6.0.18, 7.0.0)", - org.apache.tomcat.util.modeler;version="[6.0.18, 7.0.0)" + org.apache.juli.logging;version="[6.0.18, 7.0.0)" diff --git a/modules/jdbc-pool/test/org/apache/tomcat/jdbc/test/CreateTestTable.java b/modules/jdbc-pool/test/org/apache/tomcat/jdbc/test/CreateTestTable.java index b59c21196..cb2890832 100644 --- a/modules/jdbc-pool/test/org/apache/tomcat/jdbc/test/CreateTestTable.java +++ b/modules/jdbc-pool/test/org/apache/tomcat/jdbc/test/CreateTestTable.java @@ -25,12 +25,12 @@ import java.sql.ResultSet; public class CreateTestTable extends DefaultTestCase { - public static final boolean recreate = Boolean.getBoolean("recreate"); + public static volatile boolean recreate = Boolean.getBoolean("recreate"); public CreateTestTable(String name) { super(name); } - + public void testCreateTestTable() throws Exception { this.init(); Connection con = datasource.getConnection(); @@ -73,7 +73,7 @@ public class CreateTestTable extends DefaultTestCase { } PreparedStatement ps = con.prepareStatement(insert); ps.setQueryTimeout(0); - for (int i=testCheckData(); i<1000000; i++) { + for (int i=testCheckData(); i<100000; i++) { ps.setInt(1,i); String s = getRandom(); ps.setString(2, s); @@ -107,5 +107,11 @@ public class CreateTestTable extends DefaultTestCase { } return s.toString(); } + + public static void main(String[] args) throws Exception { + recreate = true; + CreateTestTable test = new CreateTestTable("CreateTestTable"); + test.testPopulateData(); + } } diff --git a/modules/jdbc-pool/test/org/apache/tomcat/jdbc/test/TestSlowQueryReport.java b/modules/jdbc-pool/test/org/apache/tomcat/jdbc/test/TestSlowQueryReport.java index cbc75a1e4..33dbec63a 100644 --- a/modules/jdbc-pool/test/org/apache/tomcat/jdbc/test/TestSlowQueryReport.java +++ b/modules/jdbc-pool/test/org/apache/tomcat/jdbc/test/TestSlowQueryReport.java @@ -16,6 +16,7 @@ */ package org.apache.tomcat.jdbc.test; +import java.lang.management.ManagementFactory; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.PreparedStatement; @@ -23,8 +24,13 @@ import java.sql.ResultSet; import java.sql.Statement; import java.util.Map; +import javax.management.AttributeChangeNotification; +import javax.management.Notification; +import javax.management.NotificationListener; + import org.apache.tomcat.jdbc.pool.ConnectionPool; import org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReport; +import org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReportJmx; public class TestSlowQueryReport extends DefaultTestCase { @@ -74,6 +80,56 @@ public class TestSlowQueryReport extends DefaultTestCase { assertNull(SlowQueryReport.getPoolStats(pool.getName())); } + public void testSlowSqlJmx() throws Exception { + int count = 1; + this.init(); + this.datasource.setMaxActive(1); + this.datasource.setJdbcInterceptors(SlowQueryReportJmx.class.getName()+"(threshold=50,notifyPool=false)"); + Connection con = this.datasource.getConnection(); + String slowSql = "select count(1) from test where val1 like 'ewq%eq'"; + for (int i=0; i map = SlowQueryReport.getPoolStats(datasource.getPool().getName()); + assertNotNull(map); + assertEquals(1,map.size()); + String key = map.keySet().iterator().next(); + SlowQueryReport.QueryStats stats = map.get(key); + System.out.println("Stats:"+stats); + ClientListener listener = new ClientListener(); + ConnectionPool pool = datasource.getPool(); + ManagementFactory.getPlatformMBeanServer().addNotificationListener( + SlowQueryReportJmx.getObjectName(SlowQueryReportJmx.class, pool.getName()), + listener, + null, + null); + + for (int i=0; i