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.PrintWriter;
021import java.io.StringWriter;
022import java.lang.management.LockInfo;
023import java.lang.management.ManagementFactory;
024import java.lang.management.MonitorInfo;
025import java.lang.management.ThreadInfo;
026import java.lang.management.ThreadMXBean;
027import java.text.DateFormat;
028import java.text.SimpleDateFormat;
029import java.util.Date;
030import java.util.Locale;
031import java.util.Map;
032import org.junit.runner.notification.Failure;
033import org.junit.runner.notification.RunListener;
034
035/**
036 * JUnit run listener which prints full thread dump into System.err in case a test is failed due to
037 * timeout.
038 */
039public class TimedOutTestsListener extends RunListener {
040
041  static final String TEST_TIMED_OUT_PREFIX = "test timed out after";
042
043  private static String INDENT = "    ";
044
045  private final PrintWriter output;
046
047  public TimedOutTestsListener() {
048    this.output = new PrintWriter(System.err);
049  }
050
051  public TimedOutTestsListener(PrintWriter output) {
052    this.output = output;
053  }
054
055  @Override
056  public void testFailure(Failure failure) throws Exception {
057    if (
058      failure != null && failure.getMessage() != null
059        && failure.getMessage().startsWith(TEST_TIMED_OUT_PREFIX)
060    ) {
061      output.println("====> TEST TIMED OUT. PRINTING THREAD DUMP. <====");
062      output.println();
063      output.print(buildThreadDiagnosticString());
064    }
065    output.flush();
066  }
067
068  public static String buildThreadDiagnosticString() {
069    StringWriter sw = new StringWriter();
070    PrintWriter output = new PrintWriter(sw);
071
072    DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss,SSS");
073    output.println(String.format("Timestamp: %s", dateFormat.format(new Date())));
074    output.println();
075    output.println(buildThreadDump());
076
077    String deadlocksInfo = buildDeadlockInfo();
078    if (deadlocksInfo != null) {
079      output.println("====> DEADLOCKS DETECTED <====");
080      output.println();
081      output.println(deadlocksInfo);
082    }
083
084    return sw.toString();
085  }
086
087  static String buildThreadDump() {
088    StringBuilder dump = new StringBuilder();
089    Map<Thread, StackTraceElement[]> stackTraces = Thread.getAllStackTraces();
090    for (Map.Entry<Thread, StackTraceElement[]> e : stackTraces.entrySet()) {
091      Thread thread = e.getKey();
092      dump.append(String.format("\"%s\" %s prio=%d tid=%d %s\njava.lang.Thread.State: %s",
093        thread.getName(), (thread.isDaemon() ? "daemon" : ""), thread.getPriority(), thread.getId(),
094        Thread.State.WAITING.equals(thread.getState())
095          ? "in Object.wait()"
096          : thread.getState().name().toLowerCase(Locale.ROOT),
097        Thread.State.WAITING.equals(thread.getState())
098          ? "WAITING (on object monitor)"
099          : thread.getState()));
100      for (StackTraceElement stackTraceElement : e.getValue()) {
101        dump.append("\n        at ");
102        dump.append(stackTraceElement);
103      }
104      dump.append("\n");
105    }
106    return dump.toString();
107  }
108
109  static String buildDeadlockInfo() {
110    ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
111    long[] threadIds = threadBean.findMonitorDeadlockedThreads();
112    if (threadIds != null && threadIds.length > 0) {
113      StringWriter stringWriter = new StringWriter();
114      PrintWriter out = new PrintWriter(stringWriter);
115
116      ThreadInfo[] infos = threadBean.getThreadInfo(threadIds, true, true);
117      for (ThreadInfo ti : infos) {
118        printThreadInfo(ti, out);
119        printLockInfo(ti.getLockedSynchronizers(), out);
120        out.println();
121      }
122
123      out.close();
124      return stringWriter.toString();
125    } else {
126      return null;
127    }
128  }
129
130  private static void printThreadInfo(ThreadInfo ti, PrintWriter out) {
131    // print thread information
132    printThread(ti, out);
133
134    // print stack trace with locks
135    StackTraceElement[] stacktrace = ti.getStackTrace();
136    MonitorInfo[] monitors = ti.getLockedMonitors();
137    for (int i = 0; i < stacktrace.length; i++) {
138      StackTraceElement ste = stacktrace[i];
139      out.println(INDENT + "at " + ste.toString());
140      for (MonitorInfo mi : monitors) {
141        if (mi.getLockedStackDepth() == i) {
142          out.println(INDENT + "  - locked " + mi);
143        }
144      }
145    }
146    out.println();
147  }
148
149  private static void printThread(ThreadInfo ti, PrintWriter out) {
150    out.print(
151      "\"" + ti.getThreadName() + "\"" + " Id=" + ti.getThreadId() + " in " + ti.getThreadState());
152    if (ti.getLockName() != null) {
153      out.print(" on lock=" + ti.getLockName());
154    }
155    if (ti.isSuspended()) {
156      out.print(" (suspended)");
157    }
158    if (ti.isInNative()) {
159      out.print(" (running in native)");
160    }
161    out.println();
162    if (ti.getLockOwnerName() != null) {
163      out.println(INDENT + " owned by " + ti.getLockOwnerName() + " Id=" + ti.getLockOwnerId());
164    }
165  }
166
167  private static void printLockInfo(LockInfo[] locks, PrintWriter out) {
168    out.println(INDENT + "Locked synchronizers: count = " + locks.length);
169    for (LockInfo li : locks) {
170      out.println(INDENT + "  - " + li);
171    }
172    out.println();
173  }
174
175}