diff --git a/src/main/java/com/profiler/interceptor/bci/InstrumentClass.java b/src/main/java/com/profiler/interceptor/bci/InstrumentClass.java index bc084f0d2..97d7d4330 100644 --- a/src/main/java/com/profiler/interceptor/bci/InstrumentClass.java +++ b/src/main/java/com/profiler/interceptor/bci/InstrumentClass.java @@ -8,11 +8,11 @@ public interface InstrumentClass { boolean insertCodeAfterMethod(String methodName, String[] args, String code); - int addConstructorInterceptor(String[] args, Interceptor interceptor) throws InstrumentException; + int addConstructorInterceptor(String[] args, Interceptor interceptor) throws InstrumentException, NotFoundInstrumentException; - int addInterceptor(String methodName, String[] args, Interceptor interceptor) throws InstrumentException; + int addInterceptor(String methodName, String[] args, Interceptor interceptor) throws InstrumentException, NotFoundInstrumentException; - int addInterceptor(String methodName, String[] args, Interceptor interceptor, Type type) throws InstrumentException; + int addInterceptor(String methodName, String[] args, Interceptor interceptor, Type type) throws InstrumentException, NotFoundInstrumentException; boolean addDebugLogBeforeAfterMethod(); @@ -22,7 +22,9 @@ public interface InstrumentClass { Class toClass() throws InstrumentException ; - void addTraceVariable(String variableName, String setterName, String getterName, String variableType) throws InstrumentException ; + void addTraceVariable(String variableName, String setterName, String getterName, String variableType, String initValue) throws InstrumentException; + + void addTraceVariable(String variableName, String setterName, String getterName, String variableType) throws InstrumentException; boolean insertCodeAfterConstructor(String[] args, String code); diff --git a/src/main/java/com/profiler/interceptor/bci/JavaAssistClass.java b/src/main/java/com/profiler/interceptor/bci/JavaAssistClass.java index 07c4a6aee..5d31a5a62 100644 --- a/src/main/java/com/profiler/interceptor/bci/JavaAssistClass.java +++ b/src/main/java/com/profiler/interceptor/bci/JavaAssistClass.java @@ -86,11 +86,23 @@ public class JavaAssistClass implements InstrumentClass { } } + public void addTraceVariable(String variableName, String setterName, String getterName, String variableType, String initValue) throws InstrumentException { + addTraceVariable0(variableName, setterName, getterName, variableType, initValue); + } + public void addTraceVariable(String variableName, String setterName, String getterName, String variableType) throws InstrumentException { + addTraceVariable0(variableName, setterName, getterName, variableType, null); + } + + public void addTraceVariable0(String variableName, String setterName, String getterName, String variableType, String initValue) throws InstrumentException { try { CtClass type = instrumentor.getClassPool().get(variableType); CtField traceVariable = new CtField(type, variableName, ctClass); - ctClass.addField(traceVariable); + if (initValue == null) { + ctClass.addField(traceVariable); + } else { + ctClass.addField(traceVariable, initValue); + } if (setterName != null) { CtMethod setterMethod = CtNewMethod.setter(setterName, traceVariable); ctClass.addMethod(setterMethod); @@ -106,18 +118,18 @@ public class JavaAssistClass implements InstrumentClass { } } - public int addConstructorInterceptor(String[] args, Interceptor interceptor) throws InstrumentException { + public int addConstructorInterceptor(String[] args, Interceptor interceptor) throws InstrumentException, NotFoundInstrumentException { return addInterceptor0(null, args, interceptor, Type.auto); } @Override - public int addInterceptor(String methodName, String[] args, Interceptor interceptor) throws InstrumentException { + public int addInterceptor(String methodName, String[] args, Interceptor interceptor) throws InstrumentException, NotFoundInstrumentException { return addInterceptor0(methodName, args, interceptor, Type.auto); } @Override - public int addInterceptor(String methodName, String[] args, Interceptor interceptor, Type type) throws InstrumentException { + public int addInterceptor(String methodName, String[] args, Interceptor interceptor, Type type) throws InstrumentException, NotFoundInstrumentException { return addInterceptor0(methodName, args, interceptor, type); } @@ -128,12 +140,18 @@ public class JavaAssistClass implements InstrumentClass { return getMethod(methodName, args); } - private int addInterceptor0(String methodName, String[] args, Interceptor interceptor, Type type) throws InstrumentException { + private int addInterceptor0(String methodName, String[] args, Interceptor interceptor, Type type) throws InstrumentException, NotFoundInstrumentException { if (interceptor == null) { throw new IllegalArgumentException("interceptor is null"); } + CtBehavior behavior = null; + try { + behavior = getBehavior(methodName, args); + } catch (NotFoundException e) { + // target method나 constructor를 차지 못했을 경우는 NotFoundInstrumentException을 던진다. + throw new NotFoundInstrumentException(interceptor.getClass().getSimpleName() + " add fail. Cause:" + e.getMessage(), e); + } try { - CtBehavior behavior = getBehavior(methodName, args); int interceptorId = InterceptorRegistry.addInterceptor(interceptor); if (type == Type.auto) { if (interceptor instanceof StaticAroundInterceptor) { diff --git a/src/main/java/com/profiler/interceptor/bci/NotFoundInstrumentException.java b/src/main/java/com/profiler/interceptor/bci/NotFoundInstrumentException.java new file mode 100644 index 000000000..0a701a57e --- /dev/null +++ b/src/main/java/com/profiler/interceptor/bci/NotFoundInstrumentException.java @@ -0,0 +1,19 @@ +package com.profiler.interceptor.bci; + +public class NotFoundInstrumentException extends InstrumentException { + + public NotFoundInstrumentException() { + } + + public NotFoundInstrumentException(String message) { + super(message); + } + + public NotFoundInstrumentException(String message, Throwable cause) { + super(message, cause); + } + + public NotFoundInstrumentException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/com/profiler/modifier/db/mysql/MySQLPreparedStatementModifier.java b/src/main/java/com/profiler/modifier/db/mysql/MySQLPreparedStatementModifier.java index d9b91133f..1ebfdc57a 100644 --- a/src/main/java/com/profiler/modifier/db/mysql/MySQLPreparedStatementModifier.java +++ b/src/main/java/com/profiler/modifier/db/mysql/MySQLPreparedStatementModifier.java @@ -1,11 +1,23 @@ package com.profiler.modifier.db.mysql; +import java.lang.reflect.Method; import java.security.ProtectionDomain; import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListSet; import java.util.logging.Level; import java.util.logging.Logger; import com.profiler.interceptor.bci.InstrumentException; +import com.profiler.interceptor.bci.NotFoundInstrumentException; +import com.profiler.util.ExcludeBindVariableFilter; +import com.profiler.util.JavaAssistUtils; +import com.profiler.util.PreparedStatementUtils; import javassist.CtClass; import javassist.CtConstructor; import javassist.CtMethod; @@ -19,6 +31,7 @@ import com.profiler.trace.DatabaseRequestTracer; public class MySQLPreparedStatementModifier extends AbstractModifier { private final Logger logger = Logger.getLogger(MySQLPreparedStatementModifier.class.getName()); + private final String[] excludes = new String[] { "setRowId", "setNClob", "setSQLXML" }; public MySQLPreparedStatementModifier(ByteCodeInstrumentor byteCodeInstrumentor) { super(byteCodeInstrumentor); @@ -44,6 +57,9 @@ public class MySQLPreparedStatementModifier extends AbstractModifier { preparedStatement.addTraceVariable("__url", "__setUrl", "__getUrl", "java.lang.String"); preparedStatement.addTraceVariable("__sql", "__setSql", "__getSql", "java.lang.String"); + preparedStatement.addTraceVariable("__bindValue", "__setBindValue", "__getBindValue", "java.util.List", "java.util.Collections.synchronizedList(new java.util.LinkedList());"); + bindVariableIntercept(preparedStatement, classLoader, protectedDomain); + return preparedStatement.toBytecode(); } catch (InstrumentException e) { if (logger.isLoggable(Level.WARNING)) { @@ -66,7 +82,26 @@ public class MySQLPreparedStatementModifier extends AbstractModifier { // return changeMethod(javassistClassName, classFileBuffer); } - private byte[] changeMethod(String javassistClassName, byte[] classfileBuffer) { + private void bindVariableIntercept(InstrumentClass preparedStatement, ClassLoader classLoader, ProtectionDomain protectedDomain) throws InstrumentException { + ExcludeBindVariableFilter exclude = new ExcludeBindVariableFilter(excludes); + List bindMethod = PreparedStatementUtils.findBindVariableSetMethod(exclude); + Interceptor interceptor = newInterceptor(classLoader, protectedDomain, "com.profiler.modifier.db.mysql.interceptors.PreparedStatementBindVariableInterceptor"); + for (Method method : bindMethod) { + String methodName = method.getName(); + String[] parameterType = JavaAssistUtils.getParameterType(method.getParameterTypes()); + try { + preparedStatement.addInterceptor(methodName, parameterType , interceptor); + } catch (NotFoundInstrumentException e) { + // bind variable setter메소드를 못찾을 경우는 그냥 경고만 표시 + if (logger.isLoggable(Level.WARNING)) { + logger.log(Level.WARNING, "bindVariable api modify fail. Cause:" + e.getMessage(), e); + } + } + } + } + + + private byte[] changeMethod(String javassistClassName, byte[] classfileBuffer) { try { CtClass cc = classPool.get(javassistClassName); diff --git a/src/main/java/com/profiler/modifier/db/mysql/interceptors/ExecuteQueryMethodInterceptor.java b/src/main/java/com/profiler/modifier/db/mysql/interceptors/ExecuteQueryMethodInterceptor.java index 60009adfa..df0c4f370 100644 --- a/src/main/java/com/profiler/modifier/db/mysql/interceptors/ExecuteQueryMethodInterceptor.java +++ b/src/main/java/com/profiler/modifier/db/mysql/interceptors/ExecuteQueryMethodInterceptor.java @@ -43,7 +43,7 @@ public class ExecuteQueryMethodInterceptor implements StaticAroundInterceptor { Trace.recordRpcName("mysql", url); if (args.length > 0) { - Trace.recordAttibute("Query", args[0]); + Trace.recordAttibute("Statement", args[0]); } Trace.record(Annotation.ClientSend); diff --git a/src/main/java/com/profiler/modifier/db/mysql/interceptors/PreparedStatementBindVariableInterceptor.java b/src/main/java/com/profiler/modifier/db/mysql/interceptors/PreparedStatementBindVariableInterceptor.java new file mode 100644 index 000000000..3f7fbf9ae --- /dev/null +++ b/src/main/java/com/profiler/modifier/db/mysql/interceptors/PreparedStatementBindVariableInterceptor.java @@ -0,0 +1,31 @@ +package com.profiler.modifier.db.mysql.interceptors; + +import com.profiler.context.Trace; +import com.profiler.interceptor.StaticAfterInterceptor; +import com.profiler.util.MetaObject; +import com.profiler.util.StringUtils; + +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class PreparedStatementBindVariableInterceptor implements StaticAfterInterceptor { + private final Logger logger = Logger.getLogger(PreparedStatementBindVariableInterceptor.class.getName()); + + private final MetaObject getBindValue = new MetaObject("__getBindValue"); + + @Override + public void after(Object target, String className, String methodName, String parameterDescription, Object[] args, Object result) { + if (logger.isLoggable(Level.INFO)) { + logger.info("after " + StringUtils.toString(target) + " " + className + "." + methodName + parameterDescription + " args:" + Arrays.toString(args) + " result:" + result); + } + if (Trace.getCurrentTraceId() == null) { + return; + } + List bindList = getBindValue.invoke(target); + String index = StringUtils.toString(args[0]); + String value = StringUtils.toString(args[1]); + bindList.add(index + ":" + value); + } +} diff --git a/src/main/java/com/profiler/modifier/db/mysql/interceptors/PreparedStatementMethodInterceptor.java b/src/main/java/com/profiler/modifier/db/mysql/interceptors/PreparedStatementMethodInterceptor.java index 4e37080a5..d0a6929bb 100644 --- a/src/main/java/com/profiler/modifier/db/mysql/interceptors/PreparedStatementMethodInterceptor.java +++ b/src/main/java/com/profiler/modifier/db/mysql/interceptors/PreparedStatementMethodInterceptor.java @@ -9,7 +9,7 @@ import com.profiler.util.InterceptorUtils; import com.profiler.util.MetaObject; import com.profiler.util.StringUtils; -import java.util.Arrays; +import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; @@ -19,6 +19,9 @@ public class PreparedStatementMethodInterceptor implements StaticAroundIntercept private final MetaObject getSql = new MetaObject("__getSql"); private final MetaObject getUrl = new MetaObject("__getUrl"); + private final MetaObject> getBindValue = new MetaObject("__getBindValue"); + private final MetaObject> setBindValue = new MetaObject("__setBindValue"); + @Override public void before(Object target, String className, String methodName, String parameterDescription, Object[] args) { @@ -30,10 +33,16 @@ public class PreparedStatementMethodInterceptor implements StaticAroundIntercept } Trace.traceBlockBegin(); try { - String url = getUrl.invoke(target, null); + String url = getUrl.invoke(target); Trace.recordRpcName("mysql", url); - String sql = getSql.invoke(target, null); - Trace.recordAttibute("Query", sql); + + String sql = getSql.invoke(target); + Trace.recordAttibute("PreparedStatement", sql); + + List bindValue = getBindValue.invoke(target); + Trace.recordAttibute("BindValue", bindValue.toString()); + setBindValue.invoke(target, Collections.synchronizedList(new LinkedList())); + Trace.record(Annotation.ClientSend); } catch (Exception e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. diff --git a/src/main/java/com/profiler/util/BindVariableFilter.java b/src/main/java/com/profiler/util/BindVariableFilter.java new file mode 100644 index 000000000..b6614f8a7 --- /dev/null +++ b/src/main/java/com/profiler/util/BindVariableFilter.java @@ -0,0 +1,7 @@ +package com.profiler.util; + +import java.lang.reflect.Method; + +public interface BindVariableFilter { + boolean filter(Method method); +} diff --git a/src/main/java/com/profiler/util/ExcludeBindVariableFilter.java b/src/main/java/com/profiler/util/ExcludeBindVariableFilter.java new file mode 100644 index 000000000..0fbb24876 --- /dev/null +++ b/src/main/java/com/profiler/util/ExcludeBindVariableFilter.java @@ -0,0 +1,22 @@ +package com.profiler.util; + +import java.lang.reflect.Method; + +public class ExcludeBindVariableFilter implements BindVariableFilter { + + private String[] excudes; + + public ExcludeBindVariableFilter(String[] excludes) { + this.excudes = excludes; + } + + @Override + public boolean filter(Method method) { + for (String exclude : excudes) { + if(method.getName().equals(exclude)) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/com/profiler/util/IncludeBindVariableFilter.java b/src/main/java/com/profiler/util/IncludeBindVariableFilter.java new file mode 100644 index 000000000..5a1cfe719 --- /dev/null +++ b/src/main/java/com/profiler/util/IncludeBindVariableFilter.java @@ -0,0 +1,21 @@ +package com.profiler.util; + +import java.lang.reflect.Method; + +public class IncludeBindVariableFilter implements BindVariableFilter { + private String[] includes; + + public IncludeBindVariableFilter(String[] includes) { + this.includes = includes; + } + + @Override + public boolean filter(Method method) { + for (String include: includes) { + if(method.getName().equals(include)) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/com/profiler/util/JavaAssistUtils.java b/src/main/java/com/profiler/util/JavaAssistUtils.java index 6400e7fb4..96b865847 100644 --- a/src/main/java/com/profiler/util/JavaAssistUtils.java +++ b/src/main/java/com/profiler/util/JavaAssistUtils.java @@ -28,6 +28,17 @@ public class JavaAssistUtils { return sb.toString(); } + public static String[] getParameterType(Class[] paramsClass) { + if (paramsClass == null) { + return null; + } + String[] paramsString = new String[paramsClass.length]; + for (int i = 0; i < paramsClass.length; i++) { + paramsString[i] = paramsClass[i].getName(); + } + return paramsString; + } + public static String getParameterDescription(Class[] params) { if(params == null) { return NULL; diff --git a/src/main/java/com/profiler/util/PreparedStatementUtils.java b/src/main/java/com/profiler/util/PreparedStatementUtils.java new file mode 100644 index 000000000..ea6231268 --- /dev/null +++ b/src/main/java/com/profiler/util/PreparedStatementUtils.java @@ -0,0 +1,70 @@ +package com.profiler.util; + + +import java.beans.IntrospectionException; +import java.lang.reflect.Method; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Pattern; + +public class PreparedStatementUtils { + + private static final Pattern BIND_SETTER = Pattern.compile("set[A-Z]([a-zA-Z]+)"); + + private static final List bindMethod; + + static { + bindMethod = findBindVariableSetMethod0(); + } + + public static List findBindVariableSetMethod() { + return bindMethod; + } + + public static List findBindVariableSetMethod(BindVariableFilter filter) { + List temp = new ArrayList(bindMethod.size()); + for (Method method : bindMethod) { + if (filter.filter(method)) { + temp.add(method); + } + } + return temp; + } + + static List findBindVariableSetMethod0() { + Method[] methods = PreparedStatement.class.getDeclaredMethods(); + List bindMethod = new LinkedList(); + for (Method method : methods) { + if (isSetter(method.getName())) { + Class[] parameterTypes = method.getParameterTypes(); + + if (parameterTypes.length < 2) { + continue; + } + if (parameterTypes[0] != int.class) { + continue; + } + if (method.getReturnType() != void.class) { + continue; + } + if (method.getExceptionTypes().equals(SQLException.class)) { + continue; + } + bindMethod.add(method); + } + } + return Collections.unmodifiableList(bindMethod); + } + + + public static boolean isSetter(String name) { + if (name == null) { + return false; + } + return BIND_SETTER.matcher(name).matches(); + } +} diff --git a/src/test/java/com/profiler/modifier/db/mysql/MySQLConnectionImplModifierTest.java b/src/test/java/com/profiler/modifier/db/mysql/MySQLConnectionImplModifierTest.java index 9b3c75cd9..1273341b4 100644 --- a/src/test/java/com/profiler/modifier/db/mysql/MySQLConnectionImplModifierTest.java +++ b/src/test/java/com/profiler/modifier/db/mysql/MySQLConnectionImplModifierTest.java @@ -1,5 +1,6 @@ package com.profiler.modifier.db.mysql; +import com.mysql.jdbc.JDBC4PreparedStatement; import com.profiler.context.Trace; import com.profiler.modifier.db.ConnectionTrace; import com.profiler.util.TestClassLoader; @@ -60,6 +61,8 @@ public class MySQLConnectionImplModifierTest { preparedStatement(connect); + preparedStatement2(connect); + connect.close(); Assert.assertEquals(connectionList.size(), 0); logger.info("connection size:" + connectionList.size()); @@ -80,4 +83,20 @@ public class MySQLConnectionImplModifierTest { preparedStatement.close(); } + private void preparedStatement2(Connection connect) throws SQLException { + PreparedStatement preparedStatement = connect.prepareStatement("select * from member where id = ?"); + preparedStatement.setInt(1, 1); + ResultSet resultSet = preparedStatement.executeQuery(); + resultSet.close(); + preparedStatement.close(); + } + + @Test + public void test() throws NoSuchMethodException { +// setNClob(int parameterIndex, NClob value) + JDBC4PreparedStatement.class.getDeclaredMethod("setNClob", new Class[]{int.class, NClob.class}); +// JDBC4PreparedStatement.class.getDeclaredMethod("addBatch", null); + JDBC4PreparedStatement.class.getMethod("addBatch", null); + + } } diff --git a/src/test/java/com/profiler/util/PreparedStatementUtilsTest.java b/src/test/java/com/profiler/util/PreparedStatementUtilsTest.java new file mode 100644 index 000000000..814550299 --- /dev/null +++ b/src/test/java/com/profiler/util/PreparedStatementUtilsTest.java @@ -0,0 +1,29 @@ +package com.profiler.util; + +import org.junit.Assert; +import org.junit.Test; + +import java.beans.BeanInfo; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.sql.PreparedStatement; +import java.util.List; + +public class PreparedStatementUtilsTest { + @Test + public void testBindSetMethod() { + List bindVariableSetMethod = PreparedStatementUtils.findBindVariableSetMethod(); + for (Method method : bindVariableSetMethod) { + System.out.println(method); + } + } + + @Test + public void testMatch() throws Exception { + Assert.assertTrue(PreparedStatementUtils.isSetter("setNCString")); + Assert.assertTrue(PreparedStatementUtils.isSetter("setInt")); + Assert.assertTrue(PreparedStatementUtils.isSetter("setTestTeTst")); + + } +}