diff --git a/src/main/java/com/profiler/modifier/DefaultModifierRegistry.java b/src/main/java/com/profiler/modifier/DefaultModifierRegistry.java index b9860a65e..b20646381 100644 --- a/src/main/java/com/profiler/modifier/DefaultModifierRegistry.java +++ b/src/main/java/com/profiler/modifier/DefaultModifierRegistry.java @@ -29,9 +29,7 @@ import com.profiler.modifier.db.mysql.MySQLNonRegisteringDriverModifier; import com.profiler.modifier.db.mysql.MySQLPreparedStatementJDBC4Modifier; import com.profiler.modifier.db.mysql.MySQLPreparedStatementModifier; import com.profiler.modifier.db.mysql.MySQLStatementModifier; -import com.profiler.modifier.db.oracle.OraclePreparedStatementModifier; -import com.profiler.modifier.db.oracle.OracleResultSetModifier; -import com.profiler.modifier.db.oracle.OracleStatementModifier; +import com.profiler.modifier.db.oracle.*; import com.profiler.modifier.method.MethodModifier; import com.profiler.modifier.servlet.FilterModifier; import com.profiler.modifier.servlet.HttpServletModifier; @@ -189,16 +187,20 @@ public class DefaultModifierRegistry implements ModifierRegistry { } private void addOracleDriver() { + Modifier oracleDriverModifier = new OracleDriverModifier(byteCodeInstrumentor, agent); + addModifier(oracleDriverModifier); - // TODO oracle의 경우 connection에 대한 impl이 없음. 확인필요. + Modifier mssqlConnectionModifier = new PhysicalConnectionModifier(byteCodeInstrumentor, agent); + addModifier(mssqlConnectionModifier); +// Modifier oraclePreparedStatementModifier = new OraclePreparedStatementModifier(byteCodeInstrumentor, agent); addModifier(oraclePreparedStatementModifier); - - Modifier oracleStatement = new OracleStatementModifier(byteCodeInstrumentor, agent); - addModifier(oracleStatement); - - Modifier oracleResultSetModifier = new OracleResultSetModifier(byteCodeInstrumentor, agent); - addModifier(oracleResultSetModifier); +// +// Modifier oracleStatement = new OracleStatementModifier(byteCodeInstrumentor, agent); +// addModifier(oracleStatement); +// +// Modifier oracleResultSetModifier = new OracleResultSetModifier(byteCodeInstrumentor, agent); +// addModifier(oracleResultSetModifier); } private void addCubridDriver() { diff --git a/src/main/java/com/profiler/modifier/db/interceptor/PreparedStatementExecuteQueryInterceptor.java b/src/main/java/com/profiler/modifier/db/interceptor/PreparedStatementExecuteQueryInterceptor.java index f3f2f59df..2702c96bb 100644 --- a/src/main/java/com/profiler/modifier/db/interceptor/PreparedStatementExecuteQueryInterceptor.java +++ b/src/main/java/com/profiler/modifier/db/interceptor/PreparedStatementExecuteQueryInterceptor.java @@ -68,6 +68,8 @@ public class PreparedStatementExecuteQueryInterceptor implements SimpleAroundInt trace.recordApi(descriptor); // trace.recordApi(apiId); // clean 타이밍을 변경해야 될듯 하다. + // clearParameters api가 따로 있으나, 구지 캡쳐 하지 않아도 될듯함.시간남으면 하면 좋기는 함. + // ibatis 등에서 확인해봐도 cleanParameters 의 경우 대부분의 경우 일부러 호출하지 않음. clean(target); diff --git a/src/main/java/com/profiler/modifier/db/oracle/OracleDriverModifier.java b/src/main/java/com/profiler/modifier/db/oracle/OracleDriverModifier.java new file mode 100644 index 000000000..00fd782b3 --- /dev/null +++ b/src/main/java/com/profiler/modifier/db/oracle/OracleDriverModifier.java @@ -0,0 +1,57 @@ +package com.profiler.modifier.db.oracle; + +import com.profiler.Agent; +import com.profiler.interceptor.Interceptor; +import com.profiler.interceptor.bci.ByteCodeInstrumentor; +import com.profiler.interceptor.bci.InstrumentClass; +import com.profiler.interceptor.bci.InstrumentException; +import com.profiler.logging.Logger; +import com.profiler.logging.LoggerFactory; +import com.profiler.modifier.AbstractModifier; +import com.profiler.modifier.db.interceptor.DriverConnectInterceptor; + +import java.security.ProtectionDomain; + +/** + * + */ +public class OracleDriverModifier extends AbstractModifier { + +// oracle.jdbc.driver + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + public OracleDriverModifier(ByteCodeInstrumentor byteCodeInstrumentor, Agent agent) { + super(byteCodeInstrumentor, agent); + } + + public String getTargetClass() { + return "oracle/jdbc/driver/OracleDriver"; + } + + public byte[] modify(ClassLoader classLoader, String javassistClassName, ProtectionDomain protectedDomain, byte[] classFileBuffer) { + if (logger.isInfoEnabled()) { + logger.info("Modifing. " + javassistClassName); + } + this.byteCodeInstrumentor.checkLibrary(classLoader, javassistClassName); + try { + InstrumentClass mysqlConnection = byteCodeInstrumentor.getClass(javassistClassName); + + + Interceptor createConnection = new DriverConnectInterceptor(); + String[] params = new String[]{ + "java.lang.String", "java.util.Properties" + }; + mysqlConnection.addInterceptor("connect", params, createConnection); + + printClassConvertComplete(javassistClassName); + + return mysqlConnection.toBytecode(); + } catch (InstrumentException e) { + if (logger.isWarnEnabled()) { + logger.warn(this.getClass().getSimpleName() + " modify fail. Cause:" + e.getMessage(), e); + } + return null; + } + } +} diff --git a/src/main/java/com/profiler/modifier/db/oracle/OraclePreparedStatementModifier.java b/src/main/java/com/profiler/modifier/db/oracle/OraclePreparedStatementModifier.java index e14c00757..716ff01dc 100644 --- a/src/main/java/com/profiler/modifier/db/oracle/OraclePreparedStatementModifier.java +++ b/src/main/java/com/profiler/modifier/db/oracle/OraclePreparedStatementModifier.java @@ -1,89 +1,89 @@ package com.profiler.modifier.db.oracle; import com.profiler.Agent; -import com.profiler.DefaultAgent; -import com.profiler.config.ProfilerConstant; +import com.profiler.interceptor.Interceptor; import com.profiler.interceptor.bci.ByteCodeInstrumentor; +import com.profiler.interceptor.bci.InstrumentClass; +import com.profiler.interceptor.bci.InstrumentException; +import com.profiler.interceptor.bci.NotFoundInstrumentException; +import com.profiler.logging.Logger; import com.profiler.logging.LoggerFactory; import com.profiler.modifier.AbstractModifier; -import com.profiler.trace.DatabaseRequestTracer; -import javassist.CtClass; -import javassist.CtConstructor; -import javassist.CtMethod; +import com.profiler.modifier.db.interceptor.PreparedStatementBindVariableInterceptor; +import com.profiler.modifier.db.interceptor.PreparedStatementExecuteQueryInterceptor; +import com.profiler.util.JavaAssistUtils; +import com.profiler.util.PreparedStatementUtils; + +import java.lang.reflect.Method; import java.security.ProtectionDomain; -import com.profiler.logging.Logger; +import java.util.List; public class OraclePreparedStatementModifier extends AbstractModifier { - - private final Logger logger = LoggerFactory.getLogger(OraclePreparedStatementModifier.class); + private final Logger logger = LoggerFactory.getLogger(this.getClass()); public OraclePreparedStatementModifier(ByteCodeInstrumentor byteCodeInstrumentor, Agent agent) { super(byteCodeInstrumentor, agent); } public String getTargetClass() { - return "oracle/jdbc/driver/OraclePreparedStatement"; + return "oracle/jdbc/driver/OraclePreparedStatementWrapper"; } public byte[] modify(ClassLoader classLoader, String javassistClassName, ProtectionDomain protectedDomain, byte[] classFileBuffer) { if (logger.isInfoEnabled()) { logger.info("Modifing. " + javassistClassName); } + this.byteCodeInstrumentor.checkLibrary(classLoader, javassistClassName); - return changeMethod(javassistClassName, classFileBuffer); - } - - private byte[] changeMethod(String javassistClassName, byte[] classfileBuffer) { try { - CtClass cc = null; - updateSetInternalMethod(cc); - updateExecuteMethod(cc); - updateConstructor(cc); + InstrumentClass preparedStatement = byteCodeInstrumentor.getClass(javassistClassName); - printClassConvertComplete(javassistClassName); + Interceptor execute = new PreparedStatementExecuteQueryInterceptor(); + preparedStatement.addInterceptor("execute", null, execute); + Interceptor executeQuery = new PreparedStatementExecuteQueryInterceptor(); + preparedStatement.addInterceptor("executeQuery", null, executeQuery); + Interceptor executeUpdate = new PreparedStatementExecuteQueryInterceptor(); + preparedStatement.addInterceptor("executeUpdate", null, executeUpdate); - return cc.toBytecode(); - } catch (Exception e) { + preparedStatement.addTraceVariable("__url", "__setUrl", "__getUrl", "java.lang.Object"); + preparedStatement.addTraceVariable("__sql", "__setSql", "__getSql", "java.lang.Object"); + + preparedStatement.addTraceVariable("__bindValue", "__setBindValue", "__getBindValue", "java.util.Map", "java.util.Collections.synchronizedMap(new java.util.HashMap());"); + bindVariableIntercept(preparedStatement, classLoader, protectedDomain); + + return preparedStatement.toBytecode(); + } catch (InstrumentException e) { if (logger.isWarnEnabled()) { - logger.warn(e.getMessage(), e); + logger.warn(this.getClass().getSimpleName() + " modify fail. Cause:" + e.getMessage(), e); + } + return null; + } + + } + + private void bindVariableIntercept(InstrumentClass preparedStatement, ClassLoader classLoader, ProtectionDomain protectedDomain) throws InstrumentException { + List bindMethod = PreparedStatementUtils.findBindVariableSetMethod(); + + Interceptor interceptor = new PreparedStatementBindVariableInterceptor(); + int interceptorId = -1; + for (Method method : bindMethod) { + String methodName = method.getName(); + String[] parameterType = JavaAssistUtils.getParameterType(method.getParameterTypes()); + try { + if (interceptorId == -1) { + interceptorId = preparedStatement.addInterceptor(methodName, parameterType, interceptor); + } else { + preparedStatement.reuseInterceptor(methodName, parameterType, interceptorId); + } + } catch (NotFoundInstrumentException e) { + // bind variable setter메소드를 못찾을 경우는 그냥 경고만 표시, 에러 아님. + if (logger.isTraceEnabled()) { + logger.trace("bindVariable api not found. Cause:" + e.getMessage(), e); + } } } - return null; + } - private void updateSetInternalMethod(CtClass cc) throws Exception { - CtClass[] params1 = new CtClass[2]; - params1[0] = null; - params1[1] = null; - CtMethod serviceMethod1 = cc.getDeclaredMethod("setStringInternal", params1); - - serviceMethod1.insertBefore("{" + DatabaseRequestTracer.FQCN + ".putSqlParam($1,$2); }"); - - // CtClass[] params2 = new CtClass[2]; - // params2[0] = classPool.getCtClass("int"); - // params2[1] = classPool.getCtClass("byte[]"); - // CtMethod serviceMethod2 = cc.getDeclaredMethod("setInternal", - // params2); - // - // serviceMethod2.insertBefore("{" + - // RequestDataTracer.FQCN + - // ".putSqlParam($1,$2); {"); - } - - private void updateConstructor(CtClass cc) throws Exception { - CtConstructor[] constructorList = cc.getConstructors(); - - for (CtConstructor constructor : constructorList) { - CtClass params[] = constructor.getParameterTypes(); - if (params.length == 6) { - constructor.insertBefore("{" + DatabaseRequestTracer.FQCN + ".putSqlQuery(" + ProfilerConstant.REQ_DATA_TYPE_DB_QUERY + ",$2); }"); - } - } - } - - private void updateExecuteMethod(CtClass cc) throws Exception { - CtMethod method = cc.getDeclaredMethod("execute", null); - method.insertAfter("{" + DatabaseRequestTracer.FQCN + ".put(" + ProfilerConstant.REQ_DATA_TYPE_DB_EXECUTE_QUERY + "); }"); - } } diff --git a/src/main/java/com/profiler/modifier/db/oracle/PhysicalConnectionModifier.java b/src/main/java/com/profiler/modifier/db/oracle/PhysicalConnectionModifier.java new file mode 100644 index 000000000..24b0a8970 --- /dev/null +++ b/src/main/java/com/profiler/modifier/db/oracle/PhysicalConnectionModifier.java @@ -0,0 +1,82 @@ +package com.profiler.modifier.db.oracle; + +import com.profiler.Agent; +import com.profiler.interceptor.Interceptor; +import com.profiler.interceptor.bci.ByteCodeInstrumentor; +import com.profiler.interceptor.bci.InstrumentClass; +import com.profiler.interceptor.bci.InstrumentException; +import com.profiler.interceptor.bci.Type; +import com.profiler.logging.Logger; +import com.profiler.logging.LoggerFactory; +import com.profiler.modifier.AbstractModifier; +import com.profiler.modifier.db.interceptor.ConnectionCloseInterceptor; +import com.profiler.modifier.db.interceptor.PreparedStatementCreateInterceptor; +import com.profiler.modifier.db.interceptor.StatementCreateInterceptor; +import com.profiler.modifier.db.interceptor.TransactionInterceptor; + +import java.security.ProtectionDomain; + +public class PhysicalConnectionModifier extends AbstractModifier { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + public PhysicalConnectionModifier(ByteCodeInstrumentor byteCodeInstrumentor, Agent agent) { + super(byteCodeInstrumentor, agent); + } + + public String getTargetClass() { + // T4C , T2C (OCI: T2C를 상속해서 만듬) 의 최상위 구현체가 있으나, + // 해당 클래스는 PhysicalConnection 를 base로 하므로 PhysicalConnection 만 해도 될듯하다. + return "oracle/jdbc/driver/PhysicalConnection"; + } + + public byte[] modify(ClassLoader classLoader, String javassistClassName, ProtectionDomain protectedDomain, byte[] classFileBuffer) { + if (logger.isInfoEnabled()) { + logger.info("Modifing. " + javassistClassName); + } + this.byteCodeInstrumentor.checkLibrary(classLoader, javassistClassName); + try { + InstrumentClass oracleConnection = byteCodeInstrumentor.getClass(javassistClassName); + + + oracleConnection.addTraceVariable("__url", "__setUrl", "__getUrl", "java.lang.Object"); + + // 해당 Interceptor를 공통클래스 만들경우 system에 로드해야 된다. +// Interceptor createConnection = new ConnectionCreateInterceptor(); +// String[] params = new String[] { +// "java.lang.String", "int", "java.util.Properties", "java.lang.String", "java.lang.String" +// }; +// mysqlConnection.addInterceptor("getInstance", params, createConnection); + + + Interceptor closeConnection = new ConnectionCloseInterceptor(); + oracleConnection.addInterceptor("close", null, closeConnection, Type.before); + + Interceptor createStatement = new StatementCreateInterceptor(); + oracleConnection.addInterceptor("createStatement", null, createStatement, Type.after); + + + Interceptor preparedStatement = new PreparedStatementCreateInterceptor(); + oracleConnection.addInterceptor("prepareStatement", new String[]{"java.lang.String"}, preparedStatement); + + + Interceptor setAutocommit = new TransactionInterceptor(); + oracleConnection.addInterceptor("setAutoCommit", new String[]{"boolean"}, setAutocommit); + Interceptor commit = new TransactionInterceptor(); + oracleConnection.addInterceptor("commit", null, commit); + Interceptor rollback = new TransactionInterceptor(); + oracleConnection.addInterceptor("rollback", null, rollback); + + printClassConvertComplete(javassistClassName); + + return oracleConnection.toBytecode(); + } catch (InstrumentException e) { + if (logger.isWarnEnabled()) { + logger.warn(this.getClass().getSimpleName() + " modify fail. Cause:" + e.getMessage(), e); + } + return null; + } + } + + +} diff --git a/src/main/java/com/profiler/modifier/tomcat/interceptors/StandardHostValveInvokeInterceptor.java b/src/main/java/com/profiler/modifier/tomcat/interceptors/StandardHostValveInvokeInterceptor.java index 9e06c8d0b..58f8f8407 100644 --- a/src/main/java/com/profiler/modifier/tomcat/interceptors/StandardHostValveInvokeInterceptor.java +++ b/src/main/java/com/profiler/modifier/tomcat/interceptors/StandardHostValveInvokeInterceptor.java @@ -18,12 +18,13 @@ import com.profiler.util.NumberUtils; public class StandardHostValveInvokeInterceptor implements SimpleAroundInterceptor, ByteCodeMethodDescriptorSupport, TraceContextSupport { - private final Logger logger = LoggerFactory.getLogger(StandardHostValveInvokeInterceptor.class.getName()); + private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final boolean isDebug = logger.isInfoEnabled(); private MethodDescriptor descriptor; private TraceContext traceContext; +// private ContainerAcceptor acceptor = new ContainerAcceptor(logger, ServiceType.TOMCAT); @Override public void before(Object target, Object[] args) { @@ -37,6 +38,8 @@ public class StandardHostValveInvokeInterceptor implements SimpleAroundIntercept HttpServletRequest request = (HttpServletRequest) args[0]; String requestURL = request.getRequestURI(); String remoteAddr = request.getRemoteAddr(); + String port = Integer.toString(request.getServerPort()); + String endPoint = request.getServerName() + ":" + port; // remote call에 sampling flag가 설정되어있을 경우는 샘플링 대상으로 삼지 않는다. boolean sampling = samplingEnable(request); @@ -83,8 +86,8 @@ public class StandardHostValveInvokeInterceptor implements SimpleAroundIntercept trace.recordServiceType(ServiceType.TOMCAT); trace.recordRpcName(requestURL); - int port = request.getServerPort(); - trace.recordEndPoint(request.getServerName() + ((port > 0) ? ":" + port : "")); + + trace.recordEndPoint(endPoint); trace.recordRemoteAddr(remoteAddr); // 서버 맵을 통계정보에서 조회하려면 remote로 호출되는 WAS의 관계를 알아야해서 부모의 application name을 전달받음.