Author: fhanik Date: Wed Dec 3 23:07:23 2008 New Revision: 723228 URL: http://svn.apache.org/viewvc?rev=723228&view=rev Log: start working on the interceptor for query stats
Modified: 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/interceptor/SlowQueryReport.java URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReport.java?rev=723228&r1=723227&r2=723228&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 Wed Dec 3 23:07:23 2008 @@ -21,6 +21,10 @@ import java.lang.reflect.Proxy; import java.sql.CallableStatement; import java.sql.SQLException; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.LinkedHashMap; +import java.util.Map.Entry; import org.apache.tomcat.jdbc.pool.ConnectionPool; import org.apache.tomcat.jdbc.pool.JdbcInterceptor; @@ -30,78 +34,173 @@ * @author Filip Hanik * @version 1.0 */ -public class SlowQueryReport extends JdbcInterceptor { +public class SlowQueryReport extends AbstractCreateStatementInterceptor { protected final String[] statements = {"createStatement","prepareStatement","prepareCall"}; protected final String[] executes = {"execute","executeQuery","executeUpdate","executeBatch"}; + protected static IdentityHashMap<ConnectionPool,HashMap<String,QueryStats>> perPoolStats = + new IdentityHashMap<ConnectionPool,HashMap<String,QueryStats>>(); + + protected HashMap<String,QueryStats> queries = null; + + protected long threshold = 100; //don't report queries less than this + protected int maxQueries= 1000; //don't store more than this amount of queries + + + public SlowQueryReport() { super(); } - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - boolean process = false; - process = process(statements, method, process); - if (process) { - Object statement = super.invoke(proxy,method,args); - CallableStatement measuredStatement = - (CallableStatement)Proxy.newProxyInstance(SlowQueryReport.class.getClassLoader(), - new Class[] {java.sql.CallableStatement.class, - java.sql.PreparedStatement.class, - java.sql.Statement.class}, - new StatementProxy(statement, args)); - - return measuredStatement; - } else { - return super.invoke(proxy,method,args); + public long getThreshold() { + return threshold; + } + + public void setThreshold(long threshold) { + this.threshold = threshold; + } + + @Override + public void closeInvoked() { + // TODO Auto-generated method stub + + } + + @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; } + return new StatementProxy(statement,sql); } - protected boolean process(String[] names, Method method, boolean process) { + protected boolean process(final String[] names, Method method, boolean process) { for (int i=0; (!process) && i<names.length; i++) { - process = (method.getName()==names[i]); + process = compare(method.getName(),names[i]); } return process; } + protected class QueryStats { + private final String query; + private int nrOfInvocations; + private long maxInvocationTime; + private long maxInvocationDate; + private long minInvocationTime; + private long minInvocationDate; + private long totalInvocationTime; + + public QueryStats(String query) { + this.query = query; + } + + public void add(long invocationTime) { + long now = -1; + //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++; + totalInvocationTime+=invocationTime; + } + + public String getQuery() { + return query; + } + + public int getNrOfInvocations() { + return nrOfInvocations; + } + + public long getMaxInvocationTime() { + return maxInvocationTime; + } + + public long getMaxInvocationDate() { + return maxInvocationDate; + } + + public long getMinInvocationTime() { + return minInvocationTime; + } + + public long getMinInvocationDate() { + return minInvocationDate; + } + + public long getTotalInvocationTime() { + return totalInvocationTime; + } + + public int hashCode() { + return query.hashCode(); + } + + public boolean equals(Object other) { + if (other instanceof QueryStats) { + QueryStats qs = (QueryStats)other; + return SlowQueryReport.this.compare(qs.query,this.query); + } + return false; + } + } + protected class StatementProxy implements InvocationHandler { - protected Object parent; - protected Object[] args; - public StatementProxy(Object parent, Object[] args) { - this.parent = parent; - this.args = args; + protected boolean closed = false; + protected Object delegate; + protected final String query; + public StatementProxy(Object parent, String query) { + this.delegate = parent; + this.query = query; } + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - if (this.parent == null ) throw new SQLException("Statement has been closed."); + final String name = method.getName(); + boolean close = compare(JdbcInterceptor.CLOSE_VAL,name); + if (close && closed) return null; //allow close to be called multiple times + if (closed) throw new SQLException("Statement closed."); boolean process = false; process = process(executes, method, process); long start = (process)?System.currentTimeMillis():0; //execute the query - Object result = method.invoke(parent,args); + Object result = method.invoke(delegate,args); long delta = (process)?(System.currentTimeMillis()-start):0; - if (delta>10) { - StringBuffer out = new StringBuffer("\n\tType:"); - out.append(parent.getClass().getName()); - out.append("\n\tCreate/Prepare args:"); - for (int i=0; this.args!=null && i<this.args.length;i++) { - out.append(this.args[i]!=null?this.args[i]:"null"); - out.append("; "); + 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); } - out.append("\n\tExecute args:"); - for (int i=0; args!=null && i<args.length;i++) { - out.append(args[i]!=null?args[i]:"null"); - out.append("; "); - } - System.out.println("Slow query:"+out+"\nTime to execute:"+(delta)+" ms."); + qs.add(delta); + return qs; } - if (JdbcInterceptor.CLOSE_VAL==method.getName()) { - this.parent = null; - this.args = null; + if (close) { + closed=true; + delegate = 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; + } + }; + } } } --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]