아시는분이 Java Agent에 대한 정보를 주셔서 해당 기능을 Tomcat 요청에 대한 모니터링툴을 만들어보고 있다.
현재 겉모습은 제니퍼의 X-View 와 비슷하게 따라 하려는중이다.ㅎㅎ
1차적으로 요청 URI, 요청 시간, 종료 시간, 처리 시간, HttpStatus 값 정도만 차트에 표현하였다.
구조는 Tomat에 Agent를 띄운 후 Agent에서 모니터링툴 WAS에 WebSocket 로 보낸 후 데이터를 받은 모니터링툴에서 모니터링에 접속한 Client Browser 에 WebSocket로 데이터를 보내주는 방식이다.
모니터링툴에 붙은 Client가 많았을 경우 어떻게 될지 아직 잘 모르겠다.
상황봐서 Queue에 담아서 한다던가 고려해볼 생각이고 현재에는 비슷하게 구현만 하는게 목적이다.
인터넷의 자료를 통해서 javassist 를 활용하여 Byte Code Instrumentation 시도해보았지만, 일반적인 Java Main Program은 잘 되는데 Tomcat 의 특정 Class를 제어 하려고 했더니 잘 되지 않았다.
많은 삽질 끝에 ClassPool을 얻어 오는 부분을 조금 수정 하니 잘 되었다.
(이 부분때문에 몇일을 삽질 하다.. 포기도 할까 하고, Application Level Servlet Filter를 이용하는 방법으로 선회 할까도 했음...)
검색을 하두 해서 어디서 이 정보를 얻었는지 기억이 안나네요.
[해결 부분]
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new LoaderClassPath(classLoader));
[2015. 7. 6.]
WAS Application 요청시 org.apache.catalina.core.StandardEngineValve.invoke(Request request, Response response) 부분을 잡아 처리 하였다.
(만들어 가며 바뀔 수도 있는 부분임..)
이렇게 함으로 인해서 어떠한 문제가 발생할지는 미지수임...
HttpStatus=302 케이스에서 문제 발생
[2015. 8. 10.]
javax.servlet.http.HttpServlet.service(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse 으로 변경
이러면.. JSP Direct 호출이 안 잡히는군.. 쉽지 않구만...
다시 org.apache.catalina.core.StandardEngineValve.invoke(Request request, Response response) 원복!!
Spring Security LOGOUT HttpStatus 302일때문 문제가 발생!!
문제 발생 부분 request.getSession().getId()
이 부분을 Before 일때만 하고, After 일때는 주석
Before, After 요청 정보 맵핑을 Thread.currentThread().getId() 로 처리
우선 잘 된다...
[MonitorAgent.java]
package pe.kr.ddakker.monitor.agent;
import java.lang.instrument.Instrumentation;
import pe.kr.ddakker.monitor.agent.transformer.TomcatTransformer;
public class MonitorAgent {
public static void premain(String args, Instrumentation inst) throws Exception {
inst.addTransformer(new TomcatTransformer());
}
public static void agentmain(String args, Instrumentation inst) throws Exception {
premain(args, inst);
}
}
[TomcatTransformer.java]
package pe.kr.ddakker.monitor.agent.transformer;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtBehavior;
import javassist.CtClass;
import javassist.LoaderClassPath;
import javassist.NotFoundException;
/**
* Tomcat 요청 가로채서 수정!!
* @author ddakker 2015. 6. 14.
*/
public class TomcatTransformer implements ClassFileTransformer {
ClassPool pool = null;
public TomcatTransformer() {
this.pool = ClassPool.getDefault();
}
public byte[] transform(ClassLoader loader, String className, Class redefiningClass, ProtectionDomain domain,
byte[] bytes) throws IllegalClassFormatException {
if (className.contains("StandardEngineValve")) {
System.out.println("className: " + className);
return transformClass(redefiningClass, bytes);
} else {
return bytes;
}
}
private byte[] transformClass(Class classToTransform, byte[] b) {
CtClass cl = null;
try {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new LoaderClassPath(classLoader));
if (pool != null) {
cl = pool.makeClass(new java.io.ByteArrayInputStream(b));
if (cl.isInterface() == false) {
CtBehavior[] methods = cl.getDeclaredBehaviors();
for (int i = 0; i < methods.length; i++) {
System.out.println("methods[" + i + "]: " + methods[i]);
if (methods[i].isEmpty() == false) {
doTransform(methods[i]);
}
}
}
b = cl.toBytecode();
}
} catch (Exception e) {
System.err.println("e111: " + e);
} finally {
if (cl != null) {
cl.detach();
}
}
return b;
}
private void doTransform(CtBehavior method) throws NotFoundException, CannotCompileException {
if (method.getName().equals("invoke")) {
System.out.println("0");
try {
System.out.println("1");
method.insertBefore(""
+ "String jvmRoute = System.getProperty(\"jvmRoute\");"
+ "String sessionId = $1.getSession().getId();"
+ "String uri = $1.getRequestURI();"
+ "pe.kr.ddakker.monitor.websocket.WSClient.send(\"{"
+ "server: '\" + jvmRoute + \"' "
+ ", sessionId: '\" + sessionId + \"' "
+ ", uri: '\" + uri + \"' "
+ ", stTime: '\" + System.currentTimeMillis() + \"' "
+ "}\");");
System.out.println("2");
method.insertAfter(""
+ "String jvmRoute = System.getProperty(\"jvmRoute\");"
+ "String sessionId = $1.getSession().getId();"
+ "String uri = $1.getRequestURI();"
+ "pe.kr.ddakker.monitor.websocket.WSClient.send(\"{"
+ "server: '\" + jvmRoute + \"' "
+ ", sessionId: '\" + sessionId + \"' "
+ ", uri: '\" + uri + \"' "
+ ", edTime: '\" + System.currentTimeMillis() + \"' "
+ ", status: '\" + $2.getStatus() + \"' "
+ "}\");");
System.out.println("3");
} catch (Exception e) {
System.err.println("Aa e: " + e);
}
System.out.println("4");
}
}
}
[build.gradle]
jar {
manifest {
attributes 'Implementation-Title': 'Gradle Quickstart',
'Implementation-Version': version,
'Premain-Class': 'pe.kr.ddakker.monitor.agent.MonitorAgent',
'Agent-Class': 'pe.kr.ddakker.monitor.agent.MonitorAgent',
'Can-Redefine-Classes': true
}
}
instrument 시 필요한 외부 Library들은 Agent jar에 포함 시켰다.
[실행]
-javaagent:D:\..\tomcat-monitor-agent-1.0.jar