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