Browse Source

Pull request #14193: REPORT-117002 feat:设计器UI性能监控

Merge in DESIGN/design from ~LEVY.XIE/design:feature/x to feature/x

* commit '727a94e47554c9897720a01a62e129c89aac73fc':
  REPORT-117002 feat:设计器UI性能监控
  REPORT-117002 feat:设计器UI性能监控
feature/x
Levy.Xie-解安森 5 months ago
parent
commit
66028303d7
  1. 61
      designer-base/src/main/java/com/fr/design/carton/CartonConstants.java
  2. 10
      designer-base/src/main/java/com/fr/design/carton/CartonThreadExecutorPool.java
  3. 147
      designer-base/src/main/java/com/fr/design/carton/CartonUtils.java
  4. 186
      designer-base/src/main/java/com/fr/design/carton/EventDispatchThreadHangMonitor.java
  5. 4
      designer-base/src/main/java/com/fr/design/carton/FeedbackToolboxDialog.java
  6. 37
      designer-base/src/main/java/com/fr/design/carton/SwitchForSwingChecker.java
  7. 159
      designer-base/src/main/java/com/fr/design/carton/latency/DesignerLatencyMetric.java
  8. 67
      designer-base/src/main/java/com/fr/design/carton/latency/LatencyLevel.java
  9. 2
      designer-base/src/main/java/com/fr/design/mainframe/DesignerFrame.java
  10. 2
      designer-realize/src/main/java/com/fr/start/MainDesigner.java

61
designer-base/src/main/java/com/fr/design/carton/CartonConstants.java

@ -0,0 +1,61 @@
package com.fr.design.carton;
import com.fr.stable.ProductConstantsBase;
import com.fr.stable.StableUtils;
import java.text.SimpleDateFormat;
/**
* 卡顿常量类管理
*
* @author Levy.Xie
* @since 11.0
* Created on 2024/07/04
*/
public class CartonConstants {
public static final String TIME = "time";
public static final String APPID = "appId";
public static final String USERID = "userId";
public static final String LOCAL = "local";
public static final String REMOTE = "remote";
public static final String OPERANDS_NUM = "operands";
public static final String DESIGN_METHOD = "designMethod";
public static final String DESIGNER_VERSION = "designerVersion";
public static final String DESIGNER_ID = "designerId";
public static final String EASY_CHECKER_FILE_NAME = "easy_check_log.csv";
public static final String TIMER_CHECKER_FILE_NAME = "timer_check_log.csv";
/**
* 开启间隔检测后两次检测的相隔时间ms
*/
public static final long CHECK_INTERVAL_MS = 100;
/**
* 最大的事件允许执行时间超过该时间则打印堆栈等相关信息
*/
public static final long UNREASONABLE_DISPATCH_DURATION_MS = 1500;
/**
* UI检测采样频率
*/
public static final int LATENCY_SAMPLING_FREQUENCY = 100;
/**
* 输出日志所在地址
*/
public static final String JOURNAL_FILE_PATH = StableUtils.pathJoin(ProductConstantsBase.getEnvHome(), "journal_log");
/**
* 日期事件格式
*/
public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* Designer4Debug类名
*/
public static final String DEBUG_MAIN_CLASS_NAME = "com.fr.start.Designer4Debug";
}

10
designer-base/src/main/java/com/fr/design/carton/CartonThreadExecutorPool.java

@ -57,18 +57,18 @@ public class CartonThreadExecutorPool extends ThreadPoolExecutor {
private void examineHang() { private void examineHang() {
StackTraceElement[] currentStack = eventThread.getStackTrace(); StackTraceElement[] currentStack = eventThread.getStackTrace();
if (lastReportedStack!=null && EventDispatchThreadHangMonitor.stacksEqual(currentStack, lastReportedStack)) { if (lastReportedStack!=null && CartonUtils.stacksEqual(currentStack, lastReportedStack)) {
return; return;
} }
lastReportedStack = currentStack; lastReportedStack = currentStack;
String stackTrace = EventDispatchThreadHangMonitor.stackTraceToString(currentStack); String stackTrace = CartonUtils.stackTraceToString(currentStack);
JSONObject jsonObject = new JSONObject(); JSONObject jsonObject = new JSONObject();
jsonObject.put(Toolkit.i18nText("Fine-Design_Basic_Carton_Output_Time"), simpleDateFormatThreadSafe.format(System.currentTimeMillis())); jsonObject.put(Toolkit.i18nText("Fine-Design_Basic_Carton_Output_Time"), simpleDateFormatThreadSafe.format(System.currentTimeMillis()));
jsonObject.put(Toolkit.i18nText("Fine-Design_Basic_Carton_Event_Number"), "swingWorker_" + hangNumber); jsonObject.put(Toolkit.i18nText("Fine-Design_Basic_Carton_Event_Number"), "swingWorker_" + hangNumber);
jsonObject.put(Toolkit.i18nText("Fine-Design_Basic_Carton_Duration_Task_Execute"), timeSoFar() + "ms"); jsonObject.put(Toolkit.i18nText("Fine-Design_Basic_Carton_Duration_Task_Execute"), timeSoFar() + "ms");
jsonObject.put(Toolkit.i18nText("Fine-Design_Basic_Carton_Stack_Info"), stackTrace); jsonObject.put(Toolkit.i18nText("Fine-Design_Basic_Carton_Stack_Info"), stackTrace);
EventDispatchThreadHangMonitor.outPutJournalLog(jsonObject.toString(), SwitchForSwingChecker.TIMER_CHECK_FLAG); CartonUtils.outPutJournalLog(jsonObject.toString(), SwitchForSwingChecker.TIMER_CHECK_FLAG);
EventDispatchThreadHangMonitor.checkForDeadlock(); CartonUtils.checkForDeadlock();
} }
} }
@ -129,7 +129,7 @@ public class CartonThreadExecutorPool extends ThreadPoolExecutor {
jsonObject.put(Toolkit.i18nText("Fine-Design_Basic_Carton_Event_Number"), "swingWorker_" + concurrentHashMap.get(currentThreadId).hangNumber); jsonObject.put(Toolkit.i18nText("Fine-Design_Basic_Carton_Event_Number"), "swingWorker_" + concurrentHashMap.get(currentThreadId).hangNumber);
jsonObject.put(Toolkit.i18nText("Fine-Design_Basic_Carton_Task_Start_Time"), simpleDateFormatThreadSafe.format(concurrentHashMap.get(currentThreadId).startTime)); jsonObject.put(Toolkit.i18nText("Fine-Design_Basic_Carton_Task_Start_Time"), simpleDateFormatThreadSafe.format(concurrentHashMap.get(currentThreadId).startTime));
jsonObject.put(Toolkit.i18nText("Fine-Design_Basic_Carton_Task_Total_Time"), runTime + "ms"); jsonObject.put(Toolkit.i18nText("Fine-Design_Basic_Carton_Task_Total_Time"), runTime + "ms");
EventDispatchThreadHangMonitor.outPutJournalLog(jsonObject.toString(), SwitchForSwingChecker.EASY_CHECK_FLAG); CartonUtils.outPutJournalLog(jsonObject.toString(), SwitchForSwingChecker.EASY_CHECK_FLAG);
} }
concurrentHashMap.remove(currentThreadId); concurrentHashMap.remove(currentThreadId);

147
designer-base/src/main/java/com/fr/design/carton/CartonUtils.java

@ -0,0 +1,147 @@
package com.fr.design.carton;
import com.fr.log.FineLoggerFactory;
import com.fr.stable.ArrayUtils;
import com.fr.stable.StableUtils;
import com.fr.stable.StringUtils;
import org.jetbrains.annotations.NotNull;
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 static com.fr.design.carton.CartonConstants.EASY_CHECKER_FILE_NAME;
import static com.fr.design.carton.CartonConstants.JOURNAL_FILE_PATH;
import static com.fr.design.carton.CartonConstants.TIMER_CHECKER_FILE_NAME;
/**
* 设计器卡顿业务工具类
*
* @author Levy.Xie
* @since 11.0
* Created on 2024/07/10
*/
public class CartonUtils {
/**
* 堆栈元素输出为文本
*
* @param stackTrace 堆栈元素
* @return 文本
*/
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();
}
/**
* 命令行显示堆栈信息
*
* @param stackTrace 堆栈元素
* @return 文本
*/
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();
}
/**
* 堆栈是否一致
*
* @param a 堆栈A
* @param b 堆栈B
* @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 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(), CartonUtils.stackTraceToStringForConsole(info.getStackTrace()));
}
}
/**
* 输出卡顿日志
*
* @param message 文本信息
* @param flag 类型true时为简单检查false时为定时检查
*/
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 ? EASY_CHECKER_FILE_NAME : 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();
}
try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file, true))) {
String outputMessage = message.replaceAll("~", "\r\n") + "," + "\r\n";
bufferedWriter.write(outputMessage);
}
} catch (IOException e) {
FineLoggerFactory.getLogger().error("output fail", e);
}
}
/**
* 用于判断是不是特定的堆栈
*/
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);
}
}

186
designer-base/src/main/java/com/fr/design/carton/EventDispatchThreadHangMonitor.java

@ -1,34 +1,26 @@
package com.fr.design.carton; package com.fr.design.carton;
import com.fr.concurrent.FineExecutors; import com.fr.concurrent.FineExecutors;
import com.fr.design.carton.latency.DesignerLatencyMetric;
import com.fr.design.ui.util.UIUtil; import com.fr.design.ui.util.UIUtil;
import com.fr.json.JSONObject; 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 javax.swing.SwingUtilities;
import java.awt.EventQueue; import java.awt.EventQueue;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.awt.AWTEvent; import java.awt.AWTEvent;
import java.awt.event.WindowEvent; 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.LinkedList;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; 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 * 参考自git swinghelper
* 用于卡顿检测 * 用于卡顿检测
@ -38,31 +30,16 @@ import java.util.concurrent.TimeUnit;
*/ */
public final class EventDispatchThreadHangMonitor extends EventQueue { 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(); public static final EventDispatchThreadHangMonitor INSTANCE = new EventDispatchThreadHangMonitor();
/** /**
* 一个timer * 一个timer
*/ */
private Timer 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 long hangCount = 0;
/**
* 输出日志所在地址
*/
private static final String JOURNAL_FILE_PATH = StableUtils.pathJoin(ProductConstantsBase.getEnvHome(), "journal_log");
/** /**
* 类似于一个开关当该值为默认的false启动时定时任务在窗口开启前都不会对执行的事件进行检查 * 类似于一个开关当该值为默认的false启动时定时任务在窗口开启前都不会对执行的事件进行检查
*/ */
@ -103,43 +80,6 @@ public final class EventDispatchThreadHangMonitor extends EventQueue {
return hangCount++; 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事件的包装类 * event事件的包装类
*/ */
@ -153,11 +93,13 @@ public final class EventDispatchThreadHangMonitor extends EventQueue {
//事件开始的时间 //事件开始的时间
private final long startDispatchTimeMillis = System.currentTimeMillis(); private final long startDispatchTimeMillis = System.currentTimeMillis();
//事件编号 //事件编号
private long hangNumber; private final long hangNumber;
//构造函数,给当前对象赋一个递增的唯一编号 //构造函数,给当前对象赋一个递增的唯一编号
public DispatchInfo() { public DispatchInfo() {
hangNumber = getHangCount(); hangNumber = getHangCount();
} }
//定时调度任务检测的入口,如果执行时间大于设定的值就进入examineHang()方法 //定时调度任务检测的入口,如果执行时间大于设定的值就进入examineHang()方法
public void checkForHang() { public void checkForHang() {
if (timeSoFar() > UNREASONABLE_DISPATCH_DURATION_MS) { if (timeSoFar() > UNREASONABLE_DISPATCH_DURATION_MS) {
@ -168,36 +110,38 @@ public final class EventDispatchThreadHangMonitor extends EventQueue {
private void examineHang() { private void examineHang() {
//获取执行线程的当前堆栈 //获取执行线程的当前堆栈
StackTraceElement[] currentStack = eventDispatchThread.getStackTrace(); StackTraceElement[] currentStack = eventDispatchThread.getStackTrace();
if (isWaitingForNextEvent(currentStack)) { if (CartonUtils.isWaitingForNextEvent(currentStack)) {
return; return;
} }
//某个事件执行时间很长,定时处理时可能会连续打很多个堆栈,对同一个事件的相同堆栈只打一次 //某个事件执行时间很长,定时处理时可能会连续打很多个堆栈,对同一个事件的相同堆栈只打一次
if (lastReportedStack !=null && stacksEqual(lastReportedStack, currentStack)) { if (lastReportedStack != null && CartonUtils.stacksEqual(lastReportedStack, currentStack)) {
return; return;
} }
String stackTrace = stackTraceToString(currentStack); String stackTrace = CartonUtils.stackTraceToString(currentStack);
lastReportedStack = currentStack; lastReportedStack = currentStack;
JSONObject jsonObject = new JSONObject(); 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_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_Duration_Task_Execute"), timeSoFar() + "ms");
jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Stack_Info"), stackTrace); jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Stack_Info"), stackTrace);
outPutJournalLog(jsonObject.toString(), SwitchForSwingChecker.TIMER_CHECK_FLAG); CartonUtils.outPutJournalLog(jsonObject.toString(), SwitchForSwingChecker.TIMER_CHECK_FLAG);
checkForDeadlock(); CartonUtils.checkForDeadlock();
} }
//记录连续运行了多长时间 //记录连续运行了多长时间
private long timeSoFar() { public long timeSoFar() {
return (System.currentTimeMillis() - lastDispatchTimeMillis); return (System.currentTimeMillis() - lastDispatchTimeMillis);
} }
//记录一个事件从被分发到结束的总运行时间 //记录一个事件从被分发到结束的总运行时间
private long totalTime() { public long totalTime() {
return (System.currentTimeMillis() - startDispatchTimeMillis); return (System.currentTimeMillis() - startDispatchTimeMillis);
} }
//事件处理完后的时间判断 //事件处理完后的时间判断
public void dispose() { public void dispose() {
if (timeSoFar() > UNREASONABLE_DISPATCH_DURATION_MS) { if (timeSoFar() > UNREASONABLE_DISPATCH_DURATION_MS) {
exportCartonLog(true); exportCartonLog(true);
} else if (lastReportedStack != null){ } else if (lastReportedStack != null) {
exportCartonLog(false); exportCartonLog(false);
} }
} }
@ -208,40 +152,15 @@ public final class EventDispatchThreadHangMonitor extends EventQueue {
*/ */
private void exportCartonLog(boolean flag) { private void exportCartonLog(boolean flag) {
JSONObject jsonObject = new JSONObject(); 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_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) { if (flag) {
jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Task_Total_Time"), timeSoFar() + "ms"); jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Task_Total_Time"), timeSoFar() + "ms");
} else { } else {
jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Task_Total_Time"), totalTime() + "ms"); jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Task_Total_Time"), totalTime() + "ms");
} }
outPutJournalLog(jsonObject.toString(), SwitchForSwingChecker.EASY_CHECK_FLAG); CartonUtils.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);
} }
} }
@ -325,8 +244,7 @@ public final class EventDispatchThreadHangMonitor extends EventQueue {
*/ */
@Override @Override
protected void dispatchEvent(AWTEvent event) { protected void dispatchEvent(AWTEvent event) {
//如果两个开关都没开,那就不走重写方法了 if (!useCustomEventQueue()) {
if (!isEasyWitch() && !isTimerWitch()) {
super.dispatchEvent(event); super.dispatchEvent(event);
} else { } else {
try { 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. * Starts tracking a dispatch.
*/ */
@ -357,6 +287,9 @@ public final class EventDispatchThreadHangMonitor extends EventQueue {
private synchronized void postDispatchEvent() { private synchronized void postDispatchEvent() {
synchronized (dispatches) { synchronized (dispatches) {
DispatchInfo justFinishedDispatch = dispatches.removeLast(); DispatchInfo justFinishedDispatch = dispatches.removeLast();
if (needSampling()) {
DesignerLatencyMetric.getInstance().record(justFinishedDispatch.timeSoFar());
}
if (isEasyWitch()) { if (isEasyWitch()) {
justFinishedDispatch.dispose(); 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();
}
} }

4
designer-base/src/main/java/com/fr/design/carton/FeedbackToolboxDialog.java

@ -49,6 +49,8 @@ import java.text.ParseException;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import static com.fr.design.carton.CartonConstants.JOURNAL_FILE_PATH;
public class FeedbackToolboxDialog extends JDialog { public class FeedbackToolboxDialog extends JDialog {
private UIDatePicker uiDatePicker; private UIDatePicker uiDatePicker;
@ -147,7 +149,7 @@ public class FeedbackToolboxDialog extends JDialog {
//selectDate 2002-03-09例子 //selectDate 2002-03-09例子
String[] split = selectDate.split("-"); String[] split = selectDate.split("-");
int month = Integer.parseInt(split[1]); int month = Integer.parseInt(split[1]);
String sourceFilePath = StableUtils.pathJoin(SwitchForSwingChecker.JOURNAL_FILE_PATH, split[0], "month-" + month, selectDate); String sourceFilePath = StableUtils.pathJoin(JOURNAL_FILE_PATH, split[0], "month-" + month, selectDate);
File sourceFile = new File(sourceFilePath); File sourceFile = new File(sourceFilePath);
if (sourceFile.exists()) { if (sourceFile.exists()) {
exportCartonLog(sourceFile, path, sourceFilePath); exportCartonLog(sourceFile, path, sourceFilePath);

37
designer-base/src/main/java/com/fr/design/carton/SwitchForSwingChecker.java

@ -7,7 +7,6 @@ import com.fr.json.JSON;
import com.fr.json.JSONArray; import com.fr.json.JSONArray;
import com.fr.json.JSONObject; import com.fr.json.JSONObject;
import com.fr.log.FineLoggerFactory; import com.fr.log.FineLoggerFactory;
import com.fr.stable.ProductConstantsBase;
import com.fr.stable.StableUtils; import com.fr.stable.StableUtils;
import com.fr.stable.StringUtils; import com.fr.stable.StringUtils;
import com.fr.stable.xml.XMLPrintWriter; import com.fr.stable.xml.XMLPrintWriter;
@ -29,11 +28,13 @@ import java.util.Map;
import java.util.Date; import java.util.Date;
import java.util.Calendar; import java.util.Calendar;
import static com.fr.design.carton.CartonConstants.DEBUG_MAIN_CLASS_NAME;
import static com.fr.design.carton.CartonConstants.EASY_CHECKER_FILE_NAME;
import static com.fr.design.carton.CartonConstants.JOURNAL_FILE_PATH;
import static com.fr.design.carton.CartonConstants.TIMER_CHECKER_FILE_NAME;
public class SwitchForSwingChecker implements XMLReadable, XMLWriter { public class SwitchForSwingChecker implements XMLReadable, XMLWriter {
/**
* Designer4Debug类名
*/
private static final String DEBUG_MAIN_CLASS_NAME = "com.fr.start.Designer4Debug";
/** /**
* XML标签 * XML标签
*/ */
@ -46,18 +47,16 @@ public class SwitchForSwingChecker implements XMLReadable, XMLWriter {
* 简单记录事件执行时间的开关 * 简单记录事件执行时间的开关
*/ */
private static boolean easyChecker = false; private static boolean easyChecker = false;
/**
* UI性能监控埋点的开关
*/
private static boolean latencyMonitor = true;
/** /**
* 一个标识位用于区分耗时任务时长检测(简单检测)和timer检测 * 一个标识位用于区分耗时任务时长检测(简单检测)和timer检测
*/ */
public static final int TIMER_CHECK_FLAG = 0; public static final int TIMER_CHECK_FLAG = 0;
public static final int EASY_CHECK_FLAG = 1; public static final int EASY_CHECK_FLAG = 1;
/**
* 日志存储地址
*/
public static final String JOURNAL_FILE_PATH = StableUtils.pathJoin(ProductConstantsBase.getEnvHome(), "journal_log");
public static final String EASY_CHECKER_FILE_NAME = "easy_check_log.csv";
public static final String TIMER_CHECKER_FILE_NAME = "timer_check_log.csv";
public static boolean isCheckerTimerSwitch() { public static boolean isCheckerTimerSwitch() {
return checkerTimerSwitch; return checkerTimerSwitch;
} }
@ -66,6 +65,13 @@ public class SwitchForSwingChecker implements XMLReadable, XMLWriter {
return easyChecker; return easyChecker;
} }
/**
* 是否开启UI性能检测
*/
public static boolean isLatencyMonitoring() {
return latencyMonitor;
}
public static volatile SwitchForSwingChecker switchForSwingChecker = new SwitchForSwingChecker(); public static volatile SwitchForSwingChecker switchForSwingChecker = new SwitchForSwingChecker();
public static SwitchForSwingChecker getInstance() { public static SwitchForSwingChecker getInstance() {
@ -137,7 +143,7 @@ public class SwitchForSwingChecker implements XMLReadable, XMLWriter {
} }
/** /**
*处理文件 * 处理文件
* 一共四种情况 * 一共四种情况
* 两个文件都不存在 * 两个文件都不存在
* 文件一存在文件二不存在 * 文件一存在文件二不存在
@ -224,7 +230,7 @@ public class SwitchForSwingChecker implements XMLReadable, XMLWriter {
/** /**
* /埋点方法上传卡顿信息入口 * /埋点方法上传卡顿信息入口
date为 2022-09-08的格式 * date为 2022-09-08的格式
*/ */
public static List<CartonUploadMessage> uploadJournalLog(Date dateTime) { public static List<CartonUploadMessage> uploadJournalLog(Date dateTime) {
List<CartonUploadMessage> res = new ArrayList<>(); List<CartonUploadMessage> res = new ArrayList<>();
@ -245,9 +251,8 @@ public class SwitchForSwingChecker implements XMLReadable, XMLWriter {
/** /**
* 初始化监控任务主要是替换EventQueue以及SwingWorker执行任务的线程池 * 初始化监控任务主要是替换EventQueue以及SwingWorker执行任务的线程池
*
*/ */
public static void initThreadMonitoring () { public static void initThreadMonitoring() {
String mainClass = System.getProperty("sun.java.command"); String mainClass = System.getProperty("sun.java.command");
//判断一下,如果是以Designer4Debug启动,就不注册代码,不然会覆盖掉SwingExplorer,导致其无法使用 //判断一下,如果是以Designer4Debug启动,就不注册代码,不然会覆盖掉SwingExplorer,导致其无法使用
if (!StringUtils.equals(mainClass, DEBUG_MAIN_CLASS_NAME)) { if (!StringUtils.equals(mainClass, DEBUG_MAIN_CLASS_NAME)) {
@ -293,6 +298,7 @@ public class SwitchForSwingChecker implements XMLReadable, XMLWriter {
if (reader.isAttr()) { if (reader.isAttr()) {
checkerTimerSwitch = reader.getAttrAsBoolean("checkerTimerSwitch", false); checkerTimerSwitch = reader.getAttrAsBoolean("checkerTimerSwitch", false);
easyChecker = reader.getAttrAsBoolean("easyChecker", false); easyChecker = reader.getAttrAsBoolean("easyChecker", false);
latencyMonitor = reader.getAttrAsBoolean("latencyMonitor", true);
} }
try { try {
initSwitchChecker(); initSwitchChecker();
@ -306,6 +312,7 @@ public class SwitchForSwingChecker implements XMLReadable, XMLWriter {
writer.startTAG(XML_TAG); writer.startTAG(XML_TAG);
writer.attr("checkerTimerSwitch", checkerTimerSwitch); writer.attr("checkerTimerSwitch", checkerTimerSwitch);
writer.attr("easyChecker", easyChecker); writer.attr("easyChecker", easyChecker);
writer.attr("latencyMonitor", latencyMonitor);
writer.end(); writer.end();
} }

159
designer-base/src/main/java/com/fr/design/carton/latency/DesignerLatencyMetric.java

@ -0,0 +1,159 @@
package com.fr.design.carton.latency;
import com.fanruan.third.v2.org.ehcache.impl.internal.concurrent.ConcurrentHashMap;
import com.fr.concurrent.FineExecutors;
import com.fr.concurrent.NamedThreadFactory;
import com.fr.config.MarketConfig;
import com.fr.design.DesignerEnvManager;
import com.fr.design.carton.SwitchForSwingChecker;
import com.fr.design.mainframe.SiteCenterToken;
import com.fr.event.Event;
import com.fr.event.EventDispatcher;
import com.fr.event.Listener;
import com.fr.general.http.HttpToolbox;
import com.fr.json.JSONObject;
import com.fr.stable.ProductConstants;
import com.fr.workspace.WorkContext;
import com.fr.workspace.Workspace;
import com.fr.workspace.WorkspaceEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static com.fr.design.carton.CartonConstants.APPID;
import static com.fr.design.carton.CartonConstants.DESIGNER_ID;
import static com.fr.design.carton.CartonConstants.DESIGNER_VERSION;
import static com.fr.design.carton.CartonConstants.DESIGN_METHOD;
import static com.fr.design.carton.CartonConstants.LOCAL;
import static com.fr.design.carton.CartonConstants.OPERANDS_NUM;
import static com.fr.design.carton.CartonConstants.REMOTE;
import static com.fr.design.carton.CartonConstants.TIME;
import static com.fr.design.carton.CartonConstants.USERID;
/**
* 设计器延迟时间记录Metric
*
* @author Levy.Xie
* @since 11.0
* Created on 2024/07/01
*/
public class DesignerLatencyMetric {
private ExecutorService executorService;
private ScheduledExecutorService scheduler;
private static final Map<LatencyLevel, AtomicInteger> LATENCY_CONTAINER = new ConcurrentHashMap<>();
private static final String LATENCY_INFO_URL = "https://cloud.fanruan.com/api/monitor/record_of_deisgner_latency/single";
private final static class InstanceHolder {
static final DesignerLatencyMetric INSTANCE = new DesignerLatencyMetric();
}
/**
* 获取单例
*/
public static DesignerLatencyMetric getInstance() {
return DesignerLatencyMetric.InstanceHolder.INSTANCE;
}
private DesignerLatencyMetric() {
}
/**
* 启动
*/
public void start() {
if (SwitchForSwingChecker.isLatencyMonitoring()) {
// 初始化容器
initializeContainer();
// 启动异步性能记录线程池
executorService = FineExecutors.newCachedThreadPool(
new NamedThreadFactory(DesignerLatencyMetric.class));
// 启动定时埋点
this.scheduler = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("LatencyMetricWorker"));
this.scheduler.scheduleWithFixedDelay(this::collectAndSubmit, 60, 60, TimeUnit.SECONDS);
// 注册设计器工作目录切换事件监听
EventDispatcher.listen(WorkspaceEvent.AfterSwitch, new Listener<Workspace>() {
@Override
public void on(Event event, Workspace param) {
collectAndSubmit();
}
});
}
}
/**
* 关闭
*/
public void stop() {
if (SwitchForSwingChecker.isLatencyMonitoring()) {
if (this.executorService != null) {
this.executorService.shutdown();
}
if (this.scheduler != null) {
this.scheduler.shutdown();
}
collectAndSubmit();
}
}
private void initializeContainer() {
for (LatencyLevel level : LatencyLevel.values()) {
LATENCY_CONTAINER.put(level, new AtomicInteger());
}
}
private void resetContainer() {
LATENCY_CONTAINER.values().forEach(count -> count.set(0));
}
/**
* 记录性能信息
*/
public void record(long cost) {
executorService.submit(() -> {
try {
LatencyLevel level = LatencyLevel.measure(cost);
LATENCY_CONTAINER.computeIfAbsent(level, k -> new AtomicInteger()).incrementAndGet();
} catch (Throwable ignore) {
// 记录失败不影响业务
}
});
}
/**
* 汇总并提交性能监控埋点
*/
public void collectAndSubmit() {
Map<String, Object> para = new HashMap<>();
para.put("token", SiteCenterToken.generateToken());
para.put("content", collect());
try {
HttpToolbox.post(LATENCY_INFO_URL, para);
} catch (Throwable ignore) {
// doNothing
}
resetContainer();
}
private JSONObject collect() {
JSONObject info = new JSONObject();
info.put(TIME, System.currentTimeMillis());
info.put(APPID, MarketConfig.getInstance().getCloudOperationMaintenanceId());
info.put(USERID, MarketConfig.getInstance().getBbsUid());
info.put(DESIGNER_ID, DesignerEnvManager.getEnvManager().getUUID());
info.put(DESIGNER_VERSION, ProductConstants.DESIGNER_VERSION);
info.put(DESIGN_METHOD, WorkContext.getCurrent().isLocal() ? LOCAL : REMOTE);
info.put(OPERANDS_NUM, LATENCY_CONTAINER.values().stream().mapToInt(AtomicInteger::get).sum());
for (Map.Entry<LatencyLevel, AtomicInteger> entry : LATENCY_CONTAINER.entrySet()) {
info.put(entry.getKey().getMark(), entry.getValue().get());
}
return info;
}
}

67
designer-base/src/main/java/com/fr/design/carton/latency/LatencyLevel.java

@ -0,0 +1,67 @@
package com.fr.design.carton.latency;
/**
* 卡顿等级
*
* @author Levy.Xie
* @since 11.0
* Created on 2024/07/01
*/
public enum LatencyLevel {
// 非常流畅
FLASH(0, 50, "waitNum1"),
// 流畅
SMOOTH(50, 100, "waitNum2"),
// 轻微卡顿
SLIGHT(100, 200, "waitNum3"),
// 中等卡顿
MILD(200, 500, "waitNum4"),
// 明显卡顿
NOTICEABLE(500, 1000, "waitNum5"),
// 严重卡顿
SERVE(1000, 2000, "waitNum6"),
// 非常严重卡顿
EXTREME(2000, 3000, "waitNum7"),
// 极度卡顿
CRITICAL(3000, Long.MAX_VALUE, "waitNum8"),
// 未知场景
UNDEFINE(-1, -1, "unknown");
final long start;
final long end;
final String mark;
LatencyLevel(long start, long end, String mark) {
this.start = start;
this.end = end;
this.mark = mark;
}
public long getStart() {
return start;
}
public long getEnd() {
return end;
}
public String getMark() {
return mark;
}
/**
* 评估当前卡顿等级
*
* @param cost UI-EventQueue响应耗时
* @return 卡顿等级
*/
public static LatencyLevel measure(long cost) {
for (LatencyLevel level : LatencyLevel.values()) {
if (cost >= level.getStart() && cost < level.getEnd()) {
return level;
}
}
return UNDEFINE;
}
}

2
designer-base/src/main/java/com/fr/design/mainframe/DesignerFrame.java

@ -11,6 +11,7 @@ import com.fr.design.ExtraDesignClassManager;
import com.fr.design.actions.core.ActionFactory; import com.fr.design.actions.core.ActionFactory;
import com.fr.design.base.mode.DesignModeContext; import com.fr.design.base.mode.DesignModeContext;
import com.fr.design.base.mode.DesignerMode; import com.fr.design.base.mode.DesignerMode;
import com.fr.design.carton.latency.DesignerLatencyMetric;
import com.fr.design.constants.UIConstants; import com.fr.design.constants.UIConstants;
import com.fr.design.data.DesignTableDataManager; import com.fr.design.data.DesignTableDataManager;
import com.fr.design.data.datapane.TableDataTreePane; import com.fr.design.data.datapane.TableDataTreePane;
@ -1161,6 +1162,7 @@ public class DesignerFrame extends JFrame implements JTemplateActionListener, Ta
}); });
DesignerEnvManager.getEnvManager().saveXMLFile(); DesignerEnvManager.getEnvManager().saveXMLFile();
DesignerLatencyMetric.getInstance().stop();
} }
/** /**

2
designer-realize/src/main/java/com/fr/start/MainDesigner.java

@ -11,6 +11,7 @@ import com.fr.design.actions.server.ServerConfigManagerAction;
import com.fr.design.actions.server.TemplateThemeManagerAction; import com.fr.design.actions.server.TemplateThemeManagerAction;
import com.fr.design.actions.server.WidgetManagerAction; import com.fr.design.actions.server.WidgetManagerAction;
import com.fr.design.base.mode.DesignModeContext; import com.fr.design.base.mode.DesignModeContext;
import com.fr.design.carton.latency.DesignerLatencyMetric;
import com.fr.design.carton.SwitchForSwingChecker; import com.fr.design.carton.SwitchForSwingChecker;
import com.fr.design.constants.DesignerLaunchStatus; import com.fr.design.constants.DesignerLaunchStatus;
import com.fr.design.constants.UIConstants; import com.fr.design.constants.UIConstants;
@ -162,6 +163,7 @@ public class MainDesigner extends BaseDesigner {
FineLoggerFactory.getLogger().info("Designer started.Time used {} ms", DesignerStartupContext.getRecorder().getTime(TimeUnit.MILLISECONDS)); FineLoggerFactory.getLogger().info("Designer started.Time used {} ms", DesignerStartupContext.getRecorder().getTime(TimeUnit.MILLISECONDS));
SwitchForSwingChecker.initThreadMonitoring(); SwitchForSwingChecker.initThreadMonitoring();
DesignerLatencyMetric.getInstance().start();
} }
/** /**

Loading…
Cancel
Save