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}