1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty5.testsuite.util;
17
18 import io.netty5.util.CharsetUtil;
19 import io.netty5.util.internal.logging.InternalLogger;
20 import io.netty5.util.internal.logging.InternalLoggerFactory;
21 import org.junit.jupiter.api.TestInfo;
22 import org.tukaani.xz.LZMA2Options;
23 import org.tukaani.xz.XZOutputStream;
24
25 import javax.management.MBeanServer;
26 import java.io.File;
27 import java.io.FileInputStream;
28 import java.io.FileOutputStream;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.OutputStream;
32 import java.lang.management.ManagementFactory;
33 import java.lang.management.ThreadInfo;
34 import java.lang.reflect.Method;
35 import java.text.SimpleDateFormat;
36 import java.util.Date;
37 import java.util.concurrent.TimeUnit;
38
39 import static java.util.Objects.requireNonNull;
40
41 public final class TestUtils {
42
43 private static final InternalLogger logger = InternalLoggerFactory.getInstance(TestUtils.class);
44
45 private static final Method hotspotMXBeanDumpHeap;
46 private static final Object hotspotMXBean;
47
48 private static final long DUMP_PROGRESS_LOGGING_INTERVAL = TimeUnit.SECONDS.toNanos(5);
49
50 static {
51
52 Object mxBean;
53 Method mxBeanDumpHeap;
54 try {
55 Class<?> clazz = Class.forName("com.sun.management.HotSpotDiagnosticMXBean");
56 MBeanServer server = ManagementFactory.getPlatformMBeanServer();
57 mxBean = ManagementFactory.newPlatformMXBeanProxy(
58 server, "com.sun.management:type=HotSpotDiagnostic", clazz);
59 mxBeanDumpHeap = clazz.getMethod("dumpHeap", String.class, boolean.class);
60 } catch (Exception ignored) {
61 mxBean = null;
62 mxBeanDumpHeap = null;
63 }
64
65 hotspotMXBean = mxBean;
66 hotspotMXBeanDumpHeap = mxBeanDumpHeap;
67 }
68
69
70
71
72 public static String testMethodName(TestInfo testInfo) {
73 String testMethodName = testInfo.getTestMethod().map(Method::getName).orElse("[unknown method]");
74 if (testMethodName.contains("[")) {
75 testMethodName = testMethodName.substring(0, testMethodName.indexOf('['));
76 }
77 return testMethodName;
78 }
79
80 public static void dump(String filenamePrefix) throws IOException {
81 requireNonNull(filenamePrefix, "filenamePrefix");
82
83 final String timestamp = timestamp();
84 final File heapDumpFile = new File(filenamePrefix + '.' + timestamp + ".hprof");
85 if (heapDumpFile.exists()) {
86 if (!heapDumpFile.delete()) {
87 throw new IOException("Failed to remove the old heap dump: " + heapDumpFile);
88 }
89 }
90
91 final File threadDumpFile = new File(filenamePrefix + '.' + timestamp + ".threads");
92 if (threadDumpFile.exists()) {
93 if (!threadDumpFile.delete()) {
94 throw new IOException("Failed to remove the old thread dump: " + threadDumpFile);
95 }
96 }
97
98 dumpHeap(heapDumpFile);
99 dumpThreads(threadDumpFile);
100 }
101
102 public static void compressHeapDumps() throws IOException {
103 final File[] files = new File(System.getProperty("user.dir")).listFiles((dir, name) -> name.endsWith(".hprof"));
104 if (files == null) {
105 logger.warn("failed to find heap dump due to I/O error!");
106 return;
107 }
108
109 final byte[] buf = new byte[65536];
110 final LZMA2Options options = new LZMA2Options(LZMA2Options.PRESET_DEFAULT);
111
112 for (File file: files) {
113 final String filename = file.toString();
114 final String xzFilename = filename + ".xz";
115 final long fileLength = file.length();
116
117 logger.info("Compressing the heap dump: {}", xzFilename);
118
119 long lastLogTime = System.nanoTime();
120 long counter = 0;
121
122 try (InputStream in = new FileInputStream(filename);
123 OutputStream out = new XZOutputStream(new FileOutputStream(xzFilename), options)) {
124 for (;;) {
125 int readBytes = in.read(buf);
126 if (readBytes < 0) {
127 break;
128 }
129 if (readBytes == 0) {
130 continue;
131 }
132
133 out.write(buf, 0, readBytes);
134 counter += readBytes;
135
136 long currentTime = System.nanoTime();
137 if (currentTime - lastLogTime > DUMP_PROGRESS_LOGGING_INTERVAL) {
138 logger.info("Compressing the heap dump: {} ({}%)",
139 xzFilename, counter * 100 / fileLength);
140 lastLogTime = currentTime;
141 }
142 }
143 } catch (Throwable t) {
144 logger.warn("Failed to compress the heap dump: {}", xzFilename, t);
145 }
146
147
148 if (!file.delete()) {
149 logger.warn("Failed to delete the uncompressed heap dump: {}", filename);
150 }
151 }
152 }
153
154 private static String timestamp() {
155 return new SimpleDateFormat("HHmmss.SSS").format(new Date());
156 }
157
158 private static void dumpHeap(File file) {
159 if (hotspotMXBean == null) {
160 logger.warn("Can't dump heap: HotSpotDiagnosticMXBean unavailable");
161 return;
162 }
163
164 final String filename = file.toString();
165 logger.info("Dumping heap: {}", filename);
166 try {
167 hotspotMXBeanDumpHeap.invoke(hotspotMXBean, filename, true);
168 } catch (Exception e) {
169 logger.warn("Failed to dump heap: {}", filename, e);
170 }
171 }
172
173 private static void dumpThreads(File file) {
174 final String filename = file.toString();
175 OutputStream out = null;
176 try {
177 logger.info("Dumping threads: {}", filename);
178 final StringBuilder buf = new StringBuilder(8192);
179 try {
180 for (ThreadInfo info : ManagementFactory.getThreadMXBean().dumpAllThreads(true, true)) {
181 buf.append(info);
182 }
183 buf.append('\n');
184 } catch (UnsupportedOperationException ignored) {
185 logger.warn("Can't dump threads: ThreadMXBean.dumpAllThreads() unsupported");
186 return;
187 }
188
189 out = new FileOutputStream(file);
190 out.write(buf.toString().getBytes(CharsetUtil.UTF_8));
191 } catch (Exception e) {
192 logger.warn("Failed to dump threads: {}", filename, e);
193 } finally {
194 if (out != null) {
195 try {
196 out.close();
197 } catch (IOException ignored) {
198
199 }
200 }
201 }
202 }
203
204 private TestUtils() { }
205 }