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 javax.management.MBeanServer;
27  import java.io.File;
28  import java.io.FilenameFilter;
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.nio.channels.Channel;
36  import java.nio.file.Files;
37  import java.nio.file.Paths;
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  
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             try (InputStream in = Files.newInputStream(Paths.get(filename));
169                  OutputStream out = new XZOutputStream(Files.newOutputStream(Paths.get(xzFilename)), options)) {
170                 for (;;) {
171                     int readBytes = in.read(buf);
172                     if (readBytes < 0) {
173                         break;
174                     }
175                     if (readBytes == 0) {
176                         continue;
177                     }
178 
179                     out.write(buf, 0, readBytes);
180                     counter += readBytes;
181 
182                     long currentTime = System.nanoTime();
183                     if (currentTime - lastLogTime > DUMP_PROGRESS_LOGGING_INTERVAL) {
184                         logger.info("Compressing the heap dump: {} ({}%)",
185                                 xzFilename, counter * 100 / fileLength);
186                         lastLogTime = currentTime;
187                     }
188                 }
189             } catch (Throwable t) {
190                 logger.warn("Failed to compress the heap dump: {}", xzFilename, t);
191             }
192 
193             // Delete the uncompressed dump in favor of the compressed one.
194             if (!file.delete()) {
195                 logger.warn("Failed to delete the uncompressed heap dump: {}", filename);
196             }
197         }
198     }
199 
200     private static String timestamp() {
201         return new SimpleDateFormat("HHmmss.SSS").format(new Date());
202     }
203 
204     private static void dumpHeap(File file) {
205         if (hotspotMXBean == null) {
206             logger.warn("Can't dump heap: HotSpotDiagnosticMXBean unavailable");
207             return;
208         }
209 
210         final String filename = file.toString();
211         logger.info("Dumping heap: {}", filename);
212         try {
213             hotspotMXBeanDumpHeap.invoke(hotspotMXBean, filename, true);
214         } catch (Exception e) {
215             logger.warn("Failed to dump heap: {}", filename, e);
216         }
217     }
218 
219     private static void dumpThreads(File file) {
220         final String filename = file.toString();
221         try {
222             logger.info("Dumping threads: {}", filename);
223             final StringBuilder buf = new StringBuilder(8192);
224             try {
225                 for (ThreadInfo info : ManagementFactory.getThreadMXBean().dumpAllThreads(true, true)) {
226                     buf.append(info);
227                 }
228                 buf.append('\n');
229             } catch (UnsupportedOperationException ignored) {
230                 logger.warn("Can't dump threads: ThreadMXBean.dumpAllThreads() unsupported");
231                 return;
232             }
233 
234             try (OutputStream out = Files.newOutputStream(file.toPath())) {
235                 out.write(buf.toString().getBytes(CharsetUtil.UTF_8));
236             }
237         } catch (Exception e) {
238             logger.warn("Failed to dump threads: {}", filename, e);
239         }
240     }
241 
242     private TestUtils() { }
243 }