|
|
|
@ -1,34 +1,26 @@
|
|
|
|
|
package com.fr.design.carton; |
|
|
|
|
|
|
|
|
|
import com.fr.concurrent.FineExecutors; |
|
|
|
|
import com.fr.design.carton.latency.DesignerLatencyMetric; |
|
|
|
|
import com.fr.design.ui.util.UIUtil; |
|
|
|
|
import com.fr.json.JSONObject; |
|
|
|
|
import com.fr.log.FineLoggerFactory; |
|
|
|
|
import com.fr.stable.ArrayUtils; |
|
|
|
|
import com.fr.stable.ProductConstantsBase; |
|
|
|
|
import com.fr.stable.StableUtils; |
|
|
|
|
import com.fr.stable.StringUtils; |
|
|
|
|
import org.jetbrains.annotations.NotNull; |
|
|
|
|
|
|
|
|
|
import javax.swing.SwingUtilities; |
|
|
|
|
import java.awt.EventQueue; |
|
|
|
|
import java.awt.Toolkit; |
|
|
|
|
import java.awt.AWTEvent; |
|
|
|
|
import java.awt.event.WindowEvent; |
|
|
|
|
import java.io.BufferedWriter; |
|
|
|
|
import java.io.File; |
|
|
|
|
import java.io.FileWriter; |
|
|
|
|
import java.io.IOException; |
|
|
|
|
import java.lang.management.ManagementFactory; |
|
|
|
|
import java.lang.management.ThreadInfo; |
|
|
|
|
import java.lang.management.ThreadMXBean; |
|
|
|
|
import java.text.SimpleDateFormat; |
|
|
|
|
import java.util.LinkedList; |
|
|
|
|
import java.util.Timer; |
|
|
|
|
import java.util.TimerTask; |
|
|
|
|
import java.util.concurrent.ScheduledExecutorService; |
|
|
|
|
import java.util.concurrent.TimeUnit; |
|
|
|
|
|
|
|
|
|
import static com.fr.design.carton.CartonConstants.CHECK_INTERVAL_MS; |
|
|
|
|
import static com.fr.design.carton.CartonConstants.DATE_FORMAT; |
|
|
|
|
import static com.fr.design.carton.CartonConstants.LATENCY_SAMPLING_FREQUENCY; |
|
|
|
|
import static com.fr.design.carton.CartonConstants.UNREASONABLE_DISPATCH_DURATION_MS; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 参考自git swinghelper |
|
|
|
|
* 用于卡顿检测 |
|
|
|
@ -38,31 +30,16 @@ import java.util.concurrent.TimeUnit;
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
public final class EventDispatchThreadHangMonitor extends EventQueue { |
|
|
|
|
/** |
|
|
|
|
* 日期事件格式 |
|
|
|
|
*/ |
|
|
|
|
private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); |
|
|
|
|
|
|
|
|
|
public static final EventDispatchThreadHangMonitor INSTANCE = new EventDispatchThreadHangMonitor(); |
|
|
|
|
/** |
|
|
|
|
* 一个timer |
|
|
|
|
*/ |
|
|
|
|
private Timer timer; |
|
|
|
|
/** |
|
|
|
|
* 开启间隔检测后两次检测的相隔时间ms |
|
|
|
|
*/ |
|
|
|
|
private static final long CHECK_INTERVAL_MS = 100; |
|
|
|
|
/** |
|
|
|
|
* 最大的事件允许执行时间,超过该时间则打印堆栈等相关信息 |
|
|
|
|
*/ |
|
|
|
|
private static final long UNREASONABLE_DISPATCH_DURATION_MS = 1500; |
|
|
|
|
/** |
|
|
|
|
* 事件唯一编码,用于方便日志的查看 |
|
|
|
|
*/ |
|
|
|
|
private static long hangCount = 0; |
|
|
|
|
/** |
|
|
|
|
* 输出日志所在地址 |
|
|
|
|
*/ |
|
|
|
|
private static final String JOURNAL_FILE_PATH = StableUtils.pathJoin(ProductConstantsBase.getEnvHome(), "journal_log"); |
|
|
|
|
private static long hangCount = 0; |
|
|
|
|
/** |
|
|
|
|
* 类似于一个开关,当该值为默认的false启动时,定时任务在窗口开启前都不会对执行的事件进行检查 |
|
|
|
|
*/ |
|
|
|
@ -103,43 +80,6 @@ public final class EventDispatchThreadHangMonitor extends EventQueue {
|
|
|
|
|
return hangCount++; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* @param a can not be null |
|
|
|
|
* @param b can not be null |
|
|
|
|
* @return |
|
|
|
|
*/ |
|
|
|
|
public static boolean stacksEqual(@NotNull StackTraceElement[] a, @NotNull StackTraceElement[] b) { |
|
|
|
|
|
|
|
|
|
if (!ArrayUtils.isSameLength(a, b)) { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
for (int i = 0; i < a.length; ++i) { |
|
|
|
|
if (!a[i].equals(b[i])) { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 用于判断是不是特定的堆栈 |
|
|
|
|
*/ |
|
|
|
|
public static boolean stackTraceElementIs(StackTraceElement e, String className, String methodName, boolean isNative) { |
|
|
|
|
return e.getClassName().equals(className) && e.getMethodName().equals(methodName) && e.isNativeMethod() == isNative; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 用于判断某个堆栈是否在等待另一个事件 |
|
|
|
|
* 取当前堆栈前三层判断是是不是匹配等待堆栈的格式 |
|
|
|
|
*/ |
|
|
|
|
public static boolean isWaitingForNextEvent(StackTraceElement[] currentStack) { |
|
|
|
|
|
|
|
|
|
return currentStack != null && currentStack.length >= 3 && |
|
|
|
|
stackTraceElementIs(currentStack[0], "java.lang.Object", "wait", true) |
|
|
|
|
&& stackTraceElementIs(currentStack[1], "java.lang.Object", "wait", false) |
|
|
|
|
&& stackTraceElementIs(currentStack[2], "java.awt.EventQueue", "getNextEvent", false); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* event事件的包装类 |
|
|
|
|
*/ |
|
|
|
@ -153,11 +93,13 @@ public final class EventDispatchThreadHangMonitor extends EventQueue {
|
|
|
|
|
//事件开始的时间
|
|
|
|
|
private final long startDispatchTimeMillis = System.currentTimeMillis(); |
|
|
|
|
//事件编号
|
|
|
|
|
private long hangNumber; |
|
|
|
|
private final long hangNumber; |
|
|
|
|
|
|
|
|
|
//构造函数,给当前对象赋一个递增的唯一编号
|
|
|
|
|
public DispatchInfo() { |
|
|
|
|
public DispatchInfo() { |
|
|
|
|
hangNumber = getHangCount(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//定时调度任务检测的入口,如果执行时间大于设定的值就进入examineHang()方法
|
|
|
|
|
public void checkForHang() { |
|
|
|
|
if (timeSoFar() > UNREASONABLE_DISPATCH_DURATION_MS) { |
|
|
|
@ -168,36 +110,38 @@ public final class EventDispatchThreadHangMonitor extends EventQueue {
|
|
|
|
|
private void examineHang() { |
|
|
|
|
//获取执行线程的当前堆栈
|
|
|
|
|
StackTraceElement[] currentStack = eventDispatchThread.getStackTrace(); |
|
|
|
|
if (isWaitingForNextEvent(currentStack)) { |
|
|
|
|
if (CartonUtils.isWaitingForNextEvent(currentStack)) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
//某个事件执行时间很长,定时处理时可能会连续打很多个堆栈,对同一个事件的相同堆栈只打一次
|
|
|
|
|
if (lastReportedStack !=null && stacksEqual(lastReportedStack, currentStack)) { |
|
|
|
|
if (lastReportedStack != null && CartonUtils.stacksEqual(lastReportedStack, currentStack)) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
String stackTrace = stackTraceToString(currentStack); |
|
|
|
|
String stackTrace = CartonUtils.stackTraceToString(currentStack); |
|
|
|
|
lastReportedStack = currentStack; |
|
|
|
|
JSONObject jsonObject = new JSONObject(); |
|
|
|
|
jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Output_Time"), simpleDateFormat.format(System.currentTimeMillis())); |
|
|
|
|
jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Output_Time"), DATE_FORMAT.format(System.currentTimeMillis())); |
|
|
|
|
jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Event_Number"), "eventQueue_" + hangNumber); |
|
|
|
|
jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Duration_Task_Execute"), timeSoFar() + "ms"); |
|
|
|
|
jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Stack_Info"), stackTrace); |
|
|
|
|
outPutJournalLog(jsonObject.toString(), SwitchForSwingChecker.TIMER_CHECK_FLAG); |
|
|
|
|
checkForDeadlock(); |
|
|
|
|
CartonUtils.outPutJournalLog(jsonObject.toString(), SwitchForSwingChecker.TIMER_CHECK_FLAG); |
|
|
|
|
CartonUtils.checkForDeadlock(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//记录连续运行了多长时间
|
|
|
|
|
private long timeSoFar() { |
|
|
|
|
public long timeSoFar() { |
|
|
|
|
return (System.currentTimeMillis() - lastDispatchTimeMillis); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//记录一个事件从被分发到结束的总运行时间
|
|
|
|
|
private long totalTime() { |
|
|
|
|
public long totalTime() { |
|
|
|
|
return (System.currentTimeMillis() - startDispatchTimeMillis); |
|
|
|
|
} |
|
|
|
|
//事件处理完后的时间判断
|
|
|
|
|
public void dispose() { |
|
|
|
|
if (timeSoFar() > UNREASONABLE_DISPATCH_DURATION_MS) { |
|
|
|
|
exportCartonLog(true); |
|
|
|
|
} else if (lastReportedStack != null){ |
|
|
|
|
} else if (lastReportedStack != null) { |
|
|
|
|
exportCartonLog(false); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -208,40 +152,15 @@ public final class EventDispatchThreadHangMonitor extends EventQueue {
|
|
|
|
|
*/ |
|
|
|
|
private void exportCartonLog(boolean flag) { |
|
|
|
|
JSONObject jsonObject = new JSONObject(); |
|
|
|
|
jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Output_Time"), simpleDateFormat.format(System.currentTimeMillis())); |
|
|
|
|
jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Output_Time"), DATE_FORMAT.format(System.currentTimeMillis())); |
|
|
|
|
jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Event_Number"), "eventQueue_" + hangNumber); |
|
|
|
|
jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Task_Start_Time"), simpleDateFormat.format(startDispatchTimeMillis)); |
|
|
|
|
jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Task_Start_Time"), DATE_FORMAT.format(startDispatchTimeMillis)); |
|
|
|
|
if (flag) { |
|
|
|
|
jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Task_Total_Time"), timeSoFar() + "ms"); |
|
|
|
|
} else { |
|
|
|
|
jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Task_Total_Time"), totalTime() + "ms"); |
|
|
|
|
} |
|
|
|
|
outPutJournalLog(jsonObject.toString(), SwitchForSwingChecker.EASY_CHECK_FLAG); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public static void outPutJournalLog(String message, int flag) { |
|
|
|
|
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); |
|
|
|
|
String date = simpleDateFormat.format(System.currentTimeMillis()); |
|
|
|
|
String filename = flag == SwitchForSwingChecker.EASY_CHECK_FLAG ? SwitchForSwingChecker.EASY_CHECKER_FILE_NAME: SwitchForSwingChecker.TIMER_CHECKER_FILE_NAME; |
|
|
|
|
String[] split = date.split("-"); |
|
|
|
|
int month = StringUtils.isEmpty(split[1]) ? -1 : Integer.parseInt(split[1]); |
|
|
|
|
String dirPath = StableUtils.pathJoin(JOURNAL_FILE_PATH, split[0], "month-" + month, date); |
|
|
|
|
File dirFile = new File(dirPath); |
|
|
|
|
File file = new File( StableUtils.pathJoin(dirPath, filename)); |
|
|
|
|
try { |
|
|
|
|
if (!file.exists()) { |
|
|
|
|
if (!dirFile.exists()) { |
|
|
|
|
dirFile.mkdirs(); |
|
|
|
|
} |
|
|
|
|
file.createNewFile(); |
|
|
|
|
} |
|
|
|
|
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file, true)); |
|
|
|
|
String outputMessage = new StringBuilder(message.replaceAll("~", "\r\n")).append(",").append("\r\n").toString(); |
|
|
|
|
bufferedWriter.write(outputMessage); |
|
|
|
|
bufferedWriter.close(); |
|
|
|
|
} catch (IOException e) { |
|
|
|
|
FineLoggerFactory.getLogger().error("output fail", e); |
|
|
|
|
CartonUtils.outPutJournalLog(jsonObject.toString(), SwitchForSwingChecker.EASY_CHECK_FLAG); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -325,8 +244,7 @@ public final class EventDispatchThreadHangMonitor extends EventQueue {
|
|
|
|
|
*/ |
|
|
|
|
@Override |
|
|
|
|
protected void dispatchEvent(AWTEvent event) { |
|
|
|
|
//如果两个开关都没开,那就不走重写方法了
|
|
|
|
|
if (!isEasyWitch() && !isTimerWitch()) { |
|
|
|
|
if (!useCustomEventQueue()) { |
|
|
|
|
super.dispatchEvent(event); |
|
|
|
|
} else { |
|
|
|
|
try { |
|
|
|
@ -342,6 +260,18 @@ public final class EventDispatchThreadHangMonitor extends EventQueue {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private boolean useCustomEventQueue() { |
|
|
|
|
// 开启性能监控或开启卡顿工具箱,则走自定义的EventQueue
|
|
|
|
|
return SwitchForSwingChecker.isLatencyMonitoring() || |
|
|
|
|
isEasyWitch() || isTimerWitch(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private boolean needSampling() { |
|
|
|
|
// UI性能采样逻辑:开启采样并且符合采样频次
|
|
|
|
|
return SwitchForSwingChecker.isLatencyMonitoring() |
|
|
|
|
&& (hangCount % LATENCY_SAMPLING_FREQUENCY == 0); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Starts tracking a dispatch. |
|
|
|
|
*/ |
|
|
|
@ -357,6 +287,9 @@ public final class EventDispatchThreadHangMonitor extends EventQueue {
|
|
|
|
|
private synchronized void postDispatchEvent() { |
|
|
|
|
synchronized (dispatches) { |
|
|
|
|
DispatchInfo justFinishedDispatch = dispatches.removeLast(); |
|
|
|
|
if (needSampling()) { |
|
|
|
|
DesignerLatencyMetric.getInstance().record(justFinishedDispatch.timeSoFar()); |
|
|
|
|
} |
|
|
|
|
if (isEasyWitch()) { |
|
|
|
|
justFinishedDispatch.dispose(); |
|
|
|
|
} |
|
|
|
@ -370,39 +303,4 @@ public final class EventDispatchThreadHangMonitor extends EventQueue {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 检查死锁 |
|
|
|
|
*/ |
|
|
|
|
public static void checkForDeadlock() { |
|
|
|
|
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); |
|
|
|
|
long[] threadIds = threadBean.findDeadlockedThreads(); |
|
|
|
|
if (threadIds == null) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
FineLoggerFactory.getLogger().warn("deadlock detected involving the following threads:"); |
|
|
|
|
ThreadInfo[] threadInfos = threadBean.getThreadInfo(threadIds, Integer.MAX_VALUE); |
|
|
|
|
for (ThreadInfo info : threadInfos) { |
|
|
|
|
FineLoggerFactory.getLogger().warn("Thread # {} {} ( {} ) waiting on {} held by {} {}", info.getThreadId(), info.getThreadName(), |
|
|
|
|
info.getThreadState(), info.getLockName(), info.getLockOwnerName(), stackTraceToStringForConsole(info.getStackTrace())); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public static String stackTraceToString(StackTraceElement[] stackTrace) { |
|
|
|
|
StringBuilder result = new StringBuilder(); |
|
|
|
|
for (StackTraceElement stackTraceElement : stackTrace) { |
|
|
|
|
String indentation = " "; |
|
|
|
|
result.append("~").append(indentation).append(stackTraceElement); |
|
|
|
|
} |
|
|
|
|
return result.toString(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public static String stackTraceToStringForConsole(StackTraceElement[] stackTrace) { |
|
|
|
|
StringBuilder result = new StringBuilder(); |
|
|
|
|
for (StackTraceElement stackTraceElement : stackTrace) { |
|
|
|
|
String indentation = " "; |
|
|
|
|
result.append("\r\n").append(indentation).append(stackTraceElement); |
|
|
|
|
} |
|
|
|
|
return result.toString(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |