From ac36bc5e48272c8cd77c46652b6572fee30fa6b6 Mon Sep 17 00:00:00 2001 From: Woonduk Kang Date: Fri, 7 Sep 2012 03:39:11 +0000 Subject: [PATCH] =?UTF-8?q?[=EA=B0=95=EC=9A=B4=EB=8D=95]=20[LUCYSUS-1744]?= =?UTF-8?q?=20mysql=20byte=20code=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-svn-id: http://svn.bds.nhncorp.com/pe/hippo-tomcat-profiler/trunk@599 84d0f5b1-2673-498c-a247-62c4ff18d310 --- .../interceptor/InterceptorRegistry.java | 2 +- .../profiler/interceptor/bci/CodeBuilder.java | 39 ++++++ .../bci/JavaAssistByteCodeInstrumentor.java | 4 +- .../interceptor/bci/JavaAssistClass.java | 119 ++++++------------ .../db/mysql/MySQLConnectionImplModifier.java | 51 ++++---- .../mysql/MySQLPreparedStatementModifier.java | 14 ++- .../CloseConnectionInterceptor.java | 2 +- .../CreateConnectionInterceptor.java | 3 +- .../CreatePreparedStatementInterceptor.java | 35 ++---- .../CreateStatementInterceptor.java | 31 ++--- .../ExecuteQueryMethodInterceptor.java | 31 +---- .../PreparedStatementMethodInterceptor.java | 8 +- .../EntryPointStandardHostValveModifier.java | 2 +- .../com/profiler/util/JavaAssistUtils.java | 48 ++++++- .../java/com/profiler/util/MetaObject.java | 70 +++++++++++ .../interceptor/bci/CodeBuilderTest.java | 27 ++++ .../MySQLConnectionImplModifierTest.java | 25 +++- .../com/profiler/util/TestClassLoader.java | 1 + 18 files changed, 313 insertions(+), 199 deletions(-) create mode 100644 src/main/java/com/profiler/interceptor/bci/CodeBuilder.java create mode 100644 src/main/java/com/profiler/util/MetaObject.java create mode 100644 src/test/java/com/profiler/interceptor/bci/CodeBuilderTest.java diff --git a/src/main/java/com/profiler/interceptor/InterceptorRegistry.java b/src/main/java/com/profiler/interceptor/InterceptorRegistry.java index f45c91995..d724e694e 100644 --- a/src/main/java/com/profiler/interceptor/InterceptorRegistry.java +++ b/src/main/java/com/profiler/interceptor/InterceptorRegistry.java @@ -42,7 +42,7 @@ public class InterceptorRegistry { // 로직이 잘못되었을 경우 에러가 발생하지 않도록 더미를 리턴. return DUMMY; } - return index[key]; + return interceptor; } public static int addInterceptor(Interceptor interceptor) { diff --git a/src/main/java/com/profiler/interceptor/bci/CodeBuilder.java b/src/main/java/com/profiler/interceptor/bci/CodeBuilder.java new file mode 100644 index 000000000..bdb88349b --- /dev/null +++ b/src/main/java/com/profiler/interceptor/bci/CodeBuilder.java @@ -0,0 +1,39 @@ +package com.profiler.interceptor.bci; + +import java.util.Formatter; + +public class CodeBuilder { + + private final StringBuilder codeBlock; + private final Formatter formatter; + + public CodeBuilder() { + this(1024); + } + + public CodeBuilder(int bufferSize) { + this.codeBlock = new StringBuilder(bufferSize); + this.formatter = new Formatter(codeBlock); + } + + public void begin(){ + codeBlock.append('{'); + } + + public void end() { + codeBlock.append('}'); + } + + public void append(String code) { + codeBlock.append(code); + } + + public void format(String format, Object... args) { + formatter.format(format, args); + } + + public String toString() { + return codeBlock.toString(); + } + +} diff --git a/src/main/java/com/profiler/interceptor/bci/JavaAssistByteCodeInstrumentor.java b/src/main/java/com/profiler/interceptor/bci/JavaAssistByteCodeInstrumentor.java index 769f8905d..85e49d859 100644 --- a/src/main/java/com/profiler/interceptor/bci/JavaAssistByteCodeInstrumentor.java +++ b/src/main/java/com/profiler/interceptor/bci/JavaAssistByteCodeInstrumentor.java @@ -73,7 +73,7 @@ public class JavaAssistByteCodeInstrumentor implements ByteCodeInstrumentor { @Override public Class defineClass(ClassLoader classLoader, String defineClass, ProtectionDomain protectedDomain) throws InstrumentException { if (logger.isLoggable(Level.INFO)) { - logger.info("defineClass classLoader:" + classLoader + " class:" + defineClass); + logger.info("defineClass class:" + defineClass + " cl:" + classLoader); } try { @@ -96,7 +96,7 @@ public class JavaAssistByteCodeInstrumentor implements ByteCodeInstrumentor { // 재귀하면서 최하위부터 로드 defineNestedClass(nested, classLoader, protectedDomain); if (logger.isLoggable(Level.INFO)) { - logger.info("defineNestedClass classLoader:" + classLoader + " class:" + nested.getName()); + logger.info("defineNestedClass class:" + nested.getName() + " cl:" + classLoader); } nested.toClass(classLoader, protectedDomain); } diff --git a/src/main/java/com/profiler/interceptor/bci/JavaAssistClass.java b/src/main/java/com/profiler/interceptor/bci/JavaAssistClass.java index 913059c11..7c482fd92 100644 --- a/src/main/java/com/profiler/interceptor/bci/JavaAssistClass.java +++ b/src/main/java/com/profiler/interceptor/bci/JavaAssistClass.java @@ -1,8 +1,6 @@ package com.profiler.interceptor.bci; import java.io.IOException; -import java.util.Arrays; -import java.util.Formatter; import java.util.logging.Level; import java.util.logging.Logger; @@ -36,9 +34,6 @@ public class JavaAssistClass implements InstrumentClass { public boolean insertCodeBeforeConstructor(String[] args, String code) { try { CtConstructor constructor = getConstructor(args); - if (constructor == null) { - return false; - } constructor.insertBefore(code); return true; } catch (Exception e) { @@ -53,9 +48,6 @@ public class JavaAssistClass implements InstrumentClass { public boolean insertCodeAfterConstructor(String[] args, String code) { try { CtConstructor constructor = getConstructor(args); - if (constructor == null) { - return false; - } constructor.insertAfter(code); return true; } catch (Exception e) { @@ -70,9 +62,6 @@ public class JavaAssistClass implements InstrumentClass { public boolean insertCodeBeforeMethod(String methodName, String[] args, String code) { try { CtMethod method = getMethod(methodName, args); - if (method == null) { - return false; - } method.insertBefore(code); return true; } catch (Exception e) { @@ -87,9 +76,6 @@ public class JavaAssistClass implements InstrumentClass { public boolean insertCodeAfterMethod(String methodName, String[] args, String code) { try { CtMethod method = getMethod(methodName, args); - if (method == null) { - return false; - } method.insertAfter(code); return true; } catch (Exception e) { @@ -100,7 +86,6 @@ public class JavaAssistClass implements InstrumentClass { } } - // TODO return type을 별도 exception으로 할지 추가 검토가 필요함. public void addTraceVariable(String variableName, String setterName, String getterName, String variableType) throws InstrumentException { try { CtClass type = instrumentor.getClassPool().get(variableType); @@ -122,35 +107,34 @@ public class JavaAssistClass implements InstrumentClass { } public int addConstructorInterceptor(String[] args, Interceptor interceptor) throws InstrumentException { - return addInterceptor(null, args, interceptor); + return addInterceptor0(null, args, interceptor, Type.auto); } @Override public int addInterceptor(String methodName, String[] args, Interceptor interceptor) throws InstrumentException { - return addInterceptor(methodName, args, interceptor, Type.auto); + return addInterceptor0(methodName, args, interceptor, Type.auto); } @Override public int addInterceptor(String methodName, String[] args, Interceptor interceptor, Type type) throws InstrumentException { - if (interceptor == null) { - throw new IllegalArgumentException("interceptor is null"); - } - CtBehavior behavior = getBehavior(methodName, args); - - return addInterceptor0(methodName, interceptor, type, behavior); + return addInterceptor0(methodName, args, interceptor, type); } - private CtBehavior getBehavior(String methodName, String[] args) throws InstrumentException { + private CtBehavior getBehavior(String methodName, String[] args) throws NotFoundException { if (methodName == null) { return getConstructor(args); } return getMethod(methodName, args); } - private int addInterceptor0(String methodName, Interceptor interceptor, Type type, CtBehavior behavior) throws InstrumentException { - int interceptorId = InterceptorRegistry.addInterceptor(interceptor); + private int addInterceptor0(String methodName, String[] args, Interceptor interceptor, Type type) throws InstrumentException { + if (interceptor == null) { + throw new IllegalArgumentException("interceptor is null"); + } try { + CtBehavior behavior = getBehavior(methodName, args); + int interceptorId = InterceptorRegistry.addInterceptor(interceptor); if (type == Type.auto) { if (interceptor instanceof StaticAroundInterceptor) { addStaticAroundInterceptor(methodName, interceptorId, behavior); @@ -170,12 +154,12 @@ public class JavaAssistClass implements InstrumentClass { } else { throw new IllegalArgumentException("unsupported"); } + return interceptorId; } catch (NotFoundException e) { - throw new InstrumentException("addInterceptor fail. Cause:" + e.getMessage(), e); + throw new InstrumentException(interceptor.getClass().getSimpleName() + " add fail. Cause:" + e.getMessage(), e); } catch (CannotCompileException e) { - throw new InstrumentException("addInterceptor fail. Cause:" + e.getMessage(), e); + throw new InstrumentException(interceptor.getClass().getSimpleName() + "add fail. Cause:" + e.getMessage(), e); } - return interceptorId; } private void addStaticAroundInterceptor(String methodName, int id, CtBehavior method) throws NotFoundException, CannotCompileException { @@ -189,11 +173,11 @@ public class JavaAssistClass implements InstrumentClass { String returnType = getReturnType(behavior); String parameterTypeString = JavaAssistUtils.getParameterDescription(behavior.getParameterTypes()); - StringBuilder after = new StringBuilder(1024); - after.append("{"); - format(after, " %1$s interceptor = (%1$s) com.profiler.interceptor.InterceptorRegistry.getInterceptor(%2$d);", StaticAfterInterceptor.class.getName(), id); - format(after, " interceptor.after(%1$s, \"%2$s\", \"%3$s\", \"%4$s\", $args, %5$s);", target, ctClass.getName(), methodName, parameterTypeString, returnType); - after.append("}"); + CodeBuilder after = new CodeBuilder(); + after.begin(); + after.format(" %1$s interceptor = (%1$s) com.profiler.interceptor.InterceptorRegistry.getInterceptor(%2$d);", StaticAfterInterceptor.class.getName(), id); + after.format(" interceptor.after(%1$s, \"%2$s\", \"%3$s\", \"%4$s\", $args, %5$s);", target, ctClass.getName(), methodName, parameterTypeString, returnType); + after.end(); String buildAfter = after.toString(); if (logger.isLoggable(Level.INFO)) { logger.info("addStaticAfterInterceptor after behavior:" + behavior.getLongName() + " code:" + buildAfter); @@ -201,12 +185,12 @@ public class JavaAssistClass implements InstrumentClass { behavior.insertAfter(buildAfter); - StringBuilder catchCode = new StringBuilder(1024); - catchCode.append("{"); - format(catchCode, " %1$s interceptor = (%1$s) com.profiler.interceptor.InterceptorRegistry.getInterceptor(%2$d);", StaticAfterInterceptor.class.getName(), id); - format(catchCode, " interceptor.after(%1$s, \"%2$s\", \"%3$s\", \"%4$s\", $args, $e);", target, ctClass.getName(), methodName, parameterTypeString); + CodeBuilder catchCode = new CodeBuilder(); + catchCode.begin(); + catchCode.format(" %1$s interceptor = (%1$s) com.profiler.interceptor.InterceptorRegistry.getInterceptor(%2$d);", StaticAfterInterceptor.class.getName(), id); + catchCode.format(" interceptor.after(%1$s, \"%2$s\", \"%3$s\", \"%4$s\", $args, $e);", target, ctClass.getName(), methodName, parameterTypeString); catchCode.append(" throw $e;"); - catchCode.append("}"); + catchCode.end(); String buildCatch = catchCode.toString(); if (logger.isLoggable(Level.INFO)) { logger.info("addStaticAfterInterceptor catch behavior:" + behavior.getLongName() + " code:" + buildCatch); @@ -217,7 +201,7 @@ public class JavaAssistClass implements InstrumentClass { } private String getTarget(CtBehavior behavior) { - boolean staticMethod = isStatic(behavior); + boolean staticMethod = JavaAssistUtils.isStaticBehavior(behavior); if (staticMethod) { return "null"; } else { @@ -235,20 +219,17 @@ public class JavaAssistClass implements InstrumentClass { return "($w)$_"; } - private boolean isStatic(CtBehavior behavior) { - int modifiers = behavior.getModifiers(); - return java.lang.reflect.Modifier.isStatic(modifiers); - } + private void addStaticBeforeInterceptor(String methodName, int id, CtBehavior behavior) throws CannotCompileException, NotFoundException { - StringBuilder code = new StringBuilder(1024); - code.append("{"); - format(code, " %1$s interceptor = (%1$s) com.profiler.interceptor.InterceptorRegistry.getInterceptor(%2$d);", StaticBeforeInterceptor.class.getName(), id); + CodeBuilder code = new CodeBuilder(); + code.begin(); + code.format(" %1$s interceptor = (%1$s) com.profiler.interceptor.InterceptorRegistry.getInterceptor(%2$d);", StaticBeforeInterceptor.class.getName(), id); String target = getTarget(behavior); // 인터셉터 호출시 최대한 연산량을 줄이기 위해서 정보는 가능한 정적 데이터로 생성한다. String parameterDescription = JavaAssistUtils.getParameterDescription(behavior.getParameterTypes()); - format(code, " interceptor.before(%1$s, \"%2$s\", \"%3$s\", \"%4$s\", $args);", target, ctClass.getName(), methodName, parameterDescription ); - code.append("}"); + code.format(" interceptor.before(%1$s, \"%2$s\", \"%3$s\", \"%4$s\", $args);", target, ctClass.getName(), methodName, parameterDescription); + code.end(); String buildBefore = code.toString(); if (logger.isLoggable(Level.INFO)) { logger.info("addStaticBeforeInterceptor catch behavior:" + behavior.getLongName() + " code:" + buildBefore); @@ -261,12 +242,6 @@ public class JavaAssistClass implements InstrumentClass { } } - private void format(StringBuilder codeBlock, String format, Object... args) { - Formatter formatter = new Formatter(codeBlock); - formatter.format(format, args); - } - - public boolean addDebugLogBeforeAfterMethod() { String className = this.ctClass.getName(); LoggingInterceptor loggingInterceptor = new LoggingInterceptor(className); @@ -354,34 +329,18 @@ public class JavaAssistClass implements InstrumentClass { return paramsStr; } - private CtMethod getMethod(String methodName, String[] args) throws InstrumentException { - try { - CtClass[] params = getCtParameter(args); - return ctClass.getDeclaredMethod(methodName, params); - } catch (NotFoundException e) { - throw new InstrumentException(methodName + Arrays.toString(args) + " method not found. Cause:" + e.getMessage(), e); - } + private CtMethod getMethod(String methodName, String[] args) throws NotFoundException { + CtClass[] params = JavaAssistUtils.getCtParameter(args, instrumentor.getClassPool()); + // cttime에는 직접 구현클래스를 조작해야 되므로 상속관계의 method를 찾으면 안됨. + return ctClass.getDeclaredMethod(methodName, params); + } + + private CtConstructor getConstructor(String[] args) throws NotFoundException { + CtClass[] params = JavaAssistUtils.getCtParameter(args, instrumentor.getClassPool()); + return ctClass.getDeclaredConstructor(params); } - private CtConstructor getConstructor(String[] args) throws InstrumentException { - try { - CtClass[] params = getCtParameter(args); - return ctClass.getDeclaredConstructor(params); - } catch (NotFoundException e) { - throw new InstrumentException(Arrays.toString(args) + " Constructor not found. Cause:" + e.getMessage(), e); - } - } - private CtClass[] getCtParameter(String[] args) throws NotFoundException { - if (args == null) { - return null; - } - CtClass[] params = new CtClass[args.length]; - for (int i = 0; i < args.length; i++) { - params[i] = instrumentor.getClassPool().getCtClass(args[i]); - } - return params; - } @Override public byte[] toBytecode() { diff --git a/src/main/java/com/profiler/modifier/db/mysql/MySQLConnectionImplModifier.java b/src/main/java/com/profiler/modifier/db/mysql/MySQLConnectionImplModifier.java index 811e50951..e36d3c79c 100644 --- a/src/main/java/com/profiler/modifier/db/mysql/MySQLConnectionImplModifier.java +++ b/src/main/java/com/profiler/modifier/db/mysql/MySQLConnectionImplModifier.java @@ -34,9 +34,6 @@ public class MySQLConnectionImplModifier extends AbstractModifier { checkLibrary(classLoader, javassistClassName); try { InstrumentClass mysqlConnection = byteCodeInstrumentor.getClass(javassistClassName); - if (mysqlConnection == null) { - return null; - } Interceptor createConnection = newInterceptor(classLoader, protectedDomain, "com.profiler.modifier.db.mysql.interceptors.CreateConnectionInterceptor"); Interceptor closeConnection = newInterceptor(classLoader, protectedDomain, "com.profiler.modifier.db.mysql.interceptors.CloseConnectionInterceptor"); @@ -49,39 +46,39 @@ public class MySQLConnectionImplModifier extends AbstractModifier { mysqlConnection.addInterceptor("getInstance", params, createConnection); mysqlConnection.addInterceptor("close", null, closeConnection); mysqlConnection.addInterceptor("createStatement", null, createStatement); - mysqlConnection.addInterceptor("prepareStatement", new String[]{"java.lang.String"}, createStatement); + mysqlConnection.addInterceptor("prepareStatement", new String[]{"java.lang.String"}, preparedStatement); printClassConvertComplete(javassistClassName); return mysqlConnection.toBytecode(); } catch (InstrumentException e) { if (logger.isLoggable(Level.WARNING)) { - logger.log(Level.WARNING, e.getMessage(), e); + logger.log(Level.WARNING, this.getClass().getSimpleName() + " modify fail. Cause:" + e.getMessage(), e); } + return null; } - return null; } - private void updateCreateStatementMethod(CtClass cc) throws Exception { - CtMethod method = cc.getDeclaredMethod("createStatement", null); - method.insertAfter("{" + DatabaseRequestTracer.FQCN + ".put(" + TomcatProfilerConstant.REQ_DATA_TYPE_DB_CREATE_STATEMENT + "); }"); - } - - private void updateGetInstanceMethod(CtClass cc) throws Exception { - CtClass[] params = new CtClass[5]; - params[0] = classPool.getCtClass("java.lang.String"); - params[1] = classPool.getCtClass("int"); - params[2] = classPool.getCtClass("java.util.Properties"); - params[3] = classPool.getCtClass("java.lang.String"); - params[4] = classPool.getCtClass("java.lang.String"); - CtMethod method = cc.getDeclaredMethod("getInstance", params); - - method.insertAfter("{" + DatabaseRequestTracer.FQCN + ".putConnection(" + TomcatProfilerConstant.REQ_DATA_TYPE_DB_GET_CONNECTION + ",$5); }"); - } - - private void updateCloseMethod(CtClass cc) throws Exception { - CtMethod method = cc.getDeclaredMethod("close", null); - method.insertAfter("{" + DatabaseRequestTracer.FQCN + ".put(" + TomcatProfilerConstant.REQ_DATA_TYPE_DB_CLOSE_CONNECTION + "); }"); - } +// private void updateCreateStatementMethod(CtClass cc) throws Exception { +// CtMethod method = cc.getDeclaredMethod("createStatement", null); +// method.insertAfter("{" + DatabaseRequestTracer.FQCN + ".put(" + TomcatProfilerConstant.REQ_DATA_TYPE_DB_CREATE_STATEMENT + "); }"); +// } +// +// private void updateGetInstanceMethod(CtClass cc) throws Exception { +// CtClass[] params = new CtClass[5]; +// params[0] = classPool.getCtClass("java.lang.String"); +// params[1] = classPool.getCtClass("int"); +// params[2] = classPool.getCtClass("java.util.Properties"); +// params[3] = classPool.getCtClass("java.lang.String"); +// params[4] = classPool.getCtClass("java.lang.String"); +// CtMethod method = cc.getDeclaredMethod("getInstance", params); +// +// method.insertAfter("{" + DatabaseRequestTracer.FQCN + ".putConnection(" + TomcatProfilerConstant.REQ_DATA_TYPE_DB_GET_CONNECTION + ",$5); }"); +// } +// +// private void updateCloseMethod(CtClass cc) throws Exception { +// CtMethod method = cc.getDeclaredMethod("close", null); +// method.insertAfter("{" + DatabaseRequestTracer.FQCN + ".put(" + TomcatProfilerConstant.REQ_DATA_TYPE_DB_CLOSE_CONNECTION + "); }"); +// } } 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 02ba1d08f..d9b91133f 100644 --- a/src/main/java/com/profiler/modifier/db/mysql/MySQLPreparedStatementModifier.java +++ b/src/main/java/com/profiler/modifier/db/mysql/MySQLPreparedStatementModifier.java @@ -1,6 +1,7 @@ package com.profiler.modifier.db.mysql; import java.security.ProtectionDomain; +import java.sql.Connection; import java.util.logging.Level; import java.util.logging.Logger; @@ -25,6 +26,8 @@ public class MySQLPreparedStatementModifier extends AbstractModifier { public String getTargetClass() { return "com/mysql/jdbc/PreparedStatement"; + // 상속관계일 경우 byte코드를 수정할 객체를 타겟으로해야 됨. +// return "com/mysql/jdbc/JDBC4PreparedStatement"; } public byte[] modify(ClassLoader classLoader, String javassistClassName, ProtectionDomain protectedDomain, byte[] classFileBuffer) { @@ -36,9 +39,16 @@ public class MySQLPreparedStatementModifier extends AbstractModifier { try { InstrumentClass preparedStatement = byteCodeInstrumentor.getClass(javassistClassName); Interceptor interceptor = newInterceptor(classLoader, protectedDomain, "com.profiler.modifier.db.mysql.interceptors.PreparedStatementMethodInterceptor"); + preparedStatement.addInterceptor("executeQuery", null, interceptor); - preparedStatement.addInterceptor("prepareStatement", new String[]{"java.lang.String"}, interceptor); + preparedStatement.addTraceVariable("__url", "__setUrl", "__getUrl", "java.lang.String"); + preparedStatement.addTraceVariable("__sql", "__setSql", "__getSql", "java.lang.String"); + + return preparedStatement.toBytecode(); } catch (InstrumentException e) { + if (logger.isLoggable(Level.WARNING)) { + logger.log(Level.WARNING, this.getClass().getSimpleName() + " modify fail. Cause:" + e.getMessage(), e); + } return null; } @@ -53,7 +63,7 @@ public class MySQLPreparedStatementModifier extends AbstractModifier { // InstrumentClass aClass = byteCodeInstrumentor.getClass(javassistClassName); // aClass.addInterceptor("executeQuery", null, interceptor); - return changeMethod(javassistClassName, classFileBuffer); +// return changeMethod(javassistClassName, classFileBuffer); } private byte[] changeMethod(String javassistClassName, byte[] classfileBuffer) { diff --git a/src/main/java/com/profiler/modifier/db/mysql/interceptors/CloseConnectionInterceptor.java b/src/main/java/com/profiler/modifier/db/mysql/interceptors/CloseConnectionInterceptor.java index ff1b9bf93..472f2a444 100644 --- a/src/main/java/com/profiler/modifier/db/mysql/interceptors/CloseConnectionInterceptor.java +++ b/src/main/java/com/profiler/modifier/db/mysql/interceptors/CloseConnectionInterceptor.java @@ -16,7 +16,7 @@ public class CloseConnectionInterceptor implements StaticBeforeInterceptor { if (logger.isLoggable(Level.INFO)) { logger.info("before " + className + "." + methodName + parameterDescription + " args:" + Arrays.toString(args)); } - + // close의 경우 호출이 실패하더라도 데이터를 삭제해야함. if (target instanceof Connection) { ConnectionTrace connectionTrace = ConnectionTrace.getConnectionTrace(); connectionTrace.closeConnection((Connection) target); diff --git a/src/main/java/com/profiler/modifier/db/mysql/interceptors/CreateConnectionInterceptor.java b/src/main/java/com/profiler/modifier/db/mysql/interceptors/CreateConnectionInterceptor.java index 735408018..7215ed012 100644 --- a/src/main/java/com/profiler/modifier/db/mysql/interceptors/CreateConnectionInterceptor.java +++ b/src/main/java/com/profiler/modifier/db/mysql/interceptors/CreateConnectionInterceptor.java @@ -18,8 +18,7 @@ public class CreateConnectionInterceptor implements StaticAfterInterceptor { if (logger.isLoggable(Level.INFO)) { logger.info("after " + className + "." + methodName + parameterDescription + " args:" + Arrays.toString(args) + " result:" + result); } - - if (InterceptorUtils.isThrowable(result)) { + if (!InterceptorUtils.isSuccess(result)) { return; } diff --git a/src/main/java/com/profiler/modifier/db/mysql/interceptors/CreatePreparedStatementInterceptor.java b/src/main/java/com/profiler/modifier/db/mysql/interceptors/CreatePreparedStatementInterceptor.java index 4b0b5b641..9020a4d3a 100644 --- a/src/main/java/com/profiler/modifier/db/mysql/interceptors/CreatePreparedStatementInterceptor.java +++ b/src/main/java/com/profiler/modifier/db/mysql/interceptors/CreatePreparedStatementInterceptor.java @@ -3,9 +3,9 @@ package com.profiler.modifier.db.mysql.interceptors; import com.profiler.context.Trace; import com.profiler.interceptor.StaticAfterInterceptor; import com.profiler.modifier.db.ConnectionTrace; +import com.profiler.util.InterceptorUtils; +import com.profiler.util.MetaObject; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.sql.Connection; import java.util.Arrays; import java.util.logging.Level; @@ -14,41 +14,28 @@ import java.util.logging.Logger; public class CreatePreparedStatementInterceptor implements StaticAfterInterceptor { private final Logger logger = Logger.getLogger(CreatePreparedStatementInterceptor.class.getName()); - private Method setUrl = null; + private final MetaObject setUrl = new MetaObject("__setUrl", String.class); + private final MetaObject setSql = new MetaObject("__setSql", String.class); @Override public void after(Object target, String className, String methodName, String parameterDescription, Object[] args, Object result) { if (logger.isLoggable(Level.INFO)) { logger.info("after " + className + "." + methodName + parameterDescription + " args:" + Arrays.toString(args) + " result:" + result); } + if(!InterceptorUtils.isSuccess(result)) { + return; + } if (Trace.getCurrentTraceId() == null) { return; } if (target instanceof Connection) { ConnectionTrace connectionTrace = ConnectionTrace.getConnectionTrace(); String connectionUrl = connectionTrace.getConnectionUrl((Connection) target); - setUrl(result, connectionUrl); + this.setUrl.invoke(result, connectionUrl); + String sql = (String) args[0]; + this.setSql.invoke(result, sql); } } - private void setUrl(Object result, String connectionUrl) { - try { - if (setUrl == null) { - setUrl = result.getClass().getMethod("__setUrl", String.class); - } - setUrl.invoke(result, connectionUrl); - } catch (NoSuchMethodException e) { - if (logger.isLoggable(Level.WARNING)) { - logger.log(Level.WARNING, e.getMessage(), e); - } - } catch (InvocationTargetException e) { - if (logger.isLoggable(Level.WARNING)) { - logger.log(Level.WARNING, e.getMessage(), e); - } - } catch (IllegalAccessException e) { - if (logger.isLoggable(Level.WARNING)) { - logger.log(Level.WARNING, e.getMessage(), e); - } - } - } + } diff --git a/src/main/java/com/profiler/modifier/db/mysql/interceptors/CreateStatementInterceptor.java b/src/main/java/com/profiler/modifier/db/mysql/interceptors/CreateStatementInterceptor.java index 1f2734274..e384bc997 100644 --- a/src/main/java/com/profiler/modifier/db/mysql/interceptors/CreateStatementInterceptor.java +++ b/src/main/java/com/profiler/modifier/db/mysql/interceptors/CreateStatementInterceptor.java @@ -3,9 +3,9 @@ package com.profiler.modifier.db.mysql.interceptors; import com.profiler.context.Trace; import com.profiler.interceptor.StaticAfterInterceptor; import com.profiler.modifier.db.ConnectionTrace; +import com.profiler.util.InterceptorUtils; +import com.profiler.util.MetaObject; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.sql.Connection; import java.util.Arrays; import java.util.logging.Level; @@ -15,41 +15,24 @@ public class CreateStatementInterceptor implements StaticAfterInterceptor { private final Logger logger = Logger.getLogger(CreateStatementInterceptor.class.getName()); - private Method setUrl = null; + private final MetaObject setUrl = new MetaObject("__setUrl", String.class); @Override public void after(Object target, String className, String methodName, String parameterDescription, Object[] args, Object result) { if (logger.isLoggable(Level.INFO)) { logger.info("after " + className + "." + methodName + parameterDescription + " args:" + Arrays.toString(args) + " result:" + result); } + if(!InterceptorUtils.isSuccess(result)) { + return; + } if (Trace.getCurrentTraceId() == null) { return; } if (target instanceof Connection) { ConnectionTrace connectionTrace = ConnectionTrace.getConnectionTrace(); String connectionUrl = connectionTrace.getConnectionUrl((Connection) target); - setUrl(result, connectionUrl); + setUrl.invoke(result, connectionUrl); } } - private void setUrl(Object result, String connectionUrl) { - try { - if (setUrl == null) { - setUrl = result.getClass().getMethod("__setUrl", String.class); - } - setUrl.invoke(result, connectionUrl); - } catch (NoSuchMethodException e) { - if (logger.isLoggable(Level.WARNING)) { - logger.log(Level.WARNING, e.getMessage(), e); - } - } catch (InvocationTargetException e) { - if (logger.isLoggable(Level.WARNING)) { - logger.log(Level.WARNING, e.getMessage(), e); - } - } catch (IllegalAccessException e) { - if (logger.isLoggable(Level.WARNING)) { - logger.log(Level.WARNING, e.getMessage(), e); - } - } - } } 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 e225ece27..4daf24aa8 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 @@ -4,9 +4,8 @@ import com.profiler.StopWatch; import com.profiler.context.Annotation; import com.profiler.context.Trace; import com.profiler.interceptor.StaticAroundInterceptor; +import com.profiler.util.MetaObject; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.Arrays; import java.util.logging.Level; import java.util.logging.Logger; @@ -20,7 +19,7 @@ public class ExecuteQueryMethodInterceptor implements StaticAroundInterceptor { private final Logger logger = Logger.getLogger(ExecuteQueryMethodInterceptor.class.getName()); - private Method getUrl = null; + private final MetaObject getUrl = new MetaObject("", "__getUrl"); @Override public void before(Object target, String className, String methodName, String parameterDescription, Object[] args) { @@ -38,7 +37,7 @@ public class ExecuteQueryMethodInterceptor implements StaticAroundInterceptor { /** * If method was not called by request handler, we skip tagging. */ - String url = getUrl(target); + String url = (String) this.getUrl.invoke(target); Trace.recordRpcName("mysql", url); if (args.length > 0) { @@ -55,30 +54,8 @@ public class ExecuteQueryMethodInterceptor implements StaticAroundInterceptor { } } - private String getUrl(Object target) { - try { - // TODO classloading 시 해당 mehtod를 한번에 가져올수 없는지 검토. - if (getUrl == null) { - getUrl = target.getClass().getMethod("__getUrl"); - } - return (String) getUrl.invoke(target); - } catch (NoSuchMethodException e) { - if (logger.isLoggable(Level.WARNING)) { - logger.log(Level.WARNING, e.getMessage(), e); - } - } catch (IllegalAccessException e) { - if (logger.isLoggable(Level.WARNING)) { - logger.log(Level.WARNING, e.getMessage(), e); - } - } catch (InvocationTargetException e) { - if (logger.isLoggable(Level.WARNING)) { - logger.log(Level.WARNING, e.getMessage(), e); - } - } - return null; - } - @Override + @Override public void after(Object target, String className, String methodName, String parameterDescription, Object[] args, Object result) { if (logger.isLoggable(Level.INFO)) { logger.info("after " + className + "." + methodName + parameterDescription + " args:" + Arrays.toString(args) + " result:" + result); 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 5e3f0a5de..c008ca5af 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 @@ -1,4 +1,10 @@ package com.profiler.modifier.db.mysql.interceptors; -public class PreparedStatementMethodInterceptor { +import com.profiler.interceptor.StaticAfterInterceptor; + +public class PreparedStatementMethodInterceptor implements StaticAfterInterceptor { + @Override + public void after(Object target, String className, String methodName, String parameterDescription, Object[] args, Object result) { + //To change body of implemented methods use File | Settings | File Templates. + } } diff --git a/src/main/java/com/profiler/modifier/tomcat/EntryPointStandardHostValveModifier.java b/src/main/java/com/profiler/modifier/tomcat/EntryPointStandardHostValveModifier.java index 9aad41ecc..602ada3ee 100644 --- a/src/main/java/com/profiler/modifier/tomcat/EntryPointStandardHostValveModifier.java +++ b/src/main/java/com/profiler/modifier/tomcat/EntryPointStandardHostValveModifier.java @@ -48,7 +48,7 @@ public class EntryPointStandardHostValveModifier extends AbstractModifier { aClass.addInterceptor("invoke", new String[] { "org.apache.catalina.connector.Request", "org.apache.catalina.connector.Response" }, interceptor); return aClass.toBytecode(); } catch (InstrumentException e) { - logger.log(Level.WARNING, "modify fail Cause:" + e.getMessage(), e); + logger.log(Level.WARNING, "modify fail. Cause:" + e.getMessage(), e); return null; } } diff --git a/src/main/java/com/profiler/util/JavaAssistUtils.java b/src/main/java/com/profiler/util/JavaAssistUtils.java index 52ef9e521..6400e7fb4 100644 --- a/src/main/java/com/profiler/util/JavaAssistUtils.java +++ b/src/main/java/com/profiler/util/JavaAssistUtils.java @@ -1,6 +1,7 @@ package com.profiler.util; -import javassist.CtClass; +import javassist.*; +import javassist.bytecode.Descriptor; public class JavaAssistUtils { private final static String NULL = "()"; @@ -43,4 +44,49 @@ public class JavaAssistUtils { sb.append(")"); return sb.toString(); } + + public static String getParameterDescription(String[] params) { + if(params == null) { + return NULL; + } + StringBuilder sb = new StringBuilder(64); + sb.append("("); + int end = params.length - 1; + for (int i = 0; i < params.length; i++) { + sb.append(params[i]); + if (i < end) { + sb.append(", "); + } + } + sb.append(")"); + return sb.toString(); + } + + public static CtClass[] getCtParameter(String[] args, ClassPool pool) throws NotFoundException { + if (args == null) { + return null; + } + CtClass[] params = new CtClass[args.length]; + for (int i = 0; i < args.length; i++) { + params[i] = pool.getCtClass(args[i]); + } + return params; + } + + public CtMethod findAllMethod(CtClass ctClass, String methodName, String[] args) throws NotFoundException { + CtClass[] params = getCtParameter(args, ctClass.getClassPool()); + String paramDescriptor = Descriptor.ofParameters(params); + CtMethod[] methods = ctClass.getMethods(); + for (CtMethod method : methods) { + if(method.getName().equals(methodName) && method.getMethodInfo2().getDescriptor().startsWith(paramDescriptor)) { + return method; + } + } + throw new NotFoundException(methodName+ "(..) is not found in " + ctClass.getName()); + } + + public static boolean isStaticBehavior(CtBehavior behavior) { + int modifiers = behavior.getModifiers(); + return java.lang.reflect.Modifier.isStatic(modifiers); + } } diff --git a/src/main/java/com/profiler/util/MetaObject.java b/src/main/java/com/profiler/util/MetaObject.java new file mode 100644 index 000000000..7ef7f36d0 --- /dev/null +++ b/src/main/java/com/profiler/util/MetaObject.java @@ -0,0 +1,70 @@ +package com.profiler.util; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class MetaObject { + + private final Logger logger = Logger.getLogger(MetaObject.class.getName()); + + private String methodName; + private Class[] args; + // 이것을 class loading시 정적 타임에서 생성해 둘수 없는가? + private Method methodRef; + + private Object defaultReturnValue = null; + + public MetaObject(String methodName, Class... args) { + this.methodName = methodName; + this.args = args; + } + + public MetaObject(Object defaultReturnValue, String methodName, Class... args) { + this.methodName = methodName; + this.args = args; + this.defaultReturnValue = defaultReturnValue; + } + + public Object invoke(Object target, Object... args) { + if (target == null) { + return defaultReturnValue; + } + + Method method = this.methodRef; + if (method == null) { + // 멀티쓰레드에서 중복 엑세스해도 별 문제 없을것임. + Class aClass = target.getClass(); + method = getMethod(aClass); + this.methodRef = method; + } + return invoke(method, target, args); + } + + private Object invoke(Method method, Object target, Object[] args) { + if (method == null) { + return defaultReturnValue; + } + try { + return method.invoke(target, args); + } catch (IllegalAccessException e) { + logger.log(Level.WARNING, "invoke fail", e); + return defaultReturnValue; + } catch (InvocationTargetException e) { + logger.log(Level.WARNING, "invoke fail", e); + return defaultReturnValue; + } + } + + private Method getMethod(Class aClass) { + try { + return aClass.getDeclaredMethod(this.methodName, this.args); + } catch (NoSuchMethodException e) { + logger.warning(this.methodName + Arrays.toString(this.args) + " not found cls:" + aClass); + return null; + } + } + +} diff --git a/src/test/java/com/profiler/interceptor/bci/CodeBuilderTest.java b/src/test/java/com/profiler/interceptor/bci/CodeBuilderTest.java new file mode 100644 index 000000000..36e0b4c77 --- /dev/null +++ b/src/test/java/com/profiler/interceptor/bci/CodeBuilderTest.java @@ -0,0 +1,27 @@ +package com.profiler.interceptor.bci; + +import org.junit.Assert; +import org.junit.Test; + +public class CodeBuilderTest { + @Test + public void testCodeBuilder() throws Exception { + CodeBuilder builder = new CodeBuilder(); + builder.begin(); + builder.format("1"); + builder.end(); + Assert.assertEquals("{1}", builder.toString()); + } + + @Test + public void testFormat() throws Exception { + CodeBuilder builder = new CodeBuilder(); + builder.begin(); + builder.format("1"); + builder.format("2"); + builder.end(); + Assert.assertEquals("{12}", builder.toString()); + } + + +} 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 30b7c8d50..9b3c75cd9 100644 --- a/src/test/java/com/profiler/modifier/db/mysql/MySQLConnectionImplModifierTest.java +++ b/src/test/java/com/profiler/modifier/db/mysql/MySQLConnectionImplModifierTest.java @@ -7,9 +7,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import java.sql.Connection; -import java.sql.Driver; -import java.sql.Statement; +import java.sql.*; import java.util.Properties; import java.util.Set; import java.util.logging.Logger; @@ -29,6 +27,8 @@ public class MySQLConnectionImplModifierTest { MySQLStatementModifier statementModifier = new MySQLStatementModifier(loader.getInstrumentor()); loader.addModifier(statementModifier); + MySQLPreparedStatementModifier preparedStatementModifier = new MySQLPreparedStatementModifier(loader.getInstrumentor()); + loader.addModifier(preparedStatementModifier); // loader.delegateLoadingOf(ConnectionTrace.class.getName()); @@ -56,9 +56,9 @@ public class MySQLConnectionImplModifierTest { Assert.assertEquals(connectionList.size(), 1); logger.info("connection size:" + connectionList.size()); - Statement statement = connect.createStatement(); - statement.executeQuery("select 1"); - statement.close(); + statement(connect); + + preparedStatement(connect); connect.close(); Assert.assertEquals(connectionList.size(), 0); @@ -67,4 +67,17 @@ public class MySQLConnectionImplModifierTest { Trace.removeCurrentTraceIdFromStack(); } + private void statement(Connection connect) throws SQLException { + Statement statement = connect.createStatement(); + statement.executeQuery("select 1"); + statement.close(); + } + + private void preparedStatement(Connection connect) throws SQLException { + PreparedStatement preparedStatement = connect.prepareStatement("select 1"); + ResultSet resultSet = preparedStatement.executeQuery(); + resultSet.close(); + preparedStatement.close(); + } + } diff --git a/src/test/java/com/profiler/util/TestClassLoader.java b/src/test/java/com/profiler/util/TestClassLoader.java index a5cbeebd5..685504cf1 100644 --- a/src/test/java/com/profiler/util/TestClassLoader.java +++ b/src/test/java/com/profiler/util/TestClassLoader.java @@ -54,6 +54,7 @@ public class TestClassLoader extends Loader { this.delegateLoadingOf(Trace.class.getName()); this.delegateLoadingOf(Annotation.class.getName()); this.delegateLoadingOf(StopWatch.class.getName()); + this.delegateLoadingOf(MetaObject.class.getName()); } @Override