001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase;
019
020import java.io.OutputStreamWriter;
021import java.io.PrintWriter;
022import java.io.StringWriter;
023import java.lang.management.LockInfo;
024import java.lang.management.ManagementFactory;
025import java.lang.management.MonitorInfo;
026import java.lang.management.ThreadInfo;
027import java.lang.management.ThreadMXBean;
028import java.nio.charset.StandardCharsets;
029import java.text.DateFormat;
030import java.text.SimpleDateFormat;
031import java.util.Date;
032import java.util.Locale;
033import java.util.Map;
034import org.junit.runner.notification.Failure;
035import org.junit.runner.notification.RunListener;
036
037/**
038 * JUnit run listener which prints full thread dump into System.err in case a test is failed due to
039 * timeout.
040 */
041public class TimedOutTestsListener extends RunListener {
042
043  static final String TEST_TIMED_OUT_PREFIX = "test timed out after";
044
045  private static String INDENT = "    ";
046
047  private final PrintWriter output;
048
049  public TimedOutTestsListener() {
050    this.output = new PrintWriter(new OutputStreamWriter(System.err, StandardCharsets.UTF_8));
051  }
052
053  public TimedOutTestsListener(PrintWriter output) {
054    this.output = output;
055  }
056
057  @Override
058  public void testFailure(Failure failure) throws Exception {
059    if (
060      failure != null && failure.getMessage() != null
061        && failure.getMessage().startsWith(TEST_TIMED_OUT_PREFIX)
062    ) {
063      output.println("====> TEST TIMED OUT. PRINTING THREAD DUMP. <====");
064      output.println();
065      output.print(buildThreadDiagnosticString());
066    }
067    output.flush();
068  }
069
070  @SuppressWarnings("JavaUtilDate")
071  public static String buildThreadDiagnosticString() {
072    StringWriter sw = new StringWriter();
073    PrintWriter output = new PrintWriter(sw);
074
075    DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss,SSS");
076    output.println(String.format("Timestamp: %s", dateFormat.format(new Date())));
077    output.println();
078    output.println(buildThreadDump());
079
080    String deadlocksInfo = buildDeadlockInfo();
081    if (deadlocksInfo != null) {
082      output.println("====> DEADLOCKS DETECTED <====");
083      output.println();
084      output.println(deadlocksInfo);
085    }
086
087    return sw.toString();
088  }
089
090  static String buildThreadDump() {
091    StringBuilder dump = new StringBuilder();
092    Map<Thread, StackTraceElement[]> stackTraces = Thread.getAllStackTraces();
093    for (Map.Entry<Thread, StackTraceElement[]> e : stackTraces.entrySet()) {
094      Thread thread = e.getKey();
095      dump.append(String.format("\"%s\" %s prio=%d tid=%d %s\njava.lang.Thread.State: %s",
096        thread.getName(), (thread.isDaemon() ? "daemon" : ""), thread.getPriority(), thread.getId(),
097        Thread.State.WAITING.equals(thread.getState())
098          ? "in Object.wait()"
099          : thread.getState().name().toLowerCase(Locale.ROOT),
100        Thread.State.WAITING.equals(thread.getState())
101          ? "WAITING (on object monitor)"
102          : thread.getState()));
103      for (StackTraceElement stackTraceElement : e.getValue()) {
104        dump.append("\n        at ");
105        dump.append(stackTraceElement);
106      }
107      dump.append("\n");
108    }
109    return dump.toString();
110  }
111
112  static String buildDeadlockInfo() {
113    ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
114    long[] threadIds = threadBean.findMonitorDeadlockedThreads();
115    if (threadIds != null && threadIds.length > 0) {
116      StringWriter stringWriter = new StringWriter();
117      PrintWriter out = new PrintWriter(stringWriter);
118
119      ThreadInfo[] infos = threadBean.getThreadInfo(threadIds, true, true);
120      for (ThreadInfo ti : infos) {
121        printThreadInfo(ti, out);
122        printLockInfo(ti.getLockedSynchronizers(), out);
123        out.println();
124      }
125
126      out.close();
127      return stringWriter.toString();
128    } else {
129      return null;
130    }
131  }
132
133  private static void printThreadInfo(ThreadInfo ti, PrintWriter out) {
134    // print thread information
135    printThread(ti, out);
136
137    // print stack trace with locks
138    StackTraceElement[] stacktrace = ti.getStackTrace();
139    MonitorInfo[] monitors = ti.getLockedMonitors();
140    for (int i = 0; i < stacktrace.length; i++) {
141      StackTraceElement ste = stacktrace[i];
142      out.println(INDENT + "at " + ste.toString());
143      for (MonitorInfo mi : monitors) {
144        if (mi.getLockedStackDepth() == i) {
145          out.println(INDENT + "  - locked " + mi);
146        }
147      }
148    }
149    out.println();
150  }
151
152  private static void printThread(ThreadInfo ti, PrintWriter out) {
153    out.print(
154      "\"" + ti.getThreadName() + "\"" + " Id=" + ti.getThreadId() + " in " + ti.getThreadState());
155    if (ti.getLockName() != null) {
156      out.print(" on lock=" + ti.getLockName());
157    }
158    if (ti.isSuspended()) {
159      out.print(" (suspended)");
160    }
161    if (ti.isInNative()) {
162      out.print(" (running in native)");
163    }
164    out.println();
165    if (ti.getLockOwnerName() != null) {
166      out.println(INDENT + " owned by " + ti.getLockOwnerName() + " Id=" + ti.getLockOwnerId());
167    }
168  }
169
170  private static void printLockInfo(LockInfo[] locks, PrintWriter out) {
171    out.println(INDENT + "Locked synchronizers: count = " + locks.length);
172    for (LockInfo li : locks) {
173      out.println(INDENT + "  - " + li);
174    }
175    out.println();
176  }
177
178}