Commit 4500a1db authored by Matt Tucker's avatar Matt Tucker Committed by matt

Backing out of dynamic proxy logic for connection profiling, as it was having...

Backing out of dynamic proxy logic for connection profiling, as it was having problems. Instead, use JAR of compiled classes that will run on 1.5 or 1.6.

git-svn-id: http://svn.igniterealtime.org/svn/repos/wildfire/trunk@6658 b35dd754-fafc-0310-a699-88a17e54d16e
parent 969e85b4
......@@ -374,6 +374,15 @@
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/build/lib/merge/dbutil.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntryProperties />
</component>
</module>
......
......@@ -9,6 +9,7 @@ commons-el.jar | Jetty 6.0.1 (1.0)
commons-httpclient.jar | 3.0
commons-codec.jar | 1.3
dom4j.jar | 1.6.1
dbutil.jar | Jive Code, no release version.
hsqldb.jar | 1.8.0.5
jetty.jar | Jetty 6.1.0 SVN SNAPSHOT (11/15/2006)
jetty-util.jar | Jetty 6.1.0 SVN SNAPSHOT (11/16/2006)
......
/**
* $RCSfile$
* $Revision: 3055 $
* $Date: 2005-11-10 21:57:51 -0300 (Thu, 10 Nov 2005) $
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.database;
import org.jivesoftware.util.Log;
import java.util.List;
import java.util.ArrayList;
import java.sql.Types;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* Allows PreparedStatement information to be cached. A prepared statement consists of
* a SQL statement containing bind variables as well as variable values. For example,
* the SQL statement <tt>"SELECT * FROM person WHERE age > ?"</tt> would have the integer
* variable <tt>18</tt> (which replaces the "?" chracter) to find all adults. This class
* encapsulates both the SQL string and bind variable values so that actual
* PreparedStatement can be created from that information later.
*
* @author Matt Tucker
*/
public class CachedPreparedStatement {
private String sql;
private List<Object> params;
private List<Integer> types;
/**
* Constructs a new CachedPreparedStatement.
*/
public CachedPreparedStatement() {
params = new ArrayList<Object>();
types = new ArrayList<Integer>();
}
/**
* Constructs a new CachedPreparedStatement.
*
* @param sql the SQL.
*/
public CachedPreparedStatement(String sql) {
this();
setSQL(sql);
}
/**
* Returns the SQL.
*
* @return the SQL.
*/
public String getSQL() {
return sql;
}
/**
* Sets the SQL.
*
* @param sql the SQL.
*/
public void setSQL(String sql) {
this.sql = sql;
}
/**
* Adds a boolean parameter to the prepared statement.
*
* @param value the boolean value.
*/
public void addBoolean(boolean value) {
params.add(value);
types.add(Types.BOOLEAN);
}
/**
* Adds an integer parameter to the prepared statement.
*
* @param value the int value.
*/
public void addInt(int value) {
params.add(value);
types.add(Types.INTEGER);
}
/**
* Adds a long parameter to the prepared statement.
*
* @param value the long value.
*/
public void addLong(long value) {
params.add(value);
types.add(Types.BIGINT);
}
/**
* Adds a String parameter to the prepared statement.
*
* @param value the String value.
*/
public void addString(String value) {
params.add(value);
types.add(Types.VARCHAR);
}
/**
* Sets all parameters on the given PreparedStatement. The standard code block
* for turning a CachedPreparedStatement into a PreparedStatement is as follows:
*
* <pre>
* PreparedStatement pstmt = con.prepareStatement(cachedPstmt.getSQL());
* cachedPstmt.setParams(pstmt);
* </pre>
*
* @param pstmt the prepared statement.
* @throws java.sql.SQLException if an SQL Exception occurs.
*/
public void setParams(PreparedStatement pstmt) throws SQLException {
for (int i=0; i<params.size(); i++) {
Object param = params.get(i);
int type = types.get(i);
// Set param, noting fact that params start at 1 and not 0.
switch(type) {
case Types.INTEGER:
pstmt.setInt(i+1, (Integer)param);
break;
case Types.BIGINT:
pstmt.setLong(i+1, (Long)param);
break;
case Types.VARCHAR:
pstmt.setString(i+1, (String)param);
break;
case Types.BOOLEAN:
pstmt.setBoolean(i+1, (Boolean)param);
}
}
}
public boolean equals(Object object) {
if (object == null) {
return false;
}
if (!(object instanceof CachedPreparedStatement)) {
return false;
}
if (this == object) {
return true;
}
CachedPreparedStatement otherStmt = (CachedPreparedStatement)object;
return (sql == null && otherStmt.sql == null) || sql != null && sql.equals(otherStmt.sql)
&& types.equals(otherStmt.types) && params.equals(otherStmt.params);
}
public int hashCode() {
int hashCode = 1;
if (sql != null) {
hashCode += sql.hashCode();
}
hashCode = hashCode * 31 + types.hashCode();
hashCode = hashCode * 31 + params.hashCode();
return hashCode;
}
public String toString() {
String toStringSql = sql;
try {
int index = toStringSql.indexOf('?');
int count = 0;
while (index > -1) {
Object param = params.get(count);
int type = types.get(count);
String val = null;
// Get param
switch(type) {
case Types.INTEGER:
val = "" + param;
break;
case Types.BIGINT:
val = "" + param;
break;
case Types.VARCHAR:
val = '\'' + (String) param + '\'';
break;
case Types.BOOLEAN:
val = "" + param;
}
toStringSql = toStringSql.substring(0, index) + val +
((index == toStringSql.length() -1) ? "" : toStringSql.substring(index + 1));
index = toStringSql.indexOf('?', index + val.length());
count++;
}
}
catch (Exception e) {
Log.error(e);
}
return "CachedPreparedStatement{ sql=" + toStringSql + '}';
}
}
......@@ -141,7 +141,7 @@ public class ConnectionPool implements Runnable {
wrapper.checkedout = true;
wrapper.lockTime = System.currentTimeMillis();
}
return wrapper.getConnection();
return wrapper;
}
else {
synchronized (waitLock) {
......@@ -156,7 +156,7 @@ public class ConnectionPool implements Runnable {
wrapper.checkedout = true;
wrapper.lockTime = System.currentTimeMillis();
}
return wrapper.getConnection();
return wrapper;
}
else {
waitLock.wait();
......
/**
* $RCSfile$
* $Revision: $
* $Date: $
* $Revision$
* $Date$
*
* Copyright (C) 2007 Jive Software. All rights reserved.
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
......@@ -11,9 +11,6 @@
package org.jivesoftware.database;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
......@@ -24,34 +21,18 @@ import java.sql.SQLException;
*
* @author Jive Software
*/
public class ConnectionWrapper {
public class ConnectionWrapper extends AbstractConnection {
private static Method close;
private static Method metaData;
private static Method prepareStatement;
static {
try {
close = Connection.class.getMethod("close");
metaData = Connection.class.getMethod("getMetaData");
prepareStatement = Connection.class.getMethod("prepareStatement", String.class);
}
catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
ConnectionPool pool;
boolean checkedout = false;
long createTime;
long lockTime;
long checkinTime;
Exception exception;
boolean hasLoggedException = false;
private Connection poolConnection;
public ConnectionPool pool;
public boolean checkedout = false;
public long createTime;
public long lockTime;
public long checkinTime;
public Exception exception;
public boolean hasLoggedException = false;
public ConnectionWrapper(Connection connection, ConnectionPool pool) {
setConnection(connection);
super(connection);
this.pool = pool;
createTime = System.currentTimeMillis();
......@@ -60,24 +41,28 @@ public class ConnectionWrapper {
}
public void setConnection(Connection connection) {
if (connection == null) {
this.poolConnection = null;
}
else {
this.poolConnection = (Connection)java.lang.reflect.Proxy.newProxyInstance(
connection.getClass().getClassLoader(),
connection.getClass().getInterfaces(),
new ConnectionProxy(connection));
}
super.connection = connection;
}
public Connection getConnection() {
return poolConnection;
/**
* Instead of closing the underlying connection, we simply release
* it back into the pool.
*/
public void close() throws SQLException {
synchronized (this) {
checkedout = false;
checkinTime = System.currentTimeMillis();
}
pool.freeConnection();
// Release object references. Any further method calls on the connection will fail.
// super.connection = null;
}
public String toString() {
if (poolConnection != null) {
return poolConnection.toString();
if (connection != null) {
return connection.toString();
}
else {
return "Jive Software Connection Wrapper";
......@@ -87,55 +72,4 @@ public class ConnectionWrapper {
public synchronized boolean isCheckedOut() {
return checkedout;
}
/**
* Dynamic proxy for connection object that returns connection to the pool when
* closing.
*/
public class ConnectionProxy implements InvocationHandler {
private Connection connection;
public ConnectionProxy(Connection connection) {
this.connection = connection;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.equals(prepareStatement)) {
return connection.prepareStatement((String) args[0]);
} else if (method.equals(metaData)) {
return connection.getMetaData();
} else if (method.equals(close)) {
close();
return null;
} else {
// Invoke the method normally if all else fails.
try {
return method.invoke(connection, args);
}
catch (InvocationTargetException ite) {
throw ite.getCause();
}
}
}
/**
* Instead of closing the underlying connection, we simply release
* it back into the pool.
*
* @throws SQLException if an SQL Exception occurs.
*/
private void close() throws SQLException {
synchronized (this) {
checkedout = false;
checkinTime = System.currentTimeMillis();
}
pool.freeConnection();
// Release object references. Any further method calls on the connection will fail.
poolConnection = null;
connection = null;
}
}
}
\ No newline at end of file
}
......@@ -98,7 +98,7 @@ public class DbConnectionManager {
// See if profiling is enabled. If yes, wrap the connection with a
// profiled connection.
if (profilingEnabled) {
return (Connection)ProfiledConnection.newInstance(con);
return new ProfiledConnection(con);
}
else {
return con;
......
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.database;
import java.sql.*;
import java.util.*;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
/**
* Wraps a Connection object and collects statistics about the database queries
* that are performed.<p/>
*
* Statistics of the profiled Connections can be obtained from the static
* methods of this class. Instances of this class are the actual wrappers that
* perform profiling.
*
* @author Jive Software
*/
public class ProfiledConnection implements InvocationHandler {
/**
* The type of the database operation.
*/
public enum Type {
/**
* SELECT database queries.
*/
select,
/**
* UPDATE database queries.
*/
update,
/**
* INSERT database queries.
*/
insert,
/**
* DLETE database queries.
*/
delete
}
private static long startInsertTime = 0;
private static long startUpdateTime = 0;
private static long startSelectTime = 0;
private static long startDeleteTime = 0;
private static long endInsertTime = 0;
private static long endUpdateTime = 0;
private static long endSelectTime = 0;
private static long endDeleteTime = 0;
private static long insertCount = 0;
private static long updateCount = 0;
private static long selectCount = 0;
private static long deleteCount = 0;
private static long totalInsertTime = 0;
private static long totalUpdateTime = 0;
private static long totalSelectTime = 0;
private static long totalDeleteTime = 0;
private static Map<String, ProfiledConnectionEntry> insertQueries =
new Hashtable<String, ProfiledConnectionEntry>();
private static Map<String, ProfiledConnectionEntry> updateQueries =
new Hashtable<String, ProfiledConnectionEntry>();
private static Map<String, ProfiledConnectionEntry> selectQueries =
new Hashtable<String, ProfiledConnectionEntry>();
private static Map<String, ProfiledConnectionEntry> deleteQueries =
new Hashtable<String, ProfiledConnectionEntry>();
/**
* Start profiling.
*/
public static void start() {
long now = System.currentTimeMillis();
startInsertTime = startUpdateTime = startSelectTime = startDeleteTime = now;
}
/**
* Stop profiling.
*/
public static void stop() {
endInsertTime = endUpdateTime = endSelectTime = endDeleteTime = 0;
}
/**
* Returns the total number database queries of a particular type performed.
* Valid types are {@link ProfiledConnection.Type#select},
* {@link ProfiledConnection.Type#update}, {@link ProfiledConnection.Type#insert},
* and {@link ProfiledConnection.Type#delete}
*
* @param type the type of query to get the count for.
* @return the number queries of type <tt>type</tt> performed.
*/
public static long getQueryCount(Type type) {
switch (type) {
case select:
return selectCount;
case update:
return updateCount;
case insert:
return insertCount;
case delete:
return deleteCount;
default:
throw new IllegalArgumentException("Invalid type");
}
}
/**
* Adds a query to the list of those that have been run.
*
* @param type the query type.
* @param sql the insert sql string.
* @param time the length of time the query took in milliseconds
*/
public static void addQuery(Type type, String sql, long time) {
// Do nothing if we didn't receive a sql statement
if (sql == null || sql.equals("")) {
return;
}
// clean up sql to insert spaces after every ','
sql = reformatQuery(sql);
// remove values from query
sql = removeQueryValues(sql);
ProfiledConnectionEntry entry;
switch (type) {
case select:
selectCount++;
totalSelectTime += time;
entry = selectQueries.get(sql);
if (entry == null) {
entry = new ProfiledConnectionEntry(sql);
selectQueries.put(sql, entry);
}
break;
case update:
updateCount++;
totalUpdateTime += time;
entry = updateQueries.get(sql);
if (entry == null) {
entry = new ProfiledConnectionEntry(sql);
updateQueries.put(sql, entry);
}
break;
case insert:
insertCount++;
totalInsertTime += time;
entry = insertQueries.get(sql);
if (entry == null) {
entry = new ProfiledConnectionEntry(sql);
insertQueries.put(sql, entry);
}
break;
case delete:
deleteCount++;
totalDeleteTime += time;
entry = deleteQueries.get(sql);
if (entry == null) {
entry = new ProfiledConnectionEntry(sql);
deleteQueries.put(sql, entry);
}
break;
default:
throw new IllegalArgumentException("Invalid type");
}
entry.count++;
entry.totalTime += time;
}
/**
* Returns the average number of queries of a certain type that have been
* performed per second since profiling started. If profiling has been
* stopped, that moment in time is used for the calculation. Otherwise,
* the current moment in time is used.
*
* @param type the type of database query to check.
* @return the average number of queries of a certain typed performed per
* second.
*/
public static double getQueriesPerSecond(Type type) {
long count, start, end;
switch (type) {
case select:
count = selectCount;
start = startSelectTime;
end = endSelectTime;
break;
case update:
count = updateCount;
start = startUpdateTime;
end = endUpdateTime;
break;
case insert:
count = insertCount;
start = startInsertTime;
end = endInsertTime;
break;
case delete:
count = deleteCount;
start = startDeleteTime;
end = endDeleteTime;
break;
default:
throw new IllegalArgumentException("Invalid type");
}
// if no queries yet, return 0;
if (count == 0) {
return 0;
}
// If the profiling hasn't been stopped yet, we want to give
// profiling values up to the current time instead.
if (end == 0) {
end = System.currentTimeMillis();
}
// Compute the number of seconds
double time = (end - start) / 1000.0;
// Finally, return the average.
return count / time;
}
/**
* Returns the average amount of time spent executing the specified type
* of query.
*
* @param type the type of query.
* @return a double representing the average time spent executing the type
* of query.
*/
public static double getAverageQueryTime(Type type) {
long time, count;
switch (type) {
case select:
count = selectCount;
time = totalSelectTime;
break;
case update:
count = updateCount;
time = totalUpdateTime;
break;
case insert:
count = insertCount;
time = totalInsertTime;
break;
case delete:
count = deleteCount;
time = totalDeleteTime;
break;
default:
throw new IllegalArgumentException("Invalid type");
}
if (count != 0) {
return time / (double)count;
}
else {
return 0.0;
}
}
/**
* Returns the total amount of time in milliseconds spent doing a particular
* type of query. Note that this isn't necessarily representative of actual real
* time since db queries often occur in parallel.
*
* @param type the type of query to check.
* @return the number of milliseconds spent executing the specified type of
* query.
*/
public static long getTotalQueryTime(Type type) {
switch (type) {
case select:
return totalSelectTime;
case update:
return totalUpdateTime;
case insert:
return totalInsertTime;
case delete:
return totalDeleteTime;
default:
throw new IllegalArgumentException("Invalid type");
}
}
/**
* Returns an array of sorted queries (as ProfiledConnectionEntry objects) by type
*
* @param type the type of query to check
* @param sortByTime sort the resulting list by Time if true,
* otherwise sort by count if false (default)
* @return an array of ProfiledConnectionEntry objects
*/
public static ProfiledConnectionEntry[] getSortedQueries(Type type, boolean sortByTime) {
Map<String, ProfiledConnectionEntry> queries;
switch (type) {
case select:
queries = selectQueries;
break;
case update:
queries = updateQueries;
break;
case insert:
queries = insertQueries;
break;
case delete:
queries = deleteQueries;
break;
default:
throw new IllegalArgumentException("Invalid type");
}
// No queries, return null
if (queries.isEmpty()) {
return null;
}
ProfiledConnectionEntry [] result = queries.values().toArray(
new ProfiledConnectionEntry[queries.size()]);
quickSort(result, sortByTime, 0, result.length - 1);
return result;
}
/**
* Reset all statistics.
*/
public static void resetStatistics() {
startInsertTime = startUpdateTime = startSelectTime = startDeleteTime = 0;
endInsertTime = endUpdateTime = endSelectTime = endDeleteTime = 0;
insertCount = updateCount = selectCount = deleteCount = 0;
totalInsertTime = totalUpdateTime = totalSelectTime = totalDeleteTime = 0;
insertQueries.clear();
updateQueries.clear();
selectQueries.clear();
deleteQueries.clear();
}
/**
* @param entries entries
* @param sortByTime sort by time if true, otherwise sort by count
* @param first first index to sort on. Normally 0
* @param last last index to sort on. Normally length -1
*/
private static void quickSort(ProfiledConnectionEntry[] entries, boolean sortByTime, int first, int last) {
// do nothing if array contains fewer than two elements
if (first >= last || entries.length < 2) {
return;
}
swap(entries, first, (first + last) / 2);
int index = first;
for (int i = first + 1; i <= last; i++) {
if (sortByTime && ((entries[first].totalTime / entries[first].count) < (entries[i].totalTime / entries[i].count))) {
swap(entries, ++index, i);
}
else if (!sortByTime && entries[first].count < entries[i].count) {
swap(entries, ++index, i);
}
}
swap(entries, first, index);
quickSort(entries, sortByTime, first, index - 1);
quickSort(entries, sortByTime, index + 1, last);
}
private static void swap(Object[] list, int i, int j) {
Object tmp = list[i];
list[i] = list[j];
list[j] = tmp;
}
private static String removeQueryValues(String _sql) {
int length = _sql.length();
if (_sql.indexOf("=") == -1) {
return _sql;
}
StringBuilder sql = new StringBuilder(_sql);
boolean inValue = false;
boolean afterEquals = false;
boolean hasQuotes = false;
int startValue = -1;
int endValue = -1;
int charRemoved = 0;
for (int x = 0; x < length; x++) {
char c = _sql.charAt(x);
switch (c) {
case '=':
{
if (!afterEquals) {
afterEquals = true;
}
break;
}
case ' ':
{
if (!hasQuotes && inValue) {
endValue = x;
inValue = false;
hasQuotes = false;
afterEquals = false;
}
break;
}
case '\'':
{
if (afterEquals && !inValue) {
startValue = x;
inValue = true;
hasQuotes = true;
}
else if (afterEquals && inValue && hasQuotes) {
endValue = x + 1;
inValue = false;
hasQuotes = false;
afterEquals = false;
}
break;
}
case '-':
{
if (afterEquals && !inValue) {
startValue = x;
inValue = true;
}
break;
}
case '+':
{
if (afterEquals && !inValue) {
startValue = x;
inValue = true;
}
break;
}
case '0':
{
if (afterEquals && !inValue) {
startValue = x;
inValue = true;
}
break;
}
case '1':
{
if (afterEquals && !inValue) {
startValue = x;
inValue = true;
}
break;
}
case '2':
{
if (afterEquals && !inValue) {
startValue = x;
inValue = true;
}
break;
}
case '3':
{
if (afterEquals && !inValue) {
startValue = x;
inValue = true;
}
break;
}
case '4':
{
if (afterEquals && !inValue) {
startValue = x;
inValue = true;
}
break;
}
case '5':
{
if (afterEquals && !inValue) {
startValue = x;
inValue = true;
}
break;
}
case '6':
{
if (afterEquals && !inValue) {
startValue = x;
inValue = true;
}
break;
}
case '7':
{
if (afterEquals && !inValue) {
startValue = x;
inValue = true;
}
break;
}
case '8':
{
if (afterEquals && !inValue) {
startValue = x;
inValue = true;
}
break;
}
case '9':
{
if (afterEquals && !inValue) {
startValue = x;
inValue = true;
}
break;
}
default:
{
if (afterEquals && !inValue) {
afterEquals = false;
}
}
}
if (x == length - 1 && afterEquals) {
endValue = x + 1;
}
if (startValue != -1 && endValue != -1) {
sql.replace(startValue - charRemoved, endValue - charRemoved, "?");
charRemoved += endValue - startValue - 1;
startValue = -1;
endValue = -1;
}
}
return sql.toString();
}
private static String reformatQuery(String _sql) {
int length = _sql.length();
int charAdded = 0;
StringBuilder sql = new StringBuilder(_sql);
for (int x = 0; x < length; x++) {
char c = _sql.charAt(x);
if (c == ',' && x < length - 1 && _sql.charAt(x + 1) != ' ') {
sql.replace(x + charAdded, x + 1 + charAdded, ", ");
charAdded++;
}
}
return sql.toString();
}
//--------------------- Connection Wrapping Code ---------------------//
// Preloaded Method objects that we override.
private static Method close;
private static Method createStatement;
private static Method createStatementWithParams;
private static Method prepareStatement;
private static Method prepareStatementWithParams;
private static Method prepareCall;
private static Method prepareCallWithParams;
static {
try {
close = Connection.class.getMethod("close");
createStatement = Connection.class.getMethod("createStatement");
createStatementWithParams = Connection.class.getMethod("createStatement",
int.class, int.class);
prepareStatement = Connection.class.getMethod("prepareStatement", String.class);
prepareStatementWithParams = Connection.class.getMethod("prepareStatement",
String.class, int.class, int.class);
prepareCall = Connection.class.getMethod("prepareCall", String.class);
prepareCallWithParams = Connection.class.getMethod("prepareCall",
String.class, int.class, int.class);
}
catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
public static Object newInstance(Connection con) {
return java.lang.reflect.Proxy.newProxyInstance(
con.getClass().getClassLoader(),
con.getClass().getInterfaces(),
new ProfiledConnection(con));
}
private Connection connection;
/**
* Creates a new ProfiledConnection that wraps the specified connection.
*
* @param connection the Connection to wrap and collect stats for.
*/
private ProfiledConnection(Connection connection) {
this.connection = connection;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.equals(close)) {
// Close underlying connection.
if (connection != null) {
connection.close();
}
return null;
}
else if (method.equals(prepareStatement)) {
return TimedPreparedStatement.newInstance(
connection.prepareStatement((String)args[0]), (String)args[0]);
}
else if (method.equals(prepareStatementWithParams)) {
return TimedPreparedStatement.newInstance(
connection.prepareStatement((String)args[0], (Integer)args[1], (Integer)args[2]),
(String)args[0]);
}
else if (method.equals(createStatement)) {
return TimedStatement.newInstance(connection.createStatement());
}
else if (method.equals(createStatementWithParams)) {
return TimedStatement.newInstance(connection.createStatement(
(Integer)args[0], (Integer)args[1]));
}
else if (method.equals(prepareCall)) {
return TimedCallableStatement.newInstance(
connection.prepareCall((String)args[0]), (String)args[0]);
}
else if (method.equals(prepareCallWithParams)) {
return TimedCallableStatement.newInstance(
connection.prepareCall((String)args[0], (Integer)args[1], (Integer)args[2]),
(String)args[0]);
}
else {
// Invoke the method normally if all else fails.
try {
return method.invoke(connection, args);
}
catch (InvocationTargetException ite) {
throw ite.getCause();
}
}
}
/**
* A dynamic proxy for the Statement interface that times usage. The class
* does not handle batch queries but should generally work otherwise.
*/
public static class TimedStatement implements InvocationHandler {
// Preloaded Method objects that we override.
private static Method execute;
private static Method executeQuery;
private static Method executeUpdate;
static {
try {
execute = Statement.class.getMethod("execute", String.class);
executeQuery = Statement.class.getMethod("executeQuery", String.class);
executeUpdate = Statement.class.getMethod("executeUpdate", String.class);
}
catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
public static Object newInstance(Statement stmt) {
return java.lang.reflect.Proxy.newProxyInstance(
stmt.getClass().getClassLoader(),
stmt.getClass().getInterfaces(),
new TimedStatement(stmt));
}
private Statement stmt;
/**
* Creates a new TimedStatement dynamic proxy.
*
* @param stmt the Statement to proxy.
*/
private TimedStatement(Statement stmt) {
this.stmt = stmt;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.equals(execute) || method.equals(executeQuery) || method.equals(executeUpdate))
{
long t1 = System.currentTimeMillis();
Object result = method.invoke(stmt, args);
long t2 = System.currentTimeMillis();
String sql = ((String)args[0]).toLowerCase().trim();
if (sql.startsWith("insert")) {
addQuery(Type.insert, sql, t2 - t1);
}
else if (sql.startsWith("update")) {
addQuery(Type.update, sql, t2 - t1);
}
else if (sql.startsWith("delete")) {
addQuery(Type.delete, sql, t2 - t1);
}
else {
addQuery(Type.select, sql, t2 - t1);
}
return result;
}
// Invoke the method normally if all else fails.
try {
return method.invoke(stmt, args);
}
catch (InvocationTargetException ite) {
throw ite.getCause();
}
}
}
/**
* A dynamic proxy for the PreparedStatement interface that times usage.
*/
public static class TimedPreparedStatement implements InvocationHandler {
// Preloaded Method objects that we override.
private static Method execute;
private static Method executeWithParam;
private static Method executeQuery;
private static Method executeQueryWithParam;
private static Method executeUpdate;
private static Method executeUpdateWithParam;
private static Method executeBatch;
static {
try {
execute = PreparedStatement.class.getMethod("execute");
executeWithParam = PreparedStatement.class.getMethod("execute", String.class);
executeQuery = PreparedStatement.class.getMethod("executeQuery");
executeQueryWithParam = PreparedStatement.class.getMethod("executeQuery", String.class);
executeUpdate = PreparedStatement.class.getMethod("executeUpdate");
executeUpdateWithParam = PreparedStatement.class.getMethod("executeUpdate", String.class);
executeBatch = PreparedStatement.class.getMethod("executeBatch");
}
catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
public static Object newInstance(PreparedStatement pstmt, String sql) {
return java.lang.reflect.Proxy.newProxyInstance(
pstmt.getClass().getClassLoader(),
pstmt.getClass().getInterfaces(),
new TimedPreparedStatement(pstmt, sql));
}
private PreparedStatement pstmt;
private String sql;
private Type type = Type.select;
/**
* Creates a new TimedStatement dynamic proxy.
*
* @param pstmt the PreparedStatement to proxy.
* @param sql the SQL.
*/
private TimedPreparedStatement(PreparedStatement pstmt, String sql) {
this.pstmt = pstmt;
this.sql = sql;
// Determine the type of query
String sqlL = sql.toLowerCase().trim();
if (sqlL.startsWith("insert")) {
type = Type.insert;
}
else if (sqlL.startsWith("update")) {
type = Type.update;
}
else if (sqlL.startsWith("delete")) {
type = Type.delete;
}
else {
type = Type.select;
}
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.equals(execute) || method.equals(executeQuery) ||
method.equals(executeUpdate) || method.equals(executeBatch))
{
long t1 = System.currentTimeMillis();
Object result = method.invoke(pstmt, args);
long t2 = System.currentTimeMillis();
switch (type) {
case select:
addQuery(Type.select, sql, t2 - t1);
break;
case update:
addQuery(Type.update, sql, t2 - t1);
break;
case insert:
addQuery(Type.insert, sql, t2 - t1);
break;
case delete:
addQuery(Type.delete, sql, t2 - t1);
break;
}
return result;
}
else if (method.equals(executeWithParam) || method.equals(executeQueryWithParam) ||
method.equals(executeUpdateWithParam))
{
long t1 = System.currentTimeMillis();
Object result = method.invoke(pstmt, args);
long t2 = System.currentTimeMillis();
String sql = ((String)args[0]).toLowerCase().trim();
if (sql.startsWith("insert")) {
addQuery(Type.insert, sql, t2 - t1);
}
else if (sql.startsWith("update")) {
addQuery(Type.update, sql, t2 - t1);
}
else if (sql.startsWith("delete")) {
addQuery(Type.delete, sql, t2 - t1);
}
else {
addQuery(Type.select, sql, t2 - t1);
}
return result;
}
// Invoke the method normally if all else fails.
try {
return method.invoke(pstmt, args);
}
catch (InvocationTargetException ite) {
throw ite.getCause();
}
}
}
/**
* A dynamic proxy for the CallableStatement interface that times usage.
*/
public static class TimedCallableStatement implements InvocationHandler {
// Preloaded Method objects that we override.
private static Method execute;
private static Method executeWithParam;
private static Method executeQuery;
private static Method executeQueryWithParam;
private static Method executeUpdate;
private static Method executeUpdateWithParam;
private static Method executeBatch;
static {
try {
execute = CallableStatement.class.getMethod("execute");
executeWithParam = CallableStatement.class.getMethod("execute", String.class);
executeQuery = CallableStatement.class.getMethod("executeQuery");
executeQueryWithParam = CallableStatement.class.getMethod("executeQuery", String.class);
executeUpdate = CallableStatement.class.getMethod("executeUpdate");
executeUpdateWithParam = CallableStatement.class.getMethod("executeUpdate", String.class);
executeBatch = CallableStatement.class.getMethod("executeBatch");
}
catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
public static Object newInstance(CallableStatement cstmt, String sql) {
return java.lang.reflect.Proxy.newProxyInstance(
cstmt.getClass().getClassLoader(),
cstmt.getClass().getInterfaces(),
new TimedCallableStatement(cstmt, sql));
}
private CallableStatement cstmt;
private String sql;
private Type type = Type.select;
/**
* Creates a new TimedStatement dynamic proxy.
*
* @param cstmt the CallableStatement to proxy.
* @param sql the SQL.
*/
private TimedCallableStatement(CallableStatement cstmt, String sql) {
this.cstmt = cstmt;
this.sql = sql;
// Determine the type of query
String sqlL = sql.toLowerCase().trim();
if (sqlL.startsWith("insert")) {
type = Type.insert;
}
else if (sqlL.startsWith("update")) {
type = Type.update;
}
else if (sqlL.startsWith("delete")) {
type = Type.delete;
}
else {
type = Type.select;
}
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.equals(execute) || method.equals(executeQuery) ||
method.equals(executeUpdate) || method.equals(executeBatch))
{
long t1 = System.currentTimeMillis();
Object result = method.invoke(cstmt, args);
long t2 = System.currentTimeMillis();
switch (type) {
case select:
addQuery(Type.select, sql, t2 - t1);
break;
case update:
addQuery(Type.update, sql, t2 - t1);
break;
case insert:
addQuery(Type.insert, sql, t2 - t1);
break;
case delete:
addQuery(Type.delete, sql, t2 - t1);
break;
}
return result;
}
else if (method.equals(executeWithParam) || method.equals(executeQueryWithParam) ||
method.equals(executeUpdateWithParam))
{
long t1 = System.currentTimeMillis();
Object result = method.invoke(cstmt, args);
long t2 = System.currentTimeMillis();
String sql = ((String)args[0]).toLowerCase().trim();
if (sql.startsWith("insert")) {
addQuery(Type.insert, sql, t2 - t1);
}
else if (sql.startsWith("update")) {
addQuery(Type.update, sql, t2 - t1);
}
else if (sql.startsWith("delete")) {
addQuery(Type.delete, sql, t2 - t1);
}
else {
addQuery(Type.select, sql, t2 - t1);
}
return result;
}
// Invoke the method normally if all else fails.
try {
return method.invoke(cstmt, args);
}
catch (InvocationTargetException ite) {
throw ite.getCause();
}
}
}
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.database;
/**
* Simple class for tracking profiling stats for individual SQL queries.
*
* @author Jive Software
*/
public class ProfiledConnectionEntry {
/**
* The SQL query.
*/
public String sql;
/**
* Number of times the query has been executed.
*/
public int count;
/**
* The total time spent executing the query (in milliseconds).
*/
public int totalTime;
public ProfiledConnectionEntry(String sql) {
this.sql = sql;
count = 0;
totalTime = 0;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment