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