start working on the interceptor for query stats
authorfhanik <fhanik@13f79535-47bb-0310-9956-ffa450edef68>
Thu, 4 Dec 2008 07:07:23 +0000 (07:07 +0000)
committerfhanik <fhanik@13f79535-47bb-0310-9956-ffa450edef68>
Thu, 4 Dec 2008 07:07:23 +0000 (07:07 +0000)
git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@723228 13f79535-47bb-0310-9956-ffa450edef68

modules/jdbc-pool/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReport.java

index aa3efab..a10eb3a 100644 (file)
@@ -21,6 +21,10 @@ import java.lang.reflect.Method;
 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
@@ -30,78 +34,173 @@ import org.apache.tomcat.jdbc.pool.PooledConnection;
  * @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