Author: fhanik
Date: Fri Dec 5 09:36:09 2008
New Revision: 723802
URL: http://svn.apache.org/viewvc?rev=723802&view=rev
Log:
Query collection stats
Added:
tomcat/trunk/modules/jdbc-pool/test/org/apache/tomcat/jdbc/test/CreateTestTable.java
tomcat/trunk/modules/jdbc-pool/test/org/apache/tomcat/jdbc/test/TestSlowQueryReport.java
Modified:
tomcat/trunk/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java
tomcat/trunk/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java
tomcat/trunk/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/interceptor/AbstractCreateStatementInterceptor.java
tomcat/trunk/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReport.java
Modified:
tomcat/trunk/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java?rev=723802&r1=723801&r2=723802&view=diff
==============================================================================
---
tomcat/trunk/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java
(original)
+++
tomcat/trunk/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java
Fri Dec 5 09:36:09 2008
@@ -28,6 +28,7 @@
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@@ -57,6 +58,9 @@
*/
public class ConnectionPool {
+ public static interface CloseListener {
+ void poolClosed(ConnectionPool pool);
+ }
//logger
protected static Log log = LogFactory.getLog(ConnectionPool.class);
@@ -64,7 +68,8 @@
//===============================================================================
// INSTANCE/QUICK ACCESS VARIABLE
//===============================================================================
-
+ protected ConcurrentLinkedQueue<CloseListener> listeners = new
ConcurrentLinkedQueue<CloseListener>();
+
/**
* All the information about the connection pool
*/
@@ -226,11 +231,7 @@
}
try {
- //cache the constructor
- if (proxyClassConstructor == null ) {
- Class proxyClass =
Proxy.getProxyClass(ConnectionPool.class.getClassLoader(), new Class[]
{java.sql.Connection.class});
- proxyClassConstructor = proxyClass.getConstructor(new Class[]
{ InvocationHandler.class });
- }
+ getProxyConstructor();
//create the proxy
//TODO possible optimization, keep track if this connection was
returned properly, and don't generate a new facade
Connection connection =
(Connection)proxyClassConstructor.newInstance(new Object[] { handler });
@@ -243,13 +244,25 @@
}
}
-
+
+ public Constructor getProxyConstructor() throws NoSuchMethodException {
+ //cache the constructor
+ if (proxyClassConstructor == null ) {
+ Class proxyClass =
Proxy.getProxyClass(ConnectionPool.class.getClassLoader(), new Class[]
{java.sql.Connection.class});
+ proxyClassConstructor = proxyClass.getConstructor(new Class[] {
InvocationHandler.class });
+ }
+ return proxyClassConstructor;
+ }
@Override
protected void finalize() throws Throwable {
close(true);
}
+ public void addCloseListener(CloseListener listener) {
+ listeners.add(listener);
+ }
+
/**
* Closes the pool and all disconnects all idle connections
* Active connections will be closed upon the [EMAIL PROTECTED]
java.sql.Connection#close close} method is called
@@ -288,6 +301,11 @@
}
size.set(0);
if (this.getPoolProperties().isJmxEnabled()) stopJmx();
+
+ while (listeners.size()>0) {
+ CloseListener listener = listeners.poll();
+ if (listener!=null) listener.poolClosed(this);
+ }
} //closePool
Modified:
tomcat/trunk/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java?rev=723802&r1=723801&r2=723802&view=diff
==============================================================================
---
tomcat/trunk/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java
(original)
+++
tomcat/trunk/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java
Fri Dec 5 09:36:09 2008
@@ -43,6 +43,10 @@
protected static Log log = LogFactory.getLog(DataSourceProxy.class);
protected volatile ConnectionPool pool = null;
+ public ConnectionPool getPool() {
+ return pool;
+ }
+
protected PoolProperties poolProperties = new PoolProperties();
public DataSourceProxy() {
Modified:
tomcat/trunk/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/interceptor/AbstractCreateStatementInterceptor.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/interceptor/AbstractCreateStatementInterceptor.java?rev=723802&r1=723801&r2=723802&view=diff
==============================================================================
---
tomcat/trunk/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/interceptor/AbstractCreateStatementInterceptor.java
(original)
+++
tomcat/trunk/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/interceptor/AbstractCreateStatementInterceptor.java
Fri Dec 5 09:36:09 2008
@@ -69,8 +69,9 @@
public abstract void closeInvoked();
protected boolean process(String[] names, Method method, boolean process) {
+ final String name = method.getName();
for (int i=0; (!process) && i<names.length; i++) {
- process = compare(names[i],method);
+ process = compare(names[i],name);
}
return process;
}
Modified:
tomcat/trunk/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReport.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReport.java?rev=723802&r1=723801&r2=723802&view=diff
==============================================================================
---
tomcat/trunk/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReport.java
(original)
+++
tomcat/trunk/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReport.java
Fri Dec 5 09:36:09 2008
@@ -16,97 +16,233 @@
*/
package org.apache.tomcat.jdbc.pool.interceptor;
+import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
import java.sql.SQLException;
+import java.sql.Statement;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
+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.JdbcInterceptor;
import org.apache.tomcat.jdbc.pool.PooledConnection;
/**
+ * Slow query report interceptor. Tracks timing of query executions.
* @author Filip Hanik
* @version 1.0
*/
-public class SlowQueryReport extends AbstractCreateStatementInterceptor {
- protected final String[] statements =
{"createStatement","prepareStatement","prepareCall"};
- protected final String[] executes =
{"execute","executeQuery","executeUpdate","executeBatch"};
-
+public class SlowQueryReport extends AbstractCreateStatementInterceptor
implements ConnectionPool.CloseListener {
+ //logger
+ protected static Log log = LogFactory.getLog(SlowQueryReport.class);
+ /**
+ * the constructors that are used to create statement proxies
+ */
+ protected static final Constructor[] constructors = new
Constructor[AbstractCreateStatementInterceptor.statements.length];
+ /**
+ * we will be keeping track of query stats on a per pool basis, do we want
this, or global?
+ */
protected static
IdentityHashMap<ConnectionPool,HashMap<String,QueryStats>> perPoolStats =
new IdentityHashMap<ConnectionPool,HashMap<String,QueryStats>>();
-
+ /**
+ * the queries that are used for this interceptor.
+ */
protected HashMap<String,QueryStats> queries = null;
-
+ /**
+ * The threshold in milliseconds. If the query is faster than this, we
don't measure it
+ */
protected long threshold = 100; //don't report queries less than this
+ /**
+ * Maximum number of queries we will be storing
+ */
protected int maxQueries= 1000; //don't store more than this amount of
queries
-
+ /**
+ * The pool that is associated with this interceptor so that we can clean
up
+ */
+ private ConnectionPool pool = null;
+
+ /**
+ * Returns the query stats for a given pool
+ * @param pool - the pool we want to retrieve stats for
+ * @return a hash map containing statistics for 0 to maxQueries
+ */
+ public static HashMap<String,QueryStats> getPoolStats(ConnectionPool pool)
{
+ return perPoolStats.get(pool);
+ }
+ /**
+ * Creates a slow query report interceptor
+ */
public SlowQueryReport() {
super();
}
+ /**
+ * returns the query measure threshold.
+ * This value is in milliseconds. If the query is faster than this
threshold than it wont be accounted for
+ * @return
+ */
public long getThreshold() {
return threshold;
}
+ /**
+ * Sets the query measurement threshold. The value is in milliseconds.
+ * If the query goes faster than this threshold it will not be recorded.
+ * @param threshold set to -1 to record every query. Value is in
milliseconds.
+ */
public void setThreshold(long threshold) {
this.threshold = threshold;
}
+ /**
+ * invoked when the connection receives the close request
+ * Not used for now.
+ */
@Override
public void closeInvoked() {
// TODO Auto-generated method stub
}
+
+ /**
+ * Creates a constructor for a proxy class, if one doesn't already exist
+ * @param idx - the index of the constructor
+ * @param clazz - the interface that the proxy will implement
+ * @return - returns a constructor used to create new instances
+ * @throws NoSuchMethodException
+ */
+ protected Constructor getConstructor(int idx, Class clazz)
+ throws NoSuchMethodException {
+ if (constructors[idx]==null) {
+ Class proxyClass =
Proxy.getProxyClass(SlowQueryReport.class.getClassLoader(), new Class[]
{clazz});
+ constructors[idx] = proxyClass.getConstructor(new Class[] {
InvocationHandler.class });
+ }
+ return constructors[idx];
+ }
+ /**
+ * Creates a statement interceptor to monitor query response times
+ */
@Override
public Object createStatement(Object proxy, Method method, Object[] args,
Object statement) {
- // TODO Auto-generated method stub
- String sql = null;
- if (method.getName().startsWith("prepare")) {
- sql = (args.length>0 && (args[0] instanceof
String))?(String)args[0]:null;
+ try {
+ Object result = null;
+ String name = method.getName();
+ String sql = null;
+ Constructor constructor = null;
+ if (compare(statements[0],name)) {
+ //createStatement
+ constructor = getConstructor(0,Statement.class);
+ }else if (compare(statements[1],name)) {
+ //prepareStatement
+ sql = (String)args[0];
+ constructor = getConstructor(1,PreparedStatement.class);
+ }else if (compare(statements[2],name)) {
+ //prepareCall
+ sql = (String)args[0];
+ constructor = getConstructor(2,CallableStatement.class);
+ }else {
+ //do nothing, might be a future unsupported method
+ //so we better bail out and let the system continue
+ return statement;
+ }
+ result = constructor.newInstance(new Object[] { new
StatementProxy(statement,sql) });
+ return result;
+ }catch (Exception x) {
+ log.warn("Unable to create statement proxy for slow query
report.",x);
}
- return new StatementProxy(statement,sql);
+ return statement;
}
- protected boolean process(final String[] names, Method method, boolean
process) {
- for (int i=0; (!process) && i<names.length; i++) {
- process = compare(method.getName(),names[i]);
+ /**
+ * [EMAIL PROTECTED]
+ */
+ public void reset(ConnectionPool parent, PooledConnection con) {
+ //see if we already created a map for this pool
+ queries = SlowQueryReport.perPoolStats.get(parent);
+ if (queries==null) {
+ //create the map to hold our stats
+ //however TODO we need to improve the eviction
+ //selection
+ queries = new LinkedHashMap<String,QueryStats>() {
+ @Override
+ protected boolean removeEldestEntry(Entry<String, QueryStats>
eldest) {
+ return size()>maxQueries;
+ }
+
+ };
+ perPoolStats.put(parent, queries);
+ //add ourselves to the pool listener, so we can cleanup
+ parent.addCloseListener(this);
}
- return process;
+ this.pool = parent;
}
- protected class QueryStats {
+
+ public void poolClosed(ConnectionPool pool) {
+ //clean up after ourselves.
+ perPoolStats.remove(pool);
+ }
+
+
+
+ /**
+ *
+ * @author fhanik
+ *
+ */
+ public static class QueryStats {
private final String query;
private int nrOfInvocations;
- private long maxInvocationTime;
+ private long maxInvocationTime = Long.MIN_VALUE;
private long maxInvocationDate;
- private long minInvocationTime;
+ private long minInvocationTime = Long.MAX_VALUE;
private long minInvocationDate;
private long totalInvocationTime;
+ public String toString() {
+ StringBuffer buf = new StringBuffer("QueryStats[query:");
+ buf.append(query);
+ buf.append(", nrOfInvocations:");
+ buf.append(nrOfInvocations);
+ buf.append(", maxInvocationTime:");
+ buf.append(maxInvocationTime);
+ buf.append(", maxInvocationDate:");
+ buf.append(new java.util.Date(maxInvocationDate).toGMTString());
+ buf.append(", minInvocationTime:");
+ buf.append(minInvocationTime);
+ buf.append(", minInvocationDate:");
+ buf.append(new java.util.Date(minInvocationDate).toGMTString());
+ buf.append(", totalInvocationTime:");
+ buf.append(totalInvocationTime);
+ buf.append(", averageInvocationTime:");
+ buf.append((float)totalInvocationTime / (float)nrOfInvocations);
+ buf.append("]");
+ return buf.toString();
+ }
+
public QueryStats(String query) {
this.query = query;
}
- public void add(long invocationTime) {
- long now = -1;
+ public void add(long invocationTime, long now) {
//not thread safe, but don't sacrifice performance for this kind
of stuff
maxInvocationTime = Math.max(invocationTime, maxInvocationTime);
if (maxInvocationTime == invocationTime) {
- now = System.currentTimeMillis();
maxInvocationDate = now;
}
minInvocationTime = Math.min(invocationTime, minInvocationTime);
if (minInvocationTime==invocationTime) {
- now = (now==-1)?System.currentTimeMillis():now;
minInvocationDate = now;
}
nrOfInvocations++;
@@ -148,12 +284,17 @@
public boolean equals(Object other) {
if (other instanceof QueryStats) {
QueryStats qs = (QueryStats)other;
- return SlowQueryReport.this.compare(qs.query,this.query);
+ return qs.query.equals(this.query);
}
return false;
}
}
+ /**
+ * Class to measure query execute time
+ * @author fhanik
+ *
+ */
protected class StatementProxy implements InvocationHandler {
protected boolean closed = false;
protected Object delegate;
@@ -164,43 +305,50 @@
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
+ //get the name of the method for comparison
final String name = method.getName();
+ //was close invoked?
boolean close = compare(JdbcInterceptor.CLOSE_VAL,name);
- if (close && closed) return null; //allow close to be called
multiple times
+ //allow close to be called multiple times
+ if (close && closed) return null;
+ //are we calling isClosed?
+ if (compare(JdbcInterceptor.ISCLOSED_VAL,name)) return closed;
+ //if we are calling anything else, bail out
if (closed) throw new SQLException("Statement closed.");
boolean process = false;
+ //check to see if we are about to execute a query
process = process(executes, method, process);
+ //if we are executing, get the current time
long start = (process)?System.currentTimeMillis():0;
//execute the query
Object result = method.invoke(delegate,args);
- long delta = (process)?(System.currentTimeMillis()-start):0;
+ //measure the time
+ long delta =
(process)?(System.currentTimeMillis()-start):Long.MIN_VALUE;
+ //see if we meet the requirements to measure
if (delta>threshold) {
- String sql = null;//TODO
- QueryStats qs = SlowQueryReport.this.queries.get(sql);
- if (qs == null) {
- qs = new QueryStats(sql);
- SlowQueryReport.this.queries.put((String)sql,qs);
+ //extract the query string
+ String sql = (query==null && args!=null &&
args.length>0)?(String)args[0]:query;
+ //if we do batch execution, then we name the query 'batch'
+ if (sql==null && compare(executes[3],name)) {
+ sql = "batch";
+ }
+ //if we have a query, record the stats
+ if (sql!=null) {
+ QueryStats qs = SlowQueryReport.this.queries.get(sql);
+ if (qs == null) {
+ qs = new QueryStats(sql);
+ SlowQueryReport.this.queries.put((String)sql,qs);
+ }
+ qs.add(delta,start);
}
- qs.add(delta);
- return qs;
}
+ //perform close cleanup
if (close) {
closed=true;
delegate = null;
+ queries = null;
}
return result;
}
}
-
- public void reset(ConnectionPool parent, PooledConnection con) {
- if (queries==null && SlowQueryReport.perPoolStats.get(parent)==null) {
- queries = new LinkedHashMap<String,QueryStats>() {
- @Override
- protected boolean removeEldestEntry(Entry<String, QueryStats>
eldest) {
- return size()>maxQueries;
- }
-
- };
- }
- }
}
Added:
tomcat/trunk/modules/jdbc-pool/test/org/apache/tomcat/jdbc/test/CreateTestTable.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/modules/jdbc-pool/test/org/apache/tomcat/jdbc/test/CreateTestTable.java?rev=723802&view=auto
==============================================================================
---
tomcat/trunk/modules/jdbc-pool/test/org/apache/tomcat/jdbc/test/CreateTestTable.java
(added)
+++
tomcat/trunk/modules/jdbc-pool/test/org/apache/tomcat/jdbc/test/CreateTestTable.java
Fri Dec 5 09:36:09 2008
@@ -0,0 +1,61 @@
+package org.apache.tomcat.jdbc.test;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.Statement;
+import java.util.Random;
+
+
+
+public class CreateTestTable extends DefaultTestCase {
+
+ public CreateTestTable(String name) {
+ super(name);
+ }
+
+ public void testPopulateData() throws Exception {
+ String insert = "insert into test values (?,?,?,?,?)";
+ this.init();
+ Connection con = datasource.getConnection();
+ Statement st = con.createStatement();
+ try {
+ st.execute("drop table test");
+ }catch (Exception ignore) {}
+ st.execute("create table test(id int not null, val1 varchar(255), val2
varchar(255), val3 varchar(255), val4 varchar(255))");
+ st.close();
+ PreparedStatement ps = con.prepareStatement(insert);
+ for (int i=0; i<10000000; i++) {
+ ps.setInt(1,i);
+ String s = getRandom();
+ ps.setString(2, s);
+ ps.setString(3, s);
+ ps.setString(4, s);
+ ps.setString(5, s);
+ ps.addBatch();
+ ps.clearParameters();
+ if ((i+1) % 1000 == 0) {
+ System.out.print(".");
+ }
+ if ((i+1) % 10000 == 0) {
+ System.out.print("\n"+(i+1));
+ ps.executeBatch();
+ ps.clearBatch();
+ }
+
+ }
+ ps.close();
+ con.close();
+ }
+
+ public static Random random = new Random(System.currentTimeMillis());
+ public static String getRandom() {
+ StringBuffer s = new StringBuffer(256);
+ for (int i=0;i<254; i++) {
+ int b = Math.abs(random.nextInt() % 29);
+ char c = (char)(b+65);
+ s.append(c);
+ }
+ return s.toString();
+ }
+
+}
Added:
tomcat/trunk/modules/jdbc-pool/test/org/apache/tomcat/jdbc/test/TestSlowQueryReport.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/modules/jdbc-pool/test/org/apache/tomcat/jdbc/test/TestSlowQueryReport.java?rev=723802&view=auto
==============================================================================
---
tomcat/trunk/modules/jdbc-pool/test/org/apache/tomcat/jdbc/test/TestSlowQueryReport.java
(added)
+++
tomcat/trunk/modules/jdbc-pool/test/org/apache/tomcat/jdbc/test/TestSlowQueryReport.java
Fri Dec 5 09:36:09 2008
@@ -0,0 +1,84 @@
+package org.apache.tomcat.jdbc.test;
+
+import java.sql.CallableStatement;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.util.HashMap;
+
+import org.apache.tomcat.jdbc.pool.ConnectionPool;
+import org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReport;
+
+public class TestSlowQueryReport extends DefaultTestCase {
+
+ public TestSlowQueryReport(String name) {
+ super(name);
+ }
+
+
+ public void testSlowSql() throws Exception {
+ int count = 3;
+ this.init();
+ this.datasource.setMaxActive(1);
+ this.datasource.setJdbcInterceptors(SlowQueryReport.class.getName());
+ Connection con = this.datasource.getConnection();
+ String slowSql = "select count(1) from test where val1 like 'ewqeq'
and val2 = 'ewrre' and val3 = 'sdada' and val4 = 'dadada'";
+ for (int i=0; i<count; i++) {
+ Statement st = con.createStatement();
+ ResultSet rs = st.executeQuery(slowSql);
+ rs.close();
+ st.close();
+ }
+ HashMap<String,SlowQueryReport.QueryStats> map =
SlowQueryReport.getPoolStats(datasource.getPool());
+ assertNotNull(map);
+ assertEquals(1,map.size());
+ String key = map.keySet().iterator().next();
+ SlowQueryReport.QueryStats stats = map.get(key);
+ System.out.println("Stats:"+stats);
+
+ for (int i=0; i<count; i++) {
+ PreparedStatement st = con.prepareStatement(slowSql);
+ ResultSet rs = st.executeQuery();
+ rs.close();
+ st.close();
+ }
+ System.out.println("Stats:"+stats);
+
+ for (int i=0; i<count; i++) {
+ CallableStatement st = con.prepareCall(slowSql);
+ ResultSet rs = st.executeQuery();
+ rs.close();
+ st.close();
+ }
+ System.out.println("Stats:"+stats);
+ ConnectionPool pool = datasource.getPool();
+ con.close();
+ tearDown();
+ //make sure we actually did clean up when the pool closed
+ assertNull(SlowQueryReport.getPoolStats(pool));
+ }
+
+ public void testFastSql() throws Exception {
+ int count = 3;
+ this.init();
+ this.datasource.setMaxActive(1);
+ this.datasource.setJdbcInterceptors(SlowQueryReport.class.getName());
+ Connection con = this.datasource.getConnection();
+ String slowSql = "select 1";
+ for (int i=0; i<count; i++) {
+ Statement st = con.createStatement();
+ ResultSet rs = st.executeQuery(slowSql);
+ rs.close();
+ st.close();
+ }
+ HashMap<String,SlowQueryReport.QueryStats> map =
SlowQueryReport.getPoolStats(datasource.getPool());
+ assertNotNull(map);
+ assertEquals(0,map.size());
+ ConnectionPool pool = datasource.getPool();
+ con.close();
+ tearDown();
+ assertNull(SlowQueryReport.getPoolStats(pool));
+ }
+
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]