From 2de702f55bdac02e58a96317301c7af103e373e8 Mon Sep 17 00:00:00 2001 From: Woonduk Kang Date: Mon, 25 Mar 2013 10:32:29 +0000 Subject: [PATCH] =?UTF-8?q?[=EA=B0=95=EC=9A=B4=EB=8D=95]=20[LUCYSUS-1744]?= =?UTF-8?q?=20=EC=9E=90=EC=B2=B4=20agent=20classLoader=20=EA=B0=9C?= =?UTF-8?q?=EB=B0=9C.?= 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@1352 84d0f5b1-2673-498c-a247-62c4ff18d310 --- src/main/java/com/profiler/Agent.java | 441 +++++++++--------- .../java/com/profiler/AgentBootStrap.java | 11 + .../com/profiler/AgentURLClassLoader.java | 60 +++ .../java/com/profiler/ClassPathResolver.java | 15 +- .../com/profiler/AgentURLClassLoaderTest.java | 56 +++ 5 files changed, 366 insertions(+), 217 deletions(-) create mode 100644 src/main/java/com/profiler/AgentBootStrap.java create mode 100644 src/main/java/com/profiler/AgentURLClassLoader.java create mode 100644 src/test/java/com/profiler/AgentURLClassLoaderTest.java diff --git a/src/main/java/com/profiler/Agent.java b/src/main/java/com/profiler/Agent.java index fd7e3131f..d65ce2819 100644 --- a/src/main/java/com/profiler/Agent.java +++ b/src/main/java/com/profiler/Agent.java @@ -1,11 +1,5 @@ package com.profiler; -import java.io.UnsupportedEncodingException; -import java.util.Map.Entry; -import java.util.Random; -import java.util.logging.Level; -import java.util.logging.Logger; - import com.profiler.common.dto.thrift.AgentInfo; import com.profiler.common.hbase.HBaseTables; import com.profiler.common.mapping.ApiMappingTable; @@ -18,248 +12,263 @@ import com.profiler.sender.UdpDataSender; import com.profiler.util.Assert; import com.profiler.util.NetworkUtils; -public class Agent implements Runnable { +import java.io.UnsupportedEncodingException; +import java.util.Map.Entry; +import java.util.Random; +import java.util.logging.Level; +import java.util.logging.Logger; - private static final Logger logger = Logger.getLogger(Agent.class.getName()); - private static final Random IDENTIFIER_KEY = new Random(); +public class Agent { - private final ProfilerConfig profilerConfig; - private final ServerInfo serverInfo; - private final SystemMonitor systemMonitor; + private static final Logger logger = Logger.getLogger(Agent.class.getName()); + private static final Random IDENTIFIER_KEY = new Random(); - private TraceContext traceContext; + private final ProfilerConfig profilerConfig; + private final ServerInfo serverInfo; + private final SystemMonitor systemMonitor; - private DataSender priorityDataSender; - private DataSender dataSender; + private TraceContext traceContext; - private final String machineName; - private final String agentId; - private final String nodeName; - private final String applicationName; - private final long startTime; - private final short identifier; + private DataSender priorityDataSender; + private DataSender dataSender; - // agent info는 heartbeat에서 매번 사용한다. - private AgentInfo agentInfo; - - // agent의 상태, - private AgentStatus agentStatus; - private Thread heartbeatThread; + private final String machineName; + private final String agentId; + private final String nodeName; + private final String applicationName; + private final long startTime; + private final short identifier; - /** - * collector로 heartbeat을 보낸다. heartbeat의 내용은 agent info. - */ - @Override - public void run() { - try { - logger.info("Send startup information to HIPPO server via " + this.priorityDataSender.getClass().getSimpleName() + ". agentInfo=" + agentInfo); - this.priorityDataSender.send(agentInfo); - this.priorityDataSender.send(agentInfo); - this.priorityDataSender.send(agentInfo); + // agent info는 heartbeat에서 매번 사용한다. + private AgentInfo agentInfo; - logger.info("Starting agent heartbeat."); - while (true) { - if (agentStatus == AgentStatus.RUNNING) { - logger.fine("Send heartbeat"); - this.priorityDataSender.send(agentInfo); - } else if (agentStatus == AgentStatus.STOPPING || agentStatus == AgentStatus.STOPPED) { - break; - } - // TODO 정밀한 시간계산 없이 일단 그냥 interval 단위로 보냄. - Thread.sleep(profilerConfig.getHeartbeatInterval()); - } - } catch (InterruptedException e) { - logger.warning(e.getMessage()); - } - logger.info(Thread.currentThread().getName() + " stopped."); - } + // agent의 상태, + private volatile AgentStatus agentStatus; + private Thread heartbeatThread; - public Agent(ProfilerConfig profilerConfig) { - changeStatus(AgentStatus.INITIALIZING); - - Assert.notNull(profilerConfig, "profilerConfig must not be null"); + /** + * collector로 heartbeat을 보낸다. heartbeat의 내용은 agent info. + */ + private Runnable heartBitCommand = new Runnable() { + @Override + public void run() { + if (logger.isLoggable(Level.INFO)) { + logger.info("Send startup information to HIPPO server via " + priorityDataSender.getClass().getSimpleName() + ". agentInfo=" + agentInfo); + } + priorityDataSender.send(agentInfo); + priorityDataSender.send(agentInfo); + priorityDataSender.send(agentInfo); - this.profilerConfig = profilerConfig; - this.serverInfo = new ServerInfo(); - - // TODO 일단 임시로 호환성을 위해 agentid에 machinename을 넣도록 하자 - // TODO 박스 하나에 서버 인스턴스를 여러개 실행할 때에 문제가 될 수 있음. - this.machineName = NetworkUtils.getMachineName(); - this.agentId = getId("hippo.agentId", machineName, HBaseTables.AGENT_NAME_MAX_LEN); - this.nodeName = System.getProperty("hippo.nodeName", machineName); - this.applicationName = getId("hippo.applicationName", "UnknownApplicationName", HBaseTables.APPLICATION_NAME_MAX_LEN); - - this.priorityDataSender = createDataSender(); - this.dataSender = createDataSender(); - this.startTime = System.currentTimeMillis(); - - this.identifier = getShortIdentifier(); - - initializeTraceContext(); - - this.systemMonitor = new SystemMonitor(this.traceContext, this.profilerConfig); - this.systemMonitor.setDataSender(dataSender); - - // 매핑 테이블 초기화를 위해 엑세스 - ApiMappingTable.findApiId("test", null, null); - - this.agentInfo = createAgentInfo(); - this.heartbeatThread = createHeartbeatThread(); - - SingletonHolder.INSTANCE = this; - } - - private Thread createHeartbeatThread() { - Thread thread = new Thread(this); - thread.setName("HIPPO-Agent-Heartbeat-Thread"); - thread.setDaemon(true); - return thread; - } - - private AgentInfo createAgentInfo() { - String ip = getServerInfo().getHostip(); - String ports = ""; - for (Entry entry : getServerInfo().getConnectors().entrySet()) { - ports += " " + entry.getKey(); - } - - AgentInfo agentInfo = new AgentInfo(); - - agentInfo.setIp(ip); - agentInfo.setHostname(this.machineName); - agentInfo.setPorts(ports); - - agentInfo.setAgentId(getAgentId()); - agentInfo.setIdentifier(this.identifier); - agentInfo.setApplicationName(getApplicationName()); - - agentInfo.setIsAlive(true); - agentInfo.setTimestamp(this.startTime); - - return agentInfo; - } - - private void changeStatus(AgentStatus status) { - this.agentStatus = status; - if (logger.isLoggable(Level.FINE)) { - logger.fine("Agent status is changed. " + status); + long heartbeatInterval = profilerConfig.getHeartbeatInterval(); + if (logger.isLoggable(Level.INFO)) { + logger.info("Starting agent heartbeat. heartbeatInterval:" + heartbeatInterval); + } + while (true) { + if (agentStatus == AgentStatus.RUNNING) { + logger.fine("Send heartbeat"); + priorityDataSender.send(agentInfo); + } else if (agentStatus == AgentStatus.STOPPING || agentStatus == AgentStatus.STOPPED) { + break; + } + // TODO 정밀한 시간계산 없이 일단 그냥 interval 단위로 보냄. + try { + Thread.sleep(heartbeatInterval); + } catch (InterruptedException e) { + break; + } + } + logger.info(Thread.currentThread().getName() + " stopped."); } - } - - private short getShortIdentifier() { - return (short) (IDENTIFIER_KEY.nextInt(65536) - 32768); - } + }; - private void initializeTraceContext() { - this.traceContext = TraceContext.getTraceContext(); - // this.traceContext.setDataSender(this.dataSender); + public Agent(ProfilerConfig profilerConfig) { + changeStatus(AgentStatus.INITIALIZING); - this.traceContext.setAgentId(this.agentId); - this.traceContext.setApplicationId(this.applicationName); - this.traceContext.setPriorityDataSender(this.priorityDataSender); + Assert.notNull(profilerConfig, "profilerConfig must not be null"); - if (profilerConfig.isSamplingElapsedTimeBaseEnable()) { - TimeBaseStorageFactory timeBaseStorageFactory = new TimeBaseStorageFactory(this.dataSender, this.profilerConfig); - this.traceContext.setStorageFactory(timeBaseStorageFactory); - } else { - this.traceContext.setStorageFactory(new BypassStorageFactory(dataSender)); - } - } + this.profilerConfig = profilerConfig; + this.serverInfo = new ServerInfo(); - private UdpDataSender createDataSender() { - return new UdpDataSender(this.profilerConfig.getCollectorServerIp(), this.profilerConfig.getCollectorServerPort()); - } + // TODO 일단 임시로 호환성을 위해 agentid에 machinename을 넣도록 하자 + // TODO 박스 하나에 서버 인스턴스를 여러개 실행할 때에 문제가 될 수 있음. + this.machineName = NetworkUtils.getMachineName(); + this.agentId = getId("hippo.agentId", machineName, HBaseTables.AGENT_NAME_MAX_LEN); + this.nodeName = System.getProperty("hippo.nodeName", machineName); + this.applicationName = getId("hippo.applicationName", "UnknownApplicationName", HBaseTables.APPLICATION_NAME_MAX_LEN); - private String getId(String key, String defaultValue, int maxlen) { - String value = System.getProperty(key, defaultValue); - validateId(value, key, maxlen); - return value; - } + this.priorityDataSender = createDataSender(); + this.dataSender = createDataSender(); + this.startTime = System.currentTimeMillis(); - private void validateId(String id, String idName, int maxlen) { - try { - byte[] bytes = id.getBytes("UTF-8"); - if (bytes.length > maxlen) { - logger.warning(idName + " is too long(1~24). value=" + id); - } - // validate = false; - // TODO 이제 그냥 exception을 던지면 됨 agent 생성 타이밍이 최초 vm스타트와 동일하다. - } catch (UnsupportedEncodingException e) { - logger.log(Level.WARNING, "invalid agentId. Cause:" + e.getMessage(), e); - } - } + this.identifier = getShortIdentifier(); - private static class SingletonHolder { - public static Agent INSTANCE; - } + initializeTraceContext(); - public static Agent getInstance() { - return SingletonHolder.INSTANCE; - } + this.systemMonitor = new SystemMonitor(this.traceContext, this.profilerConfig); + this.systemMonitor.setDataSender(dataSender); - public ServerInfo getServerInfo() { - return this.serverInfo; - } + // 매핑 테이블 초기화를 위해 엑세스 + ApiMappingTable.findApiId("test", null, null); - public String getAgentId() { - return agentId; - } + this.agentInfo = createAgentInfo(); + this.heartbeatThread = createHeartbeatThread(); - public short getIdentifier() { - return identifier; - } + SingletonHolder.INSTANCE = this; + } - public long getStartTime() { - return startTime; - } + private Thread createHeartbeatThread() { + Thread thread = new Thread(heartBitCommand); + thread.setName("HIPPO-Agent-Heartbeat-Thread"); + thread.setDaemon(true); + return thread; + } - public String getApplicationName() { - return applicationName; - } + private AgentInfo createAgentInfo() { + String ip = getServerInfo().getHostip(); + String ports = ""; + for (Entry entry : getServerInfo().getConnectors().entrySet()) { + ports += " " + entry.getKey(); + } - public TraceContext getTraceContext() { - return traceContext; - } + AgentInfo agentInfo = new AgentInfo(); - public String getNodeName() { - return nodeName; - } + agentInfo.setIp(ip); + agentInfo.setHostname(this.machineName); + agentInfo.setPorts(ports); - public boolean isRunning() { - return agentStatus == AgentStatus.RUNNING; - } + agentInfo.setAgentId(getAgentId()); + agentInfo.setIdentifier(this.identifier); + agentInfo.setApplicationName(getApplicationName()); - // TODO 필요없을것 같음 started를 start로 바꿔도 될 듯... - public void start() { - logger.info("Starting HIPPO Agent."); - } + agentInfo.setIsAlive(true); + agentInfo.setTimestamp(this.startTime); - /** - * org/apache/catalina/startup/Catalina/await함수가 호출되기 전에 실행된다. - * Tomcat이 구동되고 context가 모두 로드 된 다음 사용자의 요청을 처리할 수 있게 되었을 때 실행됨. - */ - public void started() { - changeStatus(AgentStatus.RUNNING); - this.heartbeatThread.start(); - } + return agentInfo; + } - public void stop() { - logger.info("Stopping HIPPO Agent."); + private void changeStatus(AgentStatus status) { + this.agentStatus = status; + if (logger.isLoggable(Level.FINE)) { + logger.fine("Agent status is changed. " + status); + } + } - changeStatus(AgentStatus.STOPPING); - systemMonitor.stop(); + private short getShortIdentifier() { + return (short) (IDENTIFIER_KEY.nextInt(65536) - 32768); + } - agentInfo.setIsAlive(false); - - // TODO 개선필요. 특정 collector가 죽더라도 나머지 collector가 받을수 있도록 일부러 중복해서 3번 보낸다. - this.priorityDataSender.send(agentInfo); - this.priorityDataSender.send(agentInfo); - this.priorityDataSender.send(agentInfo); + private void initializeTraceContext() { + this.traceContext = TraceContext.getTraceContext(); + // this.traceContext.setDataSender(this.dataSender); - // 종료 처리 필요. - this.dataSender.stop(); - this.priorityDataSender.stop(); + this.traceContext.setAgentId(this.agentId); + this.traceContext.setApplicationId(this.applicationName); + this.traceContext.setPriorityDataSender(this.priorityDataSender); - changeStatus(AgentStatus.STOPPED); - } + if (profilerConfig.isSamplingElapsedTimeBaseEnable()) { + TimeBaseStorageFactory timeBaseStorageFactory = new TimeBaseStorageFactory(this.dataSender, this.profilerConfig); + this.traceContext.setStorageFactory(timeBaseStorageFactory); + } else { + this.traceContext.setStorageFactory(new BypassStorageFactory(dataSender)); + } + } + + private UdpDataSender createDataSender() { + return new UdpDataSender(this.profilerConfig.getCollectorServerIp(), this.profilerConfig.getCollectorServerPort()); + } + + private String getId(String key, String defaultValue, int maxlen) { + String value = System.getProperty(key, defaultValue); + validateId(value, key, maxlen); + return value; + } + + private void validateId(String id, String idName, int maxlen) { + try { + byte[] bytes = id.getBytes("UTF-8"); + if (bytes.length > maxlen) { + logger.warning(idName + " is too long(1~24). value=" + id); + } + // validate = false; + // TODO 이제 그냥 exception을 던지면 됨 agent 생성 타이밍이 최초 vm스타트와 동일하다. + } catch (UnsupportedEncodingException e) { + logger.log(Level.WARNING, "invalid agentId. Cause:" + e.getMessage(), e); + } + } + + private static class SingletonHolder { + public static Agent INSTANCE; + } + + public static Agent getInstance() { + return SingletonHolder.INSTANCE; + } + + public ServerInfo getServerInfo() { + return this.serverInfo; + } + + public String getAgentId() { + return agentId; + } + + public short getIdentifier() { + return identifier; + } + + public long getStartTime() { + return startTime; + } + + public String getApplicationName() { + return applicationName; + } + + public TraceContext getTraceContext() { + return traceContext; + } + + public String getNodeName() { + return nodeName; + } + + public boolean isRunning() { + return agentStatus == AgentStatus.RUNNING; + } + + // TODO 필요없을것 같음 started를 start로 바꿔도 될 듯... + public void start() { + logger.info("Starting HIPPO Agent."); + } + + /** + * org/apache/catalina/startup/Catalina/await함수가 호출되기 전에 실행된다. + * Tomcat이 구동되고 context가 모두 로드 된 다음 사용자의 요청을 처리할 수 있게 되었을 때 실행됨. + */ + public void started() { + changeStatus(AgentStatus.RUNNING); + this.heartbeatThread.start(); + } + + public void stop() { + logger.info("Stopping HIPPO Agent."); + + changeStatus(AgentStatus.STOPPING); + heartbeatThread.interrupt(); + + systemMonitor.stop(); + + agentInfo.setIsAlive(false); + + // TODO 개선필요. 특정 collector가 죽더라도 나머지 collector가 받을수 있도록 일부러 중복해서 3번 보낸다. + this.priorityDataSender.send(agentInfo); + this.priorityDataSender.send(agentInfo); + this.priorityDataSender.send(agentInfo); + + // 종료 처리 필요. + this.dataSender.stop(); + this.priorityDataSender.stop(); + + changeStatus(AgentStatus.STOPPED); + } } diff --git a/src/main/java/com/profiler/AgentBootStrap.java b/src/main/java/com/profiler/AgentBootStrap.java new file mode 100644 index 000000000..96fb4382c --- /dev/null +++ b/src/main/java/com/profiler/AgentBootStrap.java @@ -0,0 +1,11 @@ +package com.profiler; + +/** + * + */ +public class AgentBootStrap { + void boot() { + System.out.println(this.getClass().getClassLoader()); + + } +} diff --git a/src/main/java/com/profiler/AgentURLClassLoader.java b/src/main/java/com/profiler/AgentURLClassLoader.java new file mode 100644 index 000000000..b8a36d745 --- /dev/null +++ b/src/main/java/com/profiler/AgentURLClassLoader.java @@ -0,0 +1,60 @@ +package com.profiler; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; + +/** + * + */ +public class AgentURLClassLoader { + + private ClassPathResolver classPathResolver = new ClassPathResolver(null); + private URLClassLoader classLoader; + private String bootClass = "com.profiler.AgentBootStrap"; + private String bootMethod = "boot"; + + public AgentURLClassLoader(URL[] urls) { + ClassLoader classLoader = AgentURLClassLoader.class.getClassLoader(); + this.classLoader = new URLClassLoader(urls, classLoader); + } + + public void setBootClass(String bootClass) { + this.bootClass = bootClass; + } + + public void setBootMethod(String bootMethod) { + this.bootMethod = bootMethod; + } + + + public void boot() { + try{ + Class bootStrap = this.classLoader.loadClass(bootClass); + Object agentBootStrap = bootStrap.newInstance(); + + Method bootMethod = bootStrap.getDeclaredMethod(this.bootMethod); + + Thread currentThread = Thread.currentThread(); + ClassLoader before = currentThread.getContextClassLoader(); + currentThread.setContextClassLoader(this.classLoader); + try { + bootMethod.invoke(agentBootStrap); + } finally { + currentThread.setContextClassLoader(before); + } + } catch (ClassNotFoundException e) { + throw new RuntimeException("boot class not found. Caused:" + e.getMessage(), e); + } catch (NoSuchMethodException e) { + throw new RuntimeException("boot method not found. Caused:" + e.getMessage(), e); + } catch (InvocationTargetException e) { + throw new RuntimeException(this.bootMethod + "() fail. Caused:" + e.getMessage(), e); + } catch (InstantiationException e) { + throw new RuntimeException("boot create fail. Caused:" + e.getMessage(), e); + } catch (IllegalAccessException e) { + throw new RuntimeException("boot method invoke fail. Caused:" + e.getMessage(), e); + } + + } +} diff --git a/src/main/java/com/profiler/ClassPathResolver.java b/src/main/java/com/profiler/ClassPathResolver.java index 38a7cbd1b..67caeef53 100644 --- a/src/main/java/com/profiler/ClassPathResolver.java +++ b/src/main/java/com/profiler/ClassPathResolver.java @@ -12,22 +12,34 @@ import java.util.regex.Pattern; */ public class ClassPathResolver { // Pattern.compile("hippo-tomcat-profiler-([0-9]+\\){2}.jar"); - private static final Pattern agentPattern = Pattern.compile("hippo-tomcat-profiler-[0-9]+\\.[0-9]+\\.[0-9]+\\.jar"); + private static final Pattern DEFAULT_AGENT_PATTERN = Pattern.compile("hippo-tomcat-profiler-[0-9]+\\.[0-9]+\\.[0-9]+\\.jar"); private String classPath; private String agentJarName; private String agentJarFullPath; private String agentDirPath; + private Pattern agentPattern; + public ClassPathResolver(String classPath) { this.classPath = classPath; + this.agentPattern = DEFAULT_AGENT_PATTERN; + } + + public ClassPathResolver(String classPath, String agentPattern) { + this.classPath = classPath; + this.agentPattern = Pattern.compile(agentPattern); } public void setClassPath(String classPath) { this.classPath = classPath; } + public void setClassPathFromSystemProperty() { + this.classPath = getClassPathFromSystemProperty(); + } + public String getClassPathFromSystemProperty() { return System.getProperty("java.class.path"); } @@ -96,4 +108,5 @@ public class ClassPathResolver { public String getAgentDirPath() { return agentDirPath; } + } diff --git a/src/test/java/com/profiler/AgentURLClassLoaderTest.java b/src/test/java/com/profiler/AgentURLClassLoaderTest.java new file mode 100644 index 000000000..f22e55701 --- /dev/null +++ b/src/test/java/com/profiler/AgentURLClassLoaderTest.java @@ -0,0 +1,56 @@ +package com.profiler; + + +import org.junit.Assert; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.CodeSource; +import java.security.ProtectionDomain; + +/** + * + */ +public class AgentURLClassLoaderTest { + + private Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Test + public void boot() throws MalformedURLException, ClassNotFoundException { + String projectDir = getProjectLibDir(); + + logger.info("lib:" + projectDir); + + String testJar = "hippoClassLoaderTest-1.0.jar"; + logger.info("load lib:" + testJar); + + String testJarPath = projectDir + File.separator + testJar; + logger.info("load testlib:" + testJarPath); + File file = new File(testJarPath); + Assert.assertTrue(file.exists()); + AgentURLClassLoader agentURLClassLoader = new AgentURLClassLoader(new URL[]{file.toURI().toURL()}); + agentURLClassLoader.setBootClass("com.profiler.boot.BootClassTest"); + agentURLClassLoader.boot(); + + } + + private String getProjectLibDir() { + ProtectionDomain protectionDomain = AgentURLClassLoader.class.getProtectionDomain(); + CodeSource codeSource = protectionDomain.getCodeSource(); + URL location = codeSource.getLocation(); + + logger.info("lib location:" + location); + String path = location.getPath(); + // file:/D:/nhn_source/hippo_project/hippo-tomcat-profiler/target/classes/ + int dirPath = path.lastIndexOf("target/classes/"); + if (dirPath == -1) { + throw new RuntimeException("target/classes/ not found"); + } + String projectDir = path.substring(0, dirPath); + return projectDir + "src/test/lib"; + } +}