현재 겉모습은 제니퍼의 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