Generate heap and thread dump when some tests fail
Motivation: We have a few sporadic test failures which are only easily reproduceable in our CI machine. To get more information about the failure, we need heap and full thread dump at the moment of failure. Modifications: - Add TestUtils.dump() method to dump heap and threads - Modify SocketGatheringWriteTest and SocketSslEchoTest to call TestUtils.dump() on failure Result: We get more information about the test failure.
This commit is contained in:
parent
a4450b76d9
commit
7124ef88cb
@ -21,8 +21,11 @@ import io.netty.buffer.ByteBuf;
|
|||||||
import io.netty.buffer.CompositeByteBuf;
|
import io.netty.buffer.CompositeByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelFuture;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.channel.SimpleChannelInboundHandler;
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
|
import io.netty.testsuite.util.TestUtils;
|
||||||
|
import io.netty.util.internal.StringUtil;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -40,7 +43,7 @@ public class SocketGatheringWriteTest extends AbstractSocketTest {
|
|||||||
random.nextBytes(data);
|
random.nextBytes(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout = 30000)
|
@Test(timeout = 60000)
|
||||||
public void testGatheringWrite() throws Throwable {
|
public void testGatheringWrite() throws Throwable {
|
||||||
run();
|
run();
|
||||||
}
|
}
|
||||||
@ -49,7 +52,7 @@ public class SocketGatheringWriteTest extends AbstractSocketTest {
|
|||||||
testGatheringWrite0(sb, cb, data, false, true);
|
testGatheringWrite0(sb, cb, data, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout = 30000)
|
@Test(timeout = 60000)
|
||||||
public void testGatheringWriteNotAutoRead() throws Throwable {
|
public void testGatheringWriteNotAutoRead() throws Throwable {
|
||||||
run();
|
run();
|
||||||
}
|
}
|
||||||
@ -58,7 +61,7 @@ public class SocketGatheringWriteTest extends AbstractSocketTest {
|
|||||||
testGatheringWrite0(sb, cb, data, false, false);
|
testGatheringWrite0(sb, cb, data, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout = 30000)
|
@Test(timeout = 60000)
|
||||||
public void testGatheringWriteWithComposite() throws Throwable {
|
public void testGatheringWriteWithComposite() throws Throwable {
|
||||||
run();
|
run();
|
||||||
}
|
}
|
||||||
@ -67,7 +70,7 @@ public class SocketGatheringWriteTest extends AbstractSocketTest {
|
|||||||
testGatheringWrite0(sb, cb, data, true, false);
|
testGatheringWrite0(sb, cb, data, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout = 30000)
|
@Test(timeout = 60000)
|
||||||
public void testGatheringWriteWithCompositeNotAutoRead() throws Throwable {
|
public void testGatheringWriteWithCompositeNotAutoRead() throws Throwable {
|
||||||
run();
|
run();
|
||||||
}
|
}
|
||||||
@ -77,7 +80,7 @@ public class SocketGatheringWriteTest extends AbstractSocketTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test for https://github.com/netty/netty/issues/2647
|
// Test for https://github.com/netty/netty/issues/2647
|
||||||
@Test(timeout = 30000)
|
@Test(timeout = 60000)
|
||||||
public void testGatheringWriteBig() throws Throwable {
|
public void testGatheringWriteBig() throws Throwable {
|
||||||
run();
|
run();
|
||||||
}
|
}
|
||||||
@ -88,7 +91,7 @@ public class SocketGatheringWriteTest extends AbstractSocketTest {
|
|||||||
testGatheringWrite0(sb, cb, bigData, false, true);
|
testGatheringWrite0(sb, cb, bigData, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void testGatheringWrite0(
|
private void testGatheringWrite0(
|
||||||
ServerBootstrap sb, Bootstrap cb, byte[] data, boolean composite, boolean autoRead) throws Throwable {
|
ServerBootstrap sb, Bootstrap cb, byte[] data, boolean composite, boolean autoRead) throws Throwable {
|
||||||
final TestHandler sh = new TestHandler(autoRead);
|
final TestHandler sh = new TestHandler(autoRead);
|
||||||
final TestHandler ch = new TestHandler(autoRead);
|
final TestHandler ch = new TestHandler(autoRead);
|
||||||
@ -116,7 +119,17 @@ public class SocketGatheringWriteTest extends AbstractSocketTest {
|
|||||||
}
|
}
|
||||||
i += length;
|
i += length;
|
||||||
}
|
}
|
||||||
assertNotEquals(cc.voidPromise(), cc.writeAndFlush(Unpooled.EMPTY_BUFFER).sync());
|
|
||||||
|
ChannelFuture cf = cc.writeAndFlush(Unpooled.EMPTY_BUFFER);
|
||||||
|
assertNotEquals(cc.voidPromise(), cf);
|
||||||
|
try {
|
||||||
|
assertTrue(cf.await(30000));
|
||||||
|
cf.sync();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
// TODO: Remove this once we fix this test.
|
||||||
|
TestUtils.dump(StringUtil.simpleClassName(this));
|
||||||
|
throw t;
|
||||||
|
}
|
||||||
|
|
||||||
while (sh.counter < data.length) {
|
while (sh.counter < data.length) {
|
||||||
if (sh.exception.get() != null) {
|
if (sh.exception.get() != null) {
|
||||||
|
@ -34,7 +34,9 @@ import io.netty.handler.ssl.SslHandler;
|
|||||||
import io.netty.handler.ssl.SslHandshakeCompletionEvent;
|
import io.netty.handler.ssl.SslHandshakeCompletionEvent;
|
||||||
import io.netty.handler.ssl.util.SelfSignedCertificate;
|
import io.netty.handler.ssl.util.SelfSignedCertificate;
|
||||||
import io.netty.handler.stream.ChunkedWriteHandler;
|
import io.netty.handler.stream.ChunkedWriteHandler;
|
||||||
|
import io.netty.testsuite.util.TestUtils;
|
||||||
import io.netty.util.concurrent.Future;
|
import io.netty.util.concurrent.Future;
|
||||||
|
import io.netty.util.internal.StringUtil;
|
||||||
import io.netty.util.internal.logging.InternalLogger;
|
import io.netty.util.internal.logging.InternalLogger;
|
||||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -285,12 +287,18 @@ public class SocketSslEchoTest extends AbstractSocketTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// When renegotiation is done, both the client and server side should be notified.
|
// When renegotiation is done, both the client and server side should be notified.
|
||||||
if (renegotiationType != RenegotiationType.NONE) {
|
try {
|
||||||
assertThat(sh.negoCounter, is(2));
|
if (renegotiationType != RenegotiationType.NONE) {
|
||||||
assertThat(ch.negoCounter, is(2));
|
assertThat(sh.negoCounter, is(2));
|
||||||
} else {
|
assertThat(ch.negoCounter, is(2));
|
||||||
assertThat(sh.negoCounter, is(1));
|
} else {
|
||||||
assertThat(ch.negoCounter, is(1));
|
assertThat(sh.negoCounter, is(1));
|
||||||
|
assertThat(ch.negoCounter, is(1));
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
// TODO: Remove this once we fix this test.
|
||||||
|
TestUtils.dump(StringUtil.simpleClassName(this));
|
||||||
|
throw t;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,22 +15,36 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.testsuite.util;
|
package io.netty.testsuite.util;
|
||||||
|
|
||||||
|
import io.netty.util.CharsetUtil;
|
||||||
import io.netty.util.NetUtil;
|
import io.netty.util.NetUtil;
|
||||||
|
import io.netty.util.internal.logging.InternalLogger;
|
||||||
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||||
import org.junit.rules.TestName;
|
import org.junit.rules.TestName;
|
||||||
|
|
||||||
|
import javax.management.MBeanServer;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.lang.management.ManagementFactory;
|
||||||
|
import java.lang.management.ThreadInfo;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.net.DatagramSocket;
|
import java.net.DatagramSocket;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.nio.channels.Channel;
|
import java.nio.channels.Channel;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public final class TestUtils {
|
public final class TestUtils {
|
||||||
|
|
||||||
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(TestUtils.class);
|
||||||
|
|
||||||
private static final int START_PORT = 32768;
|
private static final int START_PORT = 32768;
|
||||||
private static final int END_PORT = 65536;
|
private static final int END_PORT = 65536;
|
||||||
private static final int NUM_CANDIDATES = END_PORT - START_PORT;
|
private static final int NUM_CANDIDATES = END_PORT - START_PORT;
|
||||||
@ -38,18 +52,32 @@ public final class TestUtils {
|
|||||||
private static final List<Integer> PORTS = new ArrayList<Integer>();
|
private static final List<Integer> PORTS = new ArrayList<Integer>();
|
||||||
private static Iterator<Integer> portIterator;
|
private static Iterator<Integer> portIterator;
|
||||||
|
|
||||||
|
private static final Method hotspotMXBeanDumpHeap;
|
||||||
|
private static final Object hotspotMXBean;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
// Populate the list of random ports.
|
||||||
for (int i = START_PORT; i < END_PORT; i ++) {
|
for (int i = START_PORT; i < END_PORT; i ++) {
|
||||||
PORTS.add(i);
|
PORTS.add(i);
|
||||||
}
|
}
|
||||||
Collections.shuffle(PORTS);
|
Collections.shuffle(PORTS);
|
||||||
}
|
|
||||||
|
|
||||||
private static int nextCandidatePort() {
|
// Retrieve the hotspot MXBean and its class if available.
|
||||||
if (portIterator == null || !portIterator.hasNext()) {
|
Object mxBean;
|
||||||
portIterator = PORTS.iterator();
|
Method mxBeanDumpHeap;
|
||||||
|
try {
|
||||||
|
Class<?> clazz = Class.forName("com.sun.management.HotSpotDiagnosticMXBean");
|
||||||
|
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
|
||||||
|
mxBean = ManagementFactory.newPlatformMXBeanProxy(
|
||||||
|
server, "com.sun.management:type=HotSpotDiagnostic", clazz);
|
||||||
|
mxBeanDumpHeap = clazz.getMethod("dumpHeap", String.class, boolean.class);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
mxBean = null;
|
||||||
|
mxBeanDumpHeap = null;
|
||||||
}
|
}
|
||||||
return portIterator.next();
|
|
||||||
|
hotspotMXBean = mxBean;
|
||||||
|
hotspotMXBeanDumpHeap = mxBeanDumpHeap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -75,6 +103,13 @@ public final class TestUtils {
|
|||||||
throw new RuntimeException("unable to find a free port");
|
throw new RuntimeException("unable to find a free port");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int nextCandidatePort() {
|
||||||
|
if (portIterator == null || !portIterator.hasNext()) {
|
||||||
|
portIterator = PORTS.iterator();
|
||||||
|
}
|
||||||
|
return portIterator.next();
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean isTcpPortAvailable(InetSocketAddress localAddress) {
|
private static boolean isTcpPortAvailable(InetSocketAddress localAddress) {
|
||||||
ServerSocket ss = null;
|
ServerSocket ss = null;
|
||||||
try {
|
try {
|
||||||
@ -162,5 +197,78 @@ public final class TestUtils {
|
|||||||
return testMethodName;
|
return testMethodName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void dump(String filenamePrefix) throws IOException {
|
||||||
|
|
||||||
|
if (filenamePrefix == null) {
|
||||||
|
throw new NullPointerException("filenamePrefix");
|
||||||
|
}
|
||||||
|
|
||||||
|
final String timestamp = timestamp();
|
||||||
|
final File heapDumpFile = new File(filenamePrefix + '.' + timestamp + ".hprof");
|
||||||
|
if (heapDumpFile.exists()) {
|
||||||
|
if (!heapDumpFile.delete()) {
|
||||||
|
throw new IOException("Failed to remove the old heap dump: " + heapDumpFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final File threadDumpFile = new File(filenamePrefix + '.' + timestamp + ".threads");
|
||||||
|
if (threadDumpFile.exists()) {
|
||||||
|
if (!threadDumpFile.delete()) {
|
||||||
|
throw new IOException("Failed to remove the old thread dump: " + threadDumpFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dumpHeap(heapDumpFile);
|
||||||
|
dumpStack(threadDumpFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String timestamp() {
|
||||||
|
return new SimpleDateFormat("HHmmss.SSS").format(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void dumpHeap(File file) {
|
||||||
|
if (hotspotMXBean == null) {
|
||||||
|
logger.warn("Can't dump heap: HotSpotDiagnosticMXBean unavailable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String filename = file.toString();
|
||||||
|
try {
|
||||||
|
hotspotMXBeanDumpHeap.invoke(hotspotMXBean, filename, true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("Failed to dump heap: {}", filename, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void dumpStack(File file) {
|
||||||
|
final String filename = file.toString();
|
||||||
|
OutputStream out = null;
|
||||||
|
try {
|
||||||
|
final StringBuilder buf = new StringBuilder(8192);
|
||||||
|
try {
|
||||||
|
for (ThreadInfo info : ManagementFactory.getThreadMXBean().dumpAllThreads(true, true)) {
|
||||||
|
buf.append(info);
|
||||||
|
}
|
||||||
|
buf.append('\n');
|
||||||
|
} catch (UnsupportedOperationException ignored) {
|
||||||
|
logger.warn("Can't dump threads: ThreadMXBean.dumpAllThreads() unsupported");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
out = new FileOutputStream(file);
|
||||||
|
out.write(buf.toString().getBytes(CharsetUtil.UTF_8));
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("Failed to dump threads: {}", filename, e);
|
||||||
|
} finally {
|
||||||
|
if (out != null) {
|
||||||
|
try {
|
||||||
|
out.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
// Ignore.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private TestUtils() { }
|
private TestUtils() { }
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user