View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.testsuite.util;
17  
18  import io.netty.util.CharsetUtil;
19  import io.netty.util.internal.ObjectUtil;
20  import io.netty.util.internal.logging.InternalLogger;
21  import io.netty.util.internal.logging.InternalLoggerFactory;
22  import org.junit.jupiter.api.TestInfo;
23  import org.tukaani.xz.LZMA2Options;
24  import org.tukaani.xz.XZOutputStream;
25  
26  import java.io.File;
27  import java.io.FileInputStream;
28  import java.io.FileOutputStream;
29  import java.io.FilenameFilter;
30  import java.io.IOException;
31  import java.io.InputStream;
32  import java.io.OutputStream;
33  import java.lang.management.ManagementFactory;
34  import java.lang.management.ThreadInfo;
35  import java.lang.reflect.Method;
36  import java.nio.channels.Channel;
37  import java.text.SimpleDateFormat;
38  import java.util.Date;
39  import java.util.Locale;
40  import java.util.concurrent.TimeUnit;
41  import java.util.function.Function;
42  import javax.management.MBeanServer;
43  
44  public final class TestUtils {
45  
46      private static final InternalLogger logger = InternalLoggerFactory.getInstance(TestUtils.class);
47  
48      private static final Method hotspotMXBeanDumpHeap;
49      private static final Object hotspotMXBean;
50  
51      private static final long DUMP_PROGRESS_LOGGING_INTERVAL = TimeUnit.SECONDS.toNanos(5);
52  
53      static {
54          // Retrieve the hotspot MXBean and its class if available.
55          Object mxBean;
56          Method mxBeanDumpHeap;
57          try {
58              Class<?> clazz = Class.forName("com.sun.management.HotSpotDiagnosticMXBean");
59              MBeanServer server = ManagementFactory.getPlatformMBeanServer();
60              mxBean = ManagementFactory.newPlatformMXBeanProxy(
61                      server, "com.sun.management:type=HotSpotDiagnostic", clazz);
62              mxBeanDumpHeap = clazz.getMethod("dumpHeap", String.class, boolean.class);
63          } catch (Exception ignored) {
64              mxBean = null;
65              mxBeanDumpHeap = null;
66          }
67  
68          hotspotMXBean = mxBean;
69          hotspotMXBeanDumpHeap = mxBeanDumpHeap;
70      }
71  
72      /**
73       * Return {@code true} if SCTP is supported by the running os.
74       *
75       */
76      public static boolean isSctpSupported() {
77          String os = System.getProperty("os.name").toLowerCase(Locale.US);
78          if ("unix".equals(os) || "linux".equals(os) || "sun".equals(os) || "solaris".equals(os)) {
79              try {
80                  // Try to open a SCTP Channel, by using reflection to make it compile also on
81                  // operation systems that not support SCTP like OSX and Windows
82                  Class<?> sctpChannelClass = Class.forName("com.sun.nio.sctp.SctpChannel");
83                  Channel channel = (Channel) sctpChannelClass.getMethod("open").invoke(null);
84                  try {
85                      channel.close();
86                  } catch (IOException e) {
87                      // ignore
88                  }
89              } catch (UnsupportedOperationException e) {
90                  // This exception may get thrown if the OS does not have
91                  // the shared libs installed.
92                  System.out.print("Not supported: " + e.getMessage());
93                  return false;
94              } catch (Throwable t) {
95                  if (!(t instanceof IOException)) {
96                      return false;
97                  }
98              }
99              return true;
100         }
101         return false;
102     }
103 
104     /**
105      * Returns the method name of the current test.
106      */
107     public static String testMethodName(TestInfo testInfo) {
108         String testMethodName = testInfo.getTestMethod().map(new Function<Method, String>() {
109             @Override
110             public String apply(Method method) {
111                 return method.getName();
112             }
113         }).orElse("[unknown method]");
114         if (testMethodName.contains("[")) {
115             testMethodName = testMethodName.substring(0, testMethodName.indexOf('['));
116         }
117         return testMethodName;
118     }
119 
120     public static void dump(String filenamePrefix) throws IOException {
121 
122         ObjectUtil.checkNotNull(filenamePrefix, "filenamePrefix");
123 
124         final String timestamp = timestamp();
125         final File heapDumpFile = new File(filenamePrefix + '.' + timestamp + ".hprof");
126         if (heapDumpFile.exists()) {
127             if (!heapDumpFile.delete()) {
128                 throw new IOException("Failed to remove the old heap dump: " + heapDumpFile);
129             }
130         }
131 
132         final File threadDumpFile = new File(filenamePrefix + '.' + timestamp + ".threads");
133         if (threadDumpFile.exists()) {
134             if (!threadDumpFile.delete()) {
135                 throw new IOException("Failed to remove the old thread dump: " + threadDumpFile);
136             }
137         }
138 
139         dumpHeap(heapDumpFile);
140         dumpThreads(threadDumpFile);
141     }
142 
143     public static void compressHeapDumps() throws IOException {
144         final File[] files = new File(System.getProperty("user.dir")).listFiles(new FilenameFilter() {
145             @Override
146             public boolean accept(File dir, String name) {
147                 return name.endsWith(".hprof");
148             }
149         });
150         if (files == null) {
151             logger.warn("failed to find heap dump due to I/O error!");
152             return;
153         }
154 
155         final byte[] buf = new byte[65536];
156         final LZMA2Options options = new LZMA2Options(LZMA2Options.PRESET_DEFAULT);
157 
158         for (File file: files) {
159             final String filename = file.toString();
160             final String xzFilename = filename + ".xz";
161             final long fileLength = file.length();
162 
163             logger.info("Compressing the heap dump: {}", xzFilename);
164 
165             long lastLogTime = System.nanoTime();
166             long counter = 0;
167 
168             InputStream in = null;
169             OutputStream out = null;
170             try {
171                 in = new FileInputStream(filename);
172                 out = new XZOutputStream(new FileOutputStream(xzFilename), options);
173                 for (;;) {
174                     int readBytes = in.read(buf);
175                     if (readBytes < 0) {
176                         break;
177                     }
178                     if (readBytes == 0) {
179                         continue;
180                     }
181 
182                     out.write(buf, 0, readBytes);
183                     counter += readBytes;
184 
185                     long currentTime = System.nanoTime();
186                     if (currentTime - lastLogTime > DUMP_PROGRESS_LOGGING_INTERVAL) {
187                         logger.info("Compressing the heap dump: {} ({}%)",
188                                     xzFilename, counter * 100 / fileLength);
189                         lastLogTime = currentTime;
190                     }
191                 }
192                 out.close();
193                 in.close();
194             } catch (Throwable t) {
195                 logger.warn("Failed to compress the heap dump: {}", xzFilename, t);
196             } finally {
197                 if (in != null) {
198                     try {
199                         in.close();
200                     } catch (IOException ignored) {
201                         // Ignore.
202                     }
203                 }
204                 if (out != null) {
205                     try {
206                         out.close();
207                     } catch (IOException ignored) {
208                         // Ignore.
209                     }
210                 }
211             }
212 
213             // Delete the uncompressed dump in favor of the compressed one.
214             if (!file.delete()) {
215                 logger.warn("Failed to delete the uncompressed heap dump: {}", filename);
216             }
217         }
218     }
219 
220     private static String timestamp() {
221         return new SimpleDateFormat("HHmmss.SSS").format(new Date());
222     }
223 
224     private static void dumpHeap(File file) {
225         if (hotspotMXBean == null) {
226             logger.warn("Can't dump heap: HotSpotDiagnosticMXBean unavailable");
227             return;
228         }
229 
230         final String filename = file.toString();
231         logger.info("Dumping heap: {}", filename);
232         try {
233             hotspotMXBeanDumpHeap.invoke(hotspotMXBean, filename, true);
234         } catch (Exception e) {
235             logger.warn("Failed to dump heap: {}", filename, e);
236         }
237     }
238 
239     private static void dumpThreads(File file) {
240         final String filename = file.toString();
241         OutputStream out = null;
242         try {
243             logger.info("Dumping threads: {}", filename);
244             final StringBuilder buf = new StringBuilder(8192);
245             try {
246                 for (ThreadInfo info : ManagementFactory.getThreadMXBean().dumpAllThreads(true, true)) {
247                     buf.append(info);
248                 }
249                 buf.append('\n');
250             } catch (UnsupportedOperationException ignored) {
251                 logger.warn("Can't dump threads: ThreadMXBean.dumpAllThreads() unsupported");
252                 return;
253             }
254 
255             out = new FileOutputStream(file);
256             out.write(buf.toString().getBytes(CharsetUtil.UTF_8));
257         } catch (Exception e) {
258             logger.warn("Failed to dump threads: {}", filename, e);
259         } finally {
260             if (out != null) {
261                 try {
262                     out.close();
263                 } catch (IOException ignored) {
264                     // Ignore.
265                 }
266             }
267         }
268     }
269 
270     private TestUtils() { }
271 }