diff --git a/build.xml b/build.xml index fd971c2c2..02e5a2a1b 100644 --- a/build.xml +++ b/build.xml @@ -28,12 +28,4 @@ - - - - - - - - \ No newline at end of file diff --git a/src/main/java/com/profiler/DefaultAgent.java b/src/main/java/com/profiler/DefaultAgent.java index c829934d0..d37dd3572 100644 --- a/src/main/java/com/profiler/DefaultAgent.java +++ b/src/main/java/com/profiler/DefaultAgent.java @@ -177,7 +177,7 @@ public class DefaultAgent implements Agent { } private void initializeTraceContext() { - this.traceContext = DefaultTraceContext.getTraceContext(); + this.traceContext = new DefaultTraceContext(); this.traceContext.setAgentId(this.agentId); this.traceContext.setApplicationId(this.applicationName); diff --git a/src/main/java/com/profiler/TomcatProfiler.java b/src/main/java/com/profiler/TomcatProfiler.java index e34cd7da6..7d26a0771 100644 --- a/src/main/java/com/profiler/TomcatProfiler.java +++ b/src/main/java/com/profiler/TomcatProfiler.java @@ -14,6 +14,7 @@ import com.profiler.interceptor.bci.ByteCodeInstrumentor; import com.profiler.interceptor.bci.JavaAssistByteCodeInstrumentor; import com.profiler.modifier.ModifierRegistry; +@Deprecated public class TomcatProfiler { private static final Logger logger = LoggerFactory.getLogger(TomcatProfiler.class.getName()); diff --git a/src/main/java/com/profiler/context/DefaultTraceContext.java b/src/main/java/com/profiler/context/DefaultTraceContext.java index 5fc6dde8b..4f32807cc 100644 --- a/src/main/java/com/profiler/context/DefaultTraceContext.java +++ b/src/main/java/com/profiler/context/DefaultTraceContext.java @@ -23,17 +23,6 @@ public class DefaultTraceContext implements TraceContext { private final Logger logger = LoggerFactory.getLogger(DefaultTraceContext.class.getName()); - private static DefaultTraceContext CONTEXT = new DefaultTraceContext(); - - // initailze관련 생명주기가 뭔가 애매함. 추후 방안을 더 고려해보자. - public static TraceContext initialize() { - return CONTEXT = new DefaultTraceContext(); - } - - // 얻는 것도 뭔가 모양이 마음에 안듬. - public static DefaultTraceContext getTraceContext() { - return CONTEXT; - } private final ThreadLocal threadLocal = new NamedThreadLocal("Trace"); diff --git a/src/main/java/com/profiler/io/HeaderTBaseSerializer.java b/src/main/java/com/profiler/io/HeaderTBaseSerializer.java index cbfa4bd90..d06324627 100644 --- a/src/main/java/com/profiler/io/HeaderTBaseSerializer.java +++ b/src/main/java/com/profiler/io/HeaderTBaseSerializer.java @@ -22,7 +22,7 @@ public class HeaderTBaseSerializer { * This is the byte array that data is actually serialized into */ // udp 패킷 사이즈에 최대 맞춤. - private final ByteArrayOutputStream baos_ = new ByteArrayOutputStream(1024 * 64); + private final UnsafeByteArrayOutputStream baos_ = new UnsafeByteArrayOutputStream(1024 * 64); /** * This transport wraps that byte array @@ -39,7 +39,6 @@ public class HeaderTBaseSerializer { */ public HeaderTBaseSerializer() { -// this(new TBinaryProtocol.Factory()); this(new TCompactProtocol.Factory()); } @@ -65,7 +64,13 @@ public class HeaderTBaseSerializer { baos_.reset(); writeHeader(header); base.write(protocol_); - return baos_.toByteArray(); +// return baos_.toByteArray(); + return baos_.getInterBuffer(); + } + + public int getInterBufferSize() { + + return baos_.size(); } private void writeHeader(Header header) throws TException { diff --git a/src/main/java/com/profiler/io/UnsafeByteArrayOutputStream.java b/src/main/java/com/profiler/io/UnsafeByteArrayOutputStream.java new file mode 100644 index 000000000..a5bd4dfe7 --- /dev/null +++ b/src/main/java/com/profiler/io/UnsafeByteArrayOutputStream.java @@ -0,0 +1,186 @@ +package com.profiler.io; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; + +/** + * + */ +public class UnsafeByteArrayOutputStream extends OutputStream { + + /** + * The buffer where data is stored. + */ + protected byte buf[]; + + /** + * The number of valid bytes in the buffer. + */ + protected int count; + + /** + * Creates a new byte array output stream. The buffer capacity is + * initially 32 bytes, though its size increases if necessary. + */ + public UnsafeByteArrayOutputStream() { + this(32); + } + + /** + * Creates a new byte array output stream, with a buffer capacity of + * the specified size, in bytes. + * + * @param size the initial size. + * @throws IllegalArgumentException if size is negative. + */ + public UnsafeByteArrayOutputStream(int size) { + if (size < 0) { + throw new IllegalArgumentException("Negative initial size: " + + size); + } + buf = new byte[size]; + } + + /** + * Writes the specified byte to this byte array output stream. + * + * @param b the byte to be written. + */ + public void write(int b) { + int newcount = count + 1; + if (newcount > buf.length) { + buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount)); + } + buf[count] = (byte) b; + count = newcount; + } + + /** + * Writes len bytes from the specified byte array + * starting at offset off to this byte array output stream. + * + * @param b the data. + * @param off the start offset in the data. + * @param len the number of bytes to write. + */ + public void write(byte b[], int off, int len) { + if ((off < 0) || (off > b.length) || (len < 0) || + ((off + len) > b.length) || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return; + } + int newcount = count + len; + if (newcount > buf.length) { + buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount)); + } + System.arraycopy(b, off, buf, count, len); + count = newcount; + } + + /** + * Writes the complete contents of this byte array output stream to + * the specified output stream argument, as if by calling the output + * stream's write method using out.write(buf, 0, count). + * + * @param out the output stream to which to write the data. + * @throws java.io.IOException if an I/O error occurs. + */ + public void writeTo(OutputStream out) throws IOException { + out.write(buf, 0, count); + } + + /** + * Resets the count field of this byte array output + * stream to zero, so that all currently accumulated output in the + * output stream is discarded. The output stream can be used again, + * reusing the already allocated buffer space. + * + * @see java.io.ByteArrayInputStream#count + */ + public void reset() { + count = 0; + } + + /** + * Creates a newly allocated byte array. Its size is the current + * size of this output stream and the valid contents of the buffer + * have been copied into it. + * + * @return the current contents of this output stream, as a byte array. + * @see java.io.ByteArrayOutputStream#size() + */ + public byte toByteArray()[] { + return Arrays.copyOf(buf, count); + } + + public byte[] getInterBuffer() { + return buf; + } + + /** + * Returns the current size of the buffer. + * + * @return the value of the count field, which is the number + * of valid bytes in this output stream. + * @see java.io.ByteArrayOutputStream#count + */ + public int size() { + return count; + } + + /** + * Converts the buffer's contents into a string decoding bytes using the + * platform's default character set. The length of the new String + * is a function of the character set, and hence may not be equal to the + * size of the buffer. + *

+ *

This method always replaces malformed-input and unmappable-character + * sequences with the default replacement string for the platform's + * default character set. The {@linkplain java.nio.charset.CharsetDecoder} + * class should be used when more control over the decoding process is + * required. + * + * @return String decoded from the buffer's contents. + * @since JDK1.1 + */ + public String toString() { + return new String(buf, 0, count); + } + + /** + * Converts the buffer's contents into a string by decoding the bytes using + * the specified {@link java.nio.charset.Charset charsetName}. The length of + * the new String is a function of the charset, and hence may not be + * equal to the length of the byte array. + *

+ *

This method always replaces malformed-input and unmappable-character + * sequences with this charset's default replacement string. The {@link + * java.nio.charset.CharsetDecoder} class should be used when more control + * over the decoding process is required. + * + * @param charsetName the name of a supported + * {@linkplain java.nio.charset.Charset charset} + * @return String decoded from the buffer's contents. + * @throws java.io.UnsupportedEncodingException + * If the named charset is not supported + * @since JDK1.1 + */ + public String toString(String charsetName) + throws UnsupportedEncodingException { + return new String(buf, 0, count, charsetName); + } + + + /** + * Closing a ByteArrayOutputStream has no effect. The methods in + * this class can be called after the stream has been closed without + * generating an IOException. + *

+ */ + public void close() throws IOException { + } +} diff --git a/src/main/java/com/profiler/sender/UdpDataSender.java b/src/main/java/com/profiler/sender/UdpDataSender.java index f74d87cdf..3e68f5e00 100644 --- a/src/main/java/com/profiler/sender/UdpDataSender.java +++ b/src/main/java/com/profiler/sender/UdpDataSender.java @@ -36,6 +36,8 @@ public class UdpDataSender implements DataSender, Runnable { private int maxDrainSize = 10; // 주의 single thread용임 private List drain = new ArrayList(maxDrainSize); + // 주의 single thread용임 + private DatagramPacket reusePacket = new DatagramPacket(new byte[1], 1); private DatagramSocket udpSocket = null; private Thread ioThread; @@ -44,10 +46,13 @@ public class UdpDataSender implements DataSender, Runnable { // 주의 single thread용임 private HeaderTBaseSerializer serializer = new HeaderTBaseSerializer(); + private AtomicBoolean allowInput = new AtomicBoolean(); public UdpDataSender(String host, int port) { - Assert.notNull(host, "host must not be null"); + if (host == null ) { + throw new NullPointerException("host must not be null"); + } // Socket 생성에 에러가 발생하면 Agent start가 안되게 변경. this.udpSocket = createSocket(host, port); @@ -69,7 +74,8 @@ public class UdpDataSender implements DataSender, Runnable { private DatagramSocket createSocket(String host, int port) { try { - DatagramSocket datagramSocket = new DatagramSocket(); + DatagramSocket datagramSocket = new DatagramSocket(); + datagramSocket.setSoTimeout(1000 * 5); InetSocketAddress serverAddress = new InetSocketAddress(host, port); @@ -169,6 +175,8 @@ public class UdpDataSender implements DataSender, Runnable { } } + + private void sendPacket(Object dto) { TBase tBase; if (dto instanceof TBase) { @@ -179,15 +187,16 @@ public class UdpDataSender implements DataSender, Runnable { logger.warn("sendPacket fail. invalid type:" + dto.getClass()); return; } - // TODO single thread이므로 데이터 array를 nocopy해서 보낼수 있음. - byte[] sendData = serialize(tBase); - if (sendData == null) { - logger.warn("sendData is null"); + // single thread이므로 데이터 array를 nocopy해서 보냄. + byte[] interBufferData = serialize(tBase); + int interBufferSize = serializer.getInterBufferSize(); + if (interBufferData == null) { + logger.warn("interBufferData is null"); return; } - DatagramPacket packet = new DatagramPacket(sendData, sendData.length); + reusePacket.setData(interBufferData, 0, interBufferSize); try { - udpSocket.send(packet); + udpSocket.send(reusePacket); if (logger.isInfoEnabled()) { logger.info("Data sent. " + dto); } @@ -229,6 +238,10 @@ public class UdpDataSender implements DataSender, Runnable { } } + private int beforeSerializeLength() { + return serializer.getInterBufferSize(); + } + private Header headerLookup(TBase dto) throws TException { // header 객체 생성을 안하고 정적 lookup이 되도록 변경. return locator.headerLookup(dto); diff --git a/src/test/java/com/profiler/context/MockTraceContextFactory.java b/src/test/java/com/profiler/context/MockTraceContextFactory.java new file mode 100644 index 000000000..5c4485eab --- /dev/null +++ b/src/test/java/com/profiler/context/MockTraceContextFactory.java @@ -0,0 +1,15 @@ +package com.profiler.context; + +import com.profiler.sender.LoggingDataSender; + +/** + * + */ +public class MockTraceContextFactory { + public TraceContext create() { + DefaultTraceContext traceContext = new DefaultTraceContext(); + BypassStorageFactory bypassStorageFactory = new BypassStorageFactory(new LoggingDataSender()); + traceContext.setStorageFactory(bypassStorageFactory); + return traceContext; + } +} diff --git a/src/test/java/com/profiler/modifier/db/mysql/MySQLConnectionImplModifierTest.java b/src/test/java/com/profiler/modifier/db/mysql/MySQLConnectionImplModifierTest.java index 7a15575fd..dada9d77b 100644 --- a/src/test/java/com/profiler/modifier/db/mysql/MySQLConnectionImplModifierTest.java +++ b/src/test/java/com/profiler/modifier/db/mysql/MySQLConnectionImplModifierTest.java @@ -4,11 +4,10 @@ import com.mysql.jdbc.JDBC4PreparedStatement; import com.profiler.DefaultAgent; import com.profiler.DummyInstrumentation; import com.profiler.config.ProfilerConfig; -import com.profiler.context.BypassStorageFactory; -import com.profiler.context.DefaultTraceContext; -import com.profiler.context.Trace; + +import com.profiler.logger.Slf4jLoggerBinder; import com.profiler.modifier.db.DatabaseInfo; -import com.profiler.sender.LoggingDataSender; + import com.profiler.util.MetaObject; import com.profiler.util.TestClassLoader; import org.junit.Assert; @@ -26,12 +25,17 @@ public class MySQLConnectionImplModifierTest { private TestClassLoader loader; + @Before public void setUp() throws Exception { - loader = new TestClassLoader(); + + com.profiler.logging.LoggerFactory.initialize(new Slf4jLoggerBinder()); ProfilerConfig profilerConfig = new ProfilerConfig(); DefaultAgent agent = new DefaultAgent("", new DummyInstrumentation(), profilerConfig); + loader = new TestClassLoader(agent); + + MySQLNonRegisteringDriverModifier driverModifier = new MySQLNonRegisteringDriverModifier(loader.getInstrumentor(), agent); loader.addModifier(driverModifier); @@ -66,10 +70,7 @@ public class MySQLConnectionImplModifierTest { properties.setProperty("user", "lucytest"); properties.setProperty("password", "testlucy"); - DefaultTraceContext traceContext = DefaultTraceContext.getTraceContext(); - traceContext.setStorageFactory(new BypassStorageFactory(LoggingDataSender.DEFAULT_LOGGING_DATA_SENDER)); - Trace trace = traceContext.newTraceObject(); Connection connection = driver.connect("jdbc:mysql://10.98.133.22:3306/hippo", properties); @@ -91,7 +92,6 @@ public class MySQLConnectionImplModifierTest { DatabaseInfo clearUrl = getUrl.invoke(connection); Assert.assertNull(clearUrl); - traceContext.detachTraceObject(); } private void statement(Connection connection) throws SQLException { diff --git a/src/test/java/com/profiler/modifier/tomcat/InvokeMethodInterceptorTest.java b/src/test/java/com/profiler/modifier/tomcat/InvokeMethodInterceptorTest.java index 13e926f80..4b572bb57 100644 --- a/src/test/java/com/profiler/modifier/tomcat/InvokeMethodInterceptorTest.java +++ b/src/test/java/com/profiler/modifier/tomcat/InvokeMethodInterceptorTest.java @@ -9,17 +9,25 @@ import java.util.UUID; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import com.profiler.context.DefaultTraceContext; +import com.profiler.context.MockTraceContextFactory; +import com.profiler.context.TraceContext; +import com.profiler.logger.Slf4jLoggerBinder; +import com.profiler.logging.LoggerFactory; +import org.junit.BeforeClass; import org.junit.Test; import com.profiler.context.Header; import com.profiler.modifier.tomcat.interceptors.StandardHostValveInvokeInterceptor; public class InvokeMethodInterceptorTest { + @BeforeClass + public static void before() { + LoggerFactory.initialize(new Slf4jLoggerBinder()); + } @Test public void testHeaderNOTExists() { - DefaultTraceContext.initialize(); + HttpServletRequest request = mock(HttpServletRequest.class); HttpServletResponse response = mock(HttpServletResponse.class); @@ -35,7 +43,8 @@ public class InvokeMethodInterceptorTest { when(request.getParameterNames()).thenReturn(enumeration); StandardHostValveInvokeInterceptor interceptor = new StandardHostValveInvokeInterceptor(); - interceptor.setTraceContext(DefaultTraceContext.getTraceContext()); + TraceContext traceContext = new MockTraceContextFactory().create(); + interceptor.setTraceContext(traceContext); interceptor.before("target", "classname", "methodname", null, new Object[]{request, response}); interceptor.after("target", "classname", "methodname", null, new Object[]{request, response}, new Object()); @@ -46,7 +55,7 @@ public class InvokeMethodInterceptorTest { @Test public void testInvalidHeaderExists() { - DefaultTraceContext.initialize(); + // TODO 결과값 검증 필요. HttpServletRequest request = mock(HttpServletRequest.class); HttpServletResponse response = mock(HttpServletResponse.class); @@ -62,8 +71,9 @@ public class InvokeMethodInterceptorTest { Enumeration enumeration = mock(Enumeration.class); when(request.getParameterNames()).thenReturn(enumeration); + TraceContext traceContext = new MockTraceContextFactory().create(); StandardHostValveInvokeInterceptor interceptor = new StandardHostValveInvokeInterceptor(); - interceptor.setTraceContext(DefaultTraceContext.getTraceContext()); + interceptor.setTraceContext(traceContext); interceptor.before("target", "classname", "methodname", null, new Object[]{request, response}); interceptor.after("target", "classname", "methodname", null, new Object[]{request, response}, new Object()); @@ -73,7 +83,7 @@ public class InvokeMethodInterceptorTest { @Test public void testValidHeaderExists() { - DefaultTraceContext.initialize(); + // TODO 결과값 검증 필요. HttpServletRequest request = mock(HttpServletRequest.class); HttpServletResponse response = mock(HttpServletResponse.class); @@ -89,8 +99,9 @@ public class InvokeMethodInterceptorTest { Enumeration enumeration = mock(Enumeration.class); when(request.getParameterNames()).thenReturn(enumeration); + TraceContext traceContext = new MockTraceContextFactory().create(); StandardHostValveInvokeInterceptor interceptor = new StandardHostValveInvokeInterceptor(); - interceptor.setTraceContext(DefaultTraceContext.getTraceContext()); + interceptor.setTraceContext(traceContext); interceptor.before("target", "classname", "methodname", null, new Object[]{request, response}); interceptor.after("target", "classname", "methodname", null, new Object[]{request, response}, new Object()); diff --git a/src/test/java/com/profiler/util/TestClassLoader.java b/src/test/java/com/profiler/util/TestClassLoader.java index 99266e07f..803e8f1a2 100644 --- a/src/test/java/com/profiler/util/TestClassLoader.java +++ b/src/test/java/com/profiler/util/TestClassLoader.java @@ -1,5 +1,6 @@ package com.profiler.util; +import com.profiler.Agent; import com.profiler.DefaultAgent; import com.profiler.context.DefaultTrace; import com.profiler.context.DefaultTraceContext; @@ -22,18 +23,25 @@ public class TestClassLoader extends Loader { private ByteCodeInstrumentor instrumentor; private InstrumentTranslator instrumentTranslator; + private Agent agent; - public TestClassLoader() { - this.instrumentor = new JavaAssistByteCodeInstrumentor(); + public TestClassLoader(Agent agent) { + this.instrumentor = new JavaAssistByteCodeInstrumentor(null, agent); this.instrumentTranslator = new InstrumentTranslator(this); + this.agent = agent; } + public void initialize() { addDefaultDelegateLoadingOf(); addTranslator(); } + public Agent getAgent() { + return agent; + } + public ByteCodeInstrumentor getInstrumentor() { return instrumentor; }