import java.lang.reflect.Proxy;\r
import java.sql.CallableStatement;\r
import java.sql.SQLException;\r
+import java.util.HashMap;\r
+import java.util.IdentityHashMap;\r
+import java.util.LinkedHashMap;\r
+import java.util.Map.Entry;\r
\r
import org.apache.tomcat.jdbc.pool.ConnectionPool;\r
import org.apache.tomcat.jdbc.pool.JdbcInterceptor;\r
* @author Filip Hanik\r
* @version 1.0\r
*/\r
-public class SlowQueryReport extends JdbcInterceptor {\r
+public class SlowQueryReport extends AbstractCreateStatementInterceptor {\r
protected final String[] statements = {"createStatement","prepareStatement","prepareCall"};\r
protected final String[] executes = {"execute","executeQuery","executeUpdate","executeBatch"};\r
\r
+ protected static IdentityHashMap<ConnectionPool,HashMap<String,QueryStats>> perPoolStats = \r
+ new IdentityHashMap<ConnectionPool,HashMap<String,QueryStats>>();\r
+ \r
+ protected HashMap<String,QueryStats> queries = null;\r
+ \r
+ protected long threshold = 100; //don't report queries less than this\r
+ protected int maxQueries= 1000; //don't store more than this amount of queries\r
+\r
+ \r
+ \r
public SlowQueryReport() {\r
super();\r
}\r
\r
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\r
- boolean process = false;\r
- process = process(statements, method, process);\r
- if (process) {\r
- Object statement = super.invoke(proxy,method,args);\r
- CallableStatement measuredStatement =\r
- (CallableStatement)Proxy.newProxyInstance(SlowQueryReport.class.getClassLoader(),\r
- new Class[] {java.sql.CallableStatement.class,\r
- java.sql.PreparedStatement.class,\r
- java.sql.Statement.class},\r
- new StatementProxy(statement, args));\r
-\r
- return measuredStatement;\r
- } else {\r
- return super.invoke(proxy,method,args);\r
+ public long getThreshold() {\r
+ return threshold;\r
+ }\r
+\r
+ public void setThreshold(long threshold) {\r
+ this.threshold = threshold;\r
+ }\r
+\r
+ @Override\r
+ public void closeInvoked() {\r
+ // TODO Auto-generated method stub\r
+ \r
+ }\r
+\r
+ @Override\r
+ public Object createStatement(Object proxy, Method method, Object[] args, Object statement) {\r
+ // TODO Auto-generated method stub\r
+ String sql = null;\r
+ if (method.getName().startsWith("prepare")) {\r
+ sql = (args.length>0 && (args[0] instanceof String))?(String)args[0]:null;\r
}\r
+ return new StatementProxy(statement,sql);\r
}\r
\r
- protected boolean process(String[] names, Method method, boolean process) {\r
+ protected boolean process(final String[] names, Method method, boolean process) {\r
for (int i=0; (!process) && i<names.length; i++) {\r
- process = (method.getName()==names[i]);\r
+ process = compare(method.getName(),names[i]);\r
}\r
return process;\r
}\r
\r
+ protected class QueryStats {\r
+ private final String query;\r
+ private int nrOfInvocations;\r
+ private long maxInvocationTime;\r
+ private long maxInvocationDate;\r
+ private long minInvocationTime;\r
+ private long minInvocationDate;\r
+ private long totalInvocationTime;\r
+ \r
+ public QueryStats(String query) {\r
+ this.query = query;\r
+ }\r
+ \r
+ public void add(long invocationTime) {\r
+ long now = -1;\r
+ //not thread safe, but don't sacrifice performance for this kind of stuff\r
+ maxInvocationTime = Math.max(invocationTime, maxInvocationTime);\r
+ if (maxInvocationTime == invocationTime) {\r
+ now = System.currentTimeMillis();\r
+ maxInvocationDate = now;\r
+ }\r
+ minInvocationTime = Math.min(invocationTime, minInvocationTime);\r
+ if (minInvocationTime==invocationTime) {\r
+ now = (now==-1)?System.currentTimeMillis():now;\r
+ minInvocationDate = now;\r
+ }\r
+ nrOfInvocations++;\r
+ totalInvocationTime+=invocationTime;\r
+ }\r
+ \r
+ public String getQuery() {\r
+ return query;\r
+ }\r
+\r
+ public int getNrOfInvocations() {\r
+ return nrOfInvocations;\r
+ }\r
+\r
+ public long getMaxInvocationTime() {\r
+ return maxInvocationTime;\r
+ }\r
+\r
+ public long getMaxInvocationDate() {\r
+ return maxInvocationDate;\r
+ }\r
+\r
+ public long getMinInvocationTime() {\r
+ return minInvocationTime;\r
+ }\r
+\r
+ public long getMinInvocationDate() {\r
+ return minInvocationDate;\r
+ }\r
+\r
+ public long getTotalInvocationTime() {\r
+ return totalInvocationTime;\r
+ }\r
+\r
+ public int hashCode() {\r
+ return query.hashCode();\r
+ }\r
+ \r
+ public boolean equals(Object other) {\r
+ if (other instanceof QueryStats) {\r
+ QueryStats qs = (QueryStats)other;\r
+ return SlowQueryReport.this.compare(qs.query,this.query);\r
+ } \r
+ return false;\r
+ }\r
+ }\r
+ \r
protected class StatementProxy implements InvocationHandler {\r
- protected Object parent;\r
- protected Object[] args;\r
- public StatementProxy(Object parent, Object[] args) {\r
- this.parent = parent;\r
- this.args = args;\r
+ protected boolean closed = false;\r
+ protected Object delegate;\r
+ protected final String query;\r
+ public StatementProxy(Object parent, String query) {\r
+ this.delegate = parent;\r
+ this.query = query;\r
}\r
+ \r
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\r
- if (this.parent == null ) throw new SQLException("Statement has been closed.");\r
+ final String name = method.getName();\r
+ boolean close = compare(JdbcInterceptor.CLOSE_VAL,name);\r
+ if (close && closed) return null; //allow close to be called multiple times\r
+ if (closed) throw new SQLException("Statement closed.");\r
boolean process = false;\r
process = process(executes, method, process);\r
long start = (process)?System.currentTimeMillis():0;\r
//execute the query\r
- Object result = method.invoke(parent,args);\r
+ Object result = method.invoke(delegate,args);\r
long delta = (process)?(System.currentTimeMillis()-start):0;\r
- if (delta>10) {\r
- StringBuffer out = new StringBuffer("\n\tType:");\r
- out.append(parent.getClass().getName());\r
- out.append("\n\tCreate/Prepare args:");\r
- for (int i=0; this.args!=null && i<this.args.length;i++) {\r
- out.append(this.args[i]!=null?this.args[i]:"null");\r
- out.append("; ");\r
+ if (delta>threshold) {\r
+ String sql = null;//TODO\r
+ QueryStats qs = SlowQueryReport.this.queries.get(sql);\r
+ if (qs == null) {\r
+ qs = new QueryStats(sql);\r
+ SlowQueryReport.this.queries.put((String)sql,qs);\r
}\r
- out.append("\n\tExecute args:");\r
- for (int i=0; args!=null && i<args.length;i++) {\r
- out.append(args[i]!=null?args[i]:"null");\r
- out.append("; ");\r
- }\r
- System.out.println("Slow query:"+out+"\nTime to execute:"+(delta)+" ms.");\r
+ qs.add(delta);\r
+ return qs;\r
}\r
- if (JdbcInterceptor.CLOSE_VAL==method.getName()) {\r
- this.parent = null;\r
- this.args = null;\r
+ if (close) {\r
+ closed=true;\r
+ delegate = null;\r
}\r
return result;\r
}\r
}\r
\r
public void reset(ConnectionPool parent, PooledConnection con) {\r
+ if (queries==null && SlowQueryReport.perPoolStats.get(parent)==null) {\r
+ queries = new LinkedHashMap<String,QueryStats>() {\r
+ @Override\r
+ protected boolean removeEldestEntry(Entry<String, QueryStats> eldest) {\r
+ return size()>maxQueries;\r
+ }\r
\r
+ };\r
+ }\r
}\r
}\r