You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
306 lines
11 KiB
306 lines
11 KiB
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 javax.swing.SwingUtilities; |
|
import java.awt.EventQueue; |
|
import java.awt.Toolkit; |
|
import java.awt.AWTEvent; |
|
import java.awt.event.WindowEvent; |
|
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 |
|
* 用于卡顿检测 |
|
* 主要是两块内容 |
|
* 1.获取eventQueue中每个事件的执行时间 |
|
* 2.用一个Timer定时去检测当前执行任务的线程 |
|
*/ |
|
|
|
public final class EventDispatchThreadHangMonitor extends EventQueue { |
|
|
|
public static final EventDispatchThreadHangMonitor INSTANCE = new EventDispatchThreadHangMonitor(); |
|
/** |
|
* 一个timer |
|
*/ |
|
private Timer timer; |
|
/** |
|
* 事件唯一编码,用于方便日志的查看 |
|
*/ |
|
private static long hangCount = 0; |
|
/** |
|
* 类似于一个开关,当该值为默认的false启动时,定时任务在窗口开启前都不会对执行的事件进行检查 |
|
*/ |
|
private boolean haveShownSomeComponent = false; |
|
/** |
|
* 该链表为主要的实现定时任务的容器,在重写的dispatchEvent中由pre方法将DispatchInfo加入到链表,由post方法remove |
|
*/ |
|
private final LinkedList<DispatchInfo> dispatches = new LinkedList<DispatchInfo>(); |
|
/** |
|
* 一个变量,用于控制easy监测模式的开关 |
|
*/ |
|
private boolean easyWitch = false; |
|
private static ScheduledExecutorService scheduledExecutorService; |
|
|
|
public boolean isEasyWitch() { |
|
return easyWitch; |
|
} |
|
|
|
public void setEasyWitch(boolean easyWitch) { |
|
this.easyWitch = easyWitch; |
|
} |
|
|
|
/** |
|
* 一个变量,用于记录Timer的开关。 |
|
*/ |
|
|
|
public boolean isTimerWitch() { |
|
return timerWitch; |
|
} |
|
|
|
public void setTimerWitch(boolean timerWitch) { |
|
this.timerWitch = timerWitch; |
|
} |
|
|
|
private boolean timerWitch = false; |
|
|
|
private synchronized static long getHangCount() { |
|
return hangCount++; |
|
} |
|
|
|
/** |
|
* event事件的包装类 |
|
*/ |
|
public static class DispatchInfo { |
|
// 上一次被打印的堆栈ou |
|
private StackTraceElement[] lastReportedStack; |
|
//获取执行该事件的线程 |
|
private final Thread eventDispatchThread = Thread.currentThread(); |
|
//在队列中等待执行的事件最后未执行的时间,当有一个事件执行完后就遍历dispatches给该值赋当前时间 |
|
private long lastDispatchTimeMillis = System.currentTimeMillis(); |
|
//事件开始的时间 |
|
private final long startDispatchTimeMillis = System.currentTimeMillis(); |
|
//事件编号 |
|
private final long hangNumber; |
|
|
|
//构造函数,给当前对象赋一个递增的唯一编号 |
|
public DispatchInfo() { |
|
hangNumber = getHangCount(); |
|
} |
|
|
|
//定时调度任务检测的入口,如果执行时间大于设定的值就进入examineHang()方法 |
|
public void checkForHang() { |
|
if (timeSoFar() > UNREASONABLE_DISPATCH_DURATION_MS) { |
|
examineHang(); |
|
} |
|
} |
|
//超时堆栈的具体处理 |
|
private void examineHang() { |
|
//获取执行线程的当前堆栈 |
|
StackTraceElement[] currentStack = eventDispatchThread.getStackTrace(); |
|
if (CartonUtils.isWaitingForNextEvent(currentStack)) { |
|
return; |
|
} |
|
//某个事件执行时间很长,定时处理时可能会连续打很多个堆栈,对同一个事件的相同堆栈只打一次 |
|
if (lastReportedStack != null && CartonUtils.stacksEqual(lastReportedStack, currentStack)) { |
|
return; |
|
} |
|
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"), 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); |
|
CartonUtils.outPutJournalLog(jsonObject.toString(), SwitchForSwingChecker.TIMER_CHECK_FLAG); |
|
CartonUtils.checkForDeadlock(); |
|
} |
|
|
|
//记录连续运行了多长时间 |
|
public long timeSoFar() { |
|
return (System.currentTimeMillis() - lastDispatchTimeMillis); |
|
} |
|
|
|
//记录一个事件从被分发到结束的总运行时间 |
|
public long totalTime() { |
|
return (System.currentTimeMillis() - startDispatchTimeMillis); |
|
} |
|
//事件处理完后的时间判断 |
|
public void dispose() { |
|
if (timeSoFar() > UNREASONABLE_DISPATCH_DURATION_MS) { |
|
exportCartonLog(true); |
|
} else if (lastReportedStack != null) { |
|
exportCartonLog(false); |
|
} |
|
} |
|
|
|
/** |
|
* |
|
* @param flag 判断一下输出日志时要输出哪个时间 |
|
*/ |
|
private void exportCartonLog(boolean flag) { |
|
JSONObject jsonObject = new JSONObject(); |
|
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"), 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"); |
|
} |
|
CartonUtils.outPutJournalLog(jsonObject.toString(), SwitchForSwingChecker.EASY_CHECK_FLAG); |
|
} |
|
} |
|
|
|
private EventDispatchThreadHangMonitor() { |
|
|
|
} |
|
|
|
/** |
|
* 参考SwingExplorer,在处理模态框时没有做特殊处理,也不会输出卡顿堆栈 |
|
* 原因是SwingExplorer窗口一直有一个监听事件,不断的add,remove。 |
|
* 由于卡顿日志输出的是事件连续执行的时间,所以一个长时间存在的模态框被不断重复的监听事件刷新时间,就不会输出了。 |
|
* 当检测开关打开后,在这里模拟一下监听事件,给个不耗时的任务就可以。 |
|
*/ |
|
public void startFilterModalWindow() { |
|
scheduledExecutorService = FineExecutors.newSingleThreadScheduledExecutor(); |
|
scheduledExecutorService.scheduleAtFixedRate(new Runnable() { |
|
@Override |
|
public void run() { |
|
SwingUtilities.invokeLater(new Runnable() { |
|
@Override |
|
public void run() { |
|
//不用干事,切个片就可以 |
|
} |
|
}); |
|
} |
|
}, 0, 500, TimeUnit.MILLISECONDS); |
|
} |
|
|
|
public void stopFilterModalWindow() { |
|
if (scheduledExecutorService != null) { |
|
scheduledExecutorService.shutdown(); |
|
} |
|
} |
|
/** |
|
* Sets up a timer to check for hangs frequently. |
|
* 初始化一个Timer |
|
*/ |
|
public void initTimer() { |
|
final long initialDelayMs = 0; |
|
final boolean daemon = true; |
|
timer = new Timer("EventDispatchThreadHangMonitor", daemon); |
|
timer.schedule(new HangChecker(), initialDelayMs, CHECK_INTERVAL_MS); |
|
} |
|
|
|
/** |
|
* /消除Timer |
|
*/ |
|
public void stopTimer() { |
|
if (timer != null) { |
|
timer.cancel(); |
|
} |
|
} |
|
|
|
/** |
|
* /定时执行的任务 |
|
*/ |
|
public class HangChecker extends TimerTask { |
|
@Override |
|
public void run() { |
|
synchronized (dispatches) { |
|
//如果链表为空或者窗口还没启开,定时检测就不进行 |
|
if (dispatches.isEmpty() || !haveShownSomeComponent) { |
|
return; |
|
} |
|
dispatches.getLast().checkForHang(); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* 将swing中默认的EventQueue换成自己的 |
|
*/ |
|
public static void initMonitoring() { |
|
UIUtil.invokeLaterIfNeeded(() -> Toolkit.getDefaultToolkit().getSystemEventQueue().push(INSTANCE)); |
|
} |
|
|
|
/** |
|
* Overrides EventQueue.dispatchEvent to call our pre and post hooks either |
|
* side of the system's event dispatch code. |
|
* 重写 |
|
*/ |
|
@Override |
|
protected void dispatchEvent(AWTEvent event) { |
|
if (!useCustomEventQueue()) { |
|
super.dispatchEvent(event); |
|
} else { |
|
try { |
|
preDispatchEvent(); |
|
super.dispatchEvent(event); |
|
} finally { |
|
postDispatchEvent(); |
|
if (!haveShownSomeComponent && |
|
event instanceof WindowEvent && event.getID() == WindowEvent.WINDOW_OPENED) { |
|
haveShownSomeComponent = true; |
|
} |
|
} |
|
} |
|
} |
|
|
|
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. |
|
*/ |
|
private synchronized void preDispatchEvent() { |
|
synchronized (dispatches) { |
|
dispatches.addLast(new DispatchInfo()); |
|
} |
|
} |
|
|
|
/** |
|
* Stops tracking a dispatch. |
|
*/ |
|
private synchronized void postDispatchEvent() { |
|
synchronized (dispatches) { |
|
DispatchInfo justFinishedDispatch = dispatches.removeLast(); |
|
if (needSampling()) { |
|
DesignerLatencyMetric.getInstance().record(justFinishedDispatch.timeSoFar()); |
|
} |
|
if (isEasyWitch()) { |
|
justFinishedDispatch.dispose(); |
|
} |
|
//嵌套最深的事件执行完毕后刷新链表中其他事件的lastDispatchTimeMillis |
|
Thread currentEventDispatchThread = Thread.currentThread(); |
|
for (DispatchInfo dispatchInfo : dispatches) { |
|
if (dispatchInfo.eventDispatchThread == currentEventDispatchThread) { |
|
dispatchInfo.lastDispatchTimeMillis = System.currentTimeMillis(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
} |