아시는분이 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

+ Recent posts