diff --git a/designer-base/src/main/java/com/fine/theme/utils/FineUIUtils.java b/designer-base/src/main/java/com/fine/theme/utils/FineUIUtils.java index 6ccf630ed2..8065cf6d33 100644 --- a/designer-base/src/main/java/com/fine/theme/utils/FineUIUtils.java +++ b/designer-base/src/main/java/com/fine/theme/utils/FineUIUtils.java @@ -6,6 +6,8 @@ import com.fr.design.border.FineBorderFactory; import com.fr.design.constants.LayoutConstants; import com.fr.design.gui.icontainer.UIScrollPane; import com.fr.design.gui.ilable.UILabel; +import com.fr.design.mainframe.DesignerContext; +import com.fr.design.mainframe.DesignerFrame; import com.fr.design.mainframe.theme.edit.ui.LabelUtils; import com.fr.design.i18n.DesignSizeI18nManager; import com.fr.stable.os.OperatingSystem; @@ -503,4 +505,16 @@ public class FineUIUtils { public static JTextArea createAutoWrapTipLabel(String text) { return LabelUtils.createAutoWrapLabel(text, FineUIUtils.getUIColor("Label.tipColor", "inactiveCaption")); } + + /** + * 基于设计器父面板,计算当前面板尺寸 + * @param width 宽度比例 + * @param height 高度比例 + * + * @return 面板尺寸 + */ + public static Dimension calPaneDimensionByContext(double width, double height) { + DesignerFrame parent = DesignerContext.getDesignerFrame(); + return new Dimension((int) (parent.getWidth() * width),(int) (parent.getHeight() * height)); + } } diff --git a/designer-base/src/main/java/com/fr/design/carton/CartonUtils.java b/designer-base/src/main/java/com/fr/design/carton/CartonUtils.java index df8361662b..e8c313af3a 100644 --- a/designer-base/src/main/java/com/fr/design/carton/CartonUtils.java +++ b/designer-base/src/main/java/com/fr/design/carton/CartonUtils.java @@ -1,5 +1,6 @@ package com.fr.design.carton; +import com.fr.json.JSONObject; import com.fr.log.FineLoggerFactory; import com.fr.stable.ArrayUtils; import com.fr.stable.StableUtils; @@ -15,9 +16,11 @@ import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import java.text.SimpleDateFormat; +import static com.fr.design.carton.CartonConstants.DATE_FORMAT; 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; +import static com.fr.design.carton.CartonConstants.UNREASONABLE_DISPATCH_DURATION_MS; /** * 设计器卡顿业务工具类 @@ -125,6 +128,24 @@ public class CartonUtils { } } + /** + * 记录event事件信息 + * @param info event事件信息 + */ + public static void recordDispatchInfo(DispatchInfo info) { + long cost = info.timeSoFar(); + 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_" + info.getEventSeq()); + jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Task_Start_Time"), DATE_FORMAT.format(info.getStartDispatchTimeMillis())); + if (cost > UNREASONABLE_DISPATCH_DURATION_MS) { + jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Task_Total_Time"), cost + "ms"); + } else { + jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Task_Total_Time"), info.totalTime() + "ms"); + } + outPutJournalLog(jsonObject.toString(), SwitchForSwingChecker.EASY_CHECK_FLAG); + } + /** * 用于判断是不是特定的堆栈 */ diff --git a/designer-base/src/main/java/com/fr/design/carton/DispatchInfo.java b/designer-base/src/main/java/com/fr/design/carton/DispatchInfo.java new file mode 100644 index 0000000000..f7764ba62f --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/carton/DispatchInfo.java @@ -0,0 +1,68 @@ +package com.fr.design.carton; + +/** + * SwingEvent事件包装类 + * + * @author Levy.Xie + * @since 11.0 + * Created on 2024/11/05 + */ +public class DispatchInfo { + + /** + * 当前线程 + */ + private final Thread eventDispatchThread = Thread.currentThread(); + /** + * 上次触发时间 + */ + private long lastDispatchTimeMillis = System.currentTimeMillis(); + /** + * 开始时间 + */ + private final long startDispatchTimeMillis = System.currentTimeMillis(); + /** + * 事件唯一编号 + */ + private final long eventSeq; + + public DispatchInfo() { + eventSeq = EventDispatchThreadHangMonitor.incrementAndGetSeq(); + } + + public Thread getEventDispatchThread() { + return eventDispatchThread; + } + + public long getLastDispatchTimeMillis() { + return lastDispatchTimeMillis; + } + + public long getStartDispatchTimeMillis() { + return startDispatchTimeMillis; + } + + public void setLastDispatchTimeMillis(long lastDispatchTimeMillis) { + this.lastDispatchTimeMillis = lastDispatchTimeMillis; + } + + public long getEventSeq() { + return eventSeq; + } + + /** + * event事件已运行时间 + */ + public long timeSoFar() { + return (System.currentTimeMillis() - lastDispatchTimeMillis); + } + + /** + * event事件总运行时间 + */ + public long totalTime() { + return (System.currentTimeMillis() - startDispatchTimeMillis); + } + + +} diff --git a/designer-base/src/main/java/com/fr/design/carton/EventDispatchThreadHangMonitor.java b/designer-base/src/main/java/com/fr/design/carton/EventDispatchThreadHangMonitor.java index 6fa29f41ce..8cbaad1677 100644 --- a/designer-base/src/main/java/com/fr/design/carton/EventDispatchThreadHangMonitor.java +++ b/designer-base/src/main/java/com/fr/design/carton/EventDispatchThreadHangMonitor.java @@ -1,16 +1,8 @@ package com.fr.design.carton; -import com.fanruan.product.ProductConstantsBase; 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.StableUtils; -import com.fr.stable.StringUtils; -import org.jetbrains.annotations.NotNull; import javax.swing.SwingUtilities; import java.awt.EventQueue; @@ -18,15 +10,10 @@ 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 @@ -39,14 +26,11 @@ import static com.fr.design.carton.CartonConstants.UNREASONABLE_DISPATCH_DURATIO public final class EventDispatchThreadHangMonitor extends EventQueue { public static final EventDispatchThreadHangMonitor INSTANCE = new EventDispatchThreadHangMonitor(); - /** - * 一个timer - */ - private Timer timer; + /** * 事件唯一编码,用于方便日志的查看 */ - private static long hangCount = 0; + private static long eventSequence = 0; /** * 类似于一个开关,当该值为默认的false启动时,定时任务在窗口开启前都不会对执行的事件进行检查 */ @@ -54,7 +38,7 @@ public final class EventDispatchThreadHangMonitor extends EventQueue { /** * 该链表为主要的实现定时任务的容器,在重写的dispatchEvent中由pre方法将DispatchInfo加入到链表,由post方法remove */ - private final LinkedList dispatches = new LinkedList(); + private final LinkedList dispatches = new LinkedList<>(); /** * 一个变量,用于控制easy监测模式的开关 */ @@ -72,7 +56,6 @@ public final class EventDispatchThreadHangMonitor extends EventQueue { /** * 一个变量,用于记录Timer的开关。 */ - public boolean isTimerWitch() { return timerWitch; } @@ -83,96 +66,20 @@ public final class EventDispatchThreadHangMonitor extends EventQueue { private boolean timerWitch = false; - private synchronized static long getHangCount() { - return hangCount++; - } - /** - * event事件的包装类 + * 获取Swing事件唯一编号 + * + * @return 事件编号 */ - 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); - } - } + public synchronized static long incrementAndGetSeq() { + return eventSequence++; + } - /** - * - * @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); - } + public LinkedList getDispatches() { + return dispatches; } private EventDispatchThreadHangMonitor() { - } /** @@ -183,17 +90,9 @@ public final class EventDispatchThreadHangMonitor extends EventQueue { */ 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); + scheduledExecutorService.scheduleAtFixedRate(() -> SwingUtilities.invokeLater(() -> { + // 切片即可 + }), 0, 500, TimeUnit.MILLISECONDS); } public void stopFilterModalWindow() { @@ -201,54 +100,11 @@ public final class EventDispatchThreadHangMonitor extends EventQueue { 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()) { @@ -267,16 +123,23 @@ public final class EventDispatchThreadHangMonitor extends EventQueue { } } + /** + * EDT监控器是否已就绪 + * + * @return EDT监控器初始化完成 + */ + public boolean ready() { + return !dispatches.isEmpty() && haveShownSomeComponent; + } + private boolean useCustomEventQueue() { // 开启性能监控或开启卡顿工具箱,则走自定义的EventQueue - return SwitchForSwingChecker.isLatencyMonitoring() || - isEasyWitch() || isTimerWitch(); + return SwitchForSwingChecker.isLatencyMonitoring() || isEasyWitch() || isTimerWitch(); } private boolean needSampling() { // UI性能采样逻辑:开启采样并且符合采样频次 - return SwitchForSwingChecker.isLatencyMonitoring() - && (hangCount % LATENCY_SAMPLING_FREQUENCY == 0); + return SwitchForSwingChecker.isLatencyMonitoring() && (eventSequence % LATENCY_SAMPLING_FREQUENCY == 0); } /** @@ -298,13 +161,13 @@ public final class EventDispatchThreadHangMonitor extends EventQueue { DesignerLatencyMetric.getInstance().record(justFinishedDispatch.timeSoFar()); } if (isEasyWitch()) { - justFinishedDispatch.dispose(); + CartonUtils.recordDispatchInfo(justFinishedDispatch); } //嵌套最深的事件执行完毕后刷新链表中其他事件的lastDispatchTimeMillis Thread currentEventDispatchThread = Thread.currentThread(); for (DispatchInfo dispatchInfo : dispatches) { - if (dispatchInfo.eventDispatchThread == currentEventDispatchThread) { - dispatchInfo.lastDispatchTimeMillis = System.currentTimeMillis(); + if (dispatchInfo.getEventDispatchThread() == currentEventDispatchThread) { + dispatchInfo.setLastDispatchTimeMillis(System.currentTimeMillis()); } } } diff --git a/designer-base/src/main/java/com/fr/design/carton/SwitchForSwingChecker.java b/designer-base/src/main/java/com/fr/design/carton/SwitchForSwingChecker.java index 66527dde5b..dc6d388a33 100644 --- a/designer-base/src/main/java/com/fr/design/carton/SwitchForSwingChecker.java +++ b/designer-base/src/main/java/com/fr/design/carton/SwitchForSwingChecker.java @@ -1,7 +1,7 @@ package com.fr.design.carton; -import com.fanruan.product.ProductConstantsBase; +import com.fr.design.carton.latency.UIDispatchManager; import com.fr.design.i18n.Toolkit; import com.fr.general.GeneralUtils; import com.fr.json.JSON; @@ -82,7 +82,7 @@ public class SwitchForSwingChecker implements XMLReadable, XMLWriter { public static void startTimerChecker() { if (!checkerTimerSwitch) { - EventDispatchThreadHangMonitor.INSTANCE.initTimer(); + UIDispatchManager.getInstance().startScheduler(); CartonThreadExecutorPool.getTimerThreadExecutorPool().initTimer(); EventDispatchThreadHangMonitor.INSTANCE.setTimerWitch(true); checkerTimerSwitch = true; @@ -94,7 +94,7 @@ public class SwitchForSwingChecker implements XMLReadable, XMLWriter { public static void stopTimerChecker() { if (checkerTimerSwitch) { - EventDispatchThreadHangMonitor.INSTANCE.stopTimer(); + UIDispatchManager.getInstance().stopSchedulerIfNecessary(); CartonThreadExecutorPool.getTimerThreadExecutorPool().stopTimer(); EventDispatchThreadHangMonitor.INSTANCE.setTimerWitch(false); checkerTimerSwitch = false; @@ -286,7 +286,7 @@ public class SwitchForSwingChecker implements XMLReadable, XMLWriter { CartonThreadExecutorPool.getTimerThreadExecutorPool().setEasyWitch(true); } if (checkerTimerSwitch) { - EventDispatchThreadHangMonitor.INSTANCE.initTimer(); + UIDispatchManager.getInstance().startScheduler(); CartonThreadExecutorPool.getTimerThreadExecutorPool().initTimer(); EventDispatchThreadHangMonitor.INSTANCE.setTimerWitch(true); } diff --git a/designer-base/src/main/java/com/fr/design/carton/latency/AbstractUIDispatchHandler.java b/designer-base/src/main/java/com/fr/design/carton/latency/AbstractUIDispatchHandler.java new file mode 100644 index 0000000000..9307411b33 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/carton/latency/AbstractUIDispatchHandler.java @@ -0,0 +1,33 @@ +package com.fr.design.carton.latency; + +import com.fr.design.carton.DispatchInfo; + +/** + * 设计器UI事件切面处理器 + * + * @author Levy.Xie + * @since 11.0 + * Created on 2024/11/07 + */ +public abstract class AbstractUIDispatchHandler { + + /** + * 是否需要处理 + */ + protected abstract boolean accept(DispatchInfo info); + + /** + * 实际处理 + */ + protected abstract void doHandle(DispatchInfo info); + + /** + * 处理UI切面 + */ + public void handle(DispatchInfo info) { + if (accept(info)) { + doHandle(info); + } + } + +} diff --git a/designer-base/src/main/java/com/fr/design/carton/latency/UIDispatchManager.java b/designer-base/src/main/java/com/fr/design/carton/latency/UIDispatchManager.java new file mode 100644 index 0000000000..974b155db0 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/carton/latency/UIDispatchManager.java @@ -0,0 +1,104 @@ +package com.fr.design.carton.latency; + +import com.fr.design.carton.DispatchInfo; +import com.fr.design.carton.EventDispatchThreadHangMonitor; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import static com.fr.design.carton.CartonConstants.CHECK_INTERVAL_MS; + +/** + * 设计器UI事件切面处理器 + * + * @author Levy.Xie + * @since 11.0 + * Created on 2024/11/05 + */ +public class UIDispatchManager { + + private ScheduledExecutorService scheduler; + private final List handlerList = new ArrayList<>(); + + private final static class InstanceHolder { + static final UIDispatchManager INSTANCE = new UIDispatchManager(); + } + + /** + * 单例 + */ + public static UIDispatchManager getInstance() { + return UIDispatchManager.InstanceHolder.INSTANCE; + } + + /** + * 注册处理器 + * @param handler 处理器 + */ + public void registerHandler(AbstractUIDispatchHandler handler) { + handlerList.add(handler); + } + + /** + * 注销处理器 + * @param handler 处理器 + */ + public void unregisterHandler(AbstractUIDispatchHandler handler) { + handlerList.remove(handler); + } + + /** + * 开启定时UI事件监听任务 + */ + public void startScheduler() { + if (scheduler != null && !scheduler.isShutdown()) { + return; + } + scheduler = Executors.newSingleThreadScheduledExecutor(r -> { + Thread thread = new Thread(r, "DesignerLatencyChecker"); + thread.setDaemon(true); + return thread; + }); + scheduler.scheduleAtFixedRate(new DispatchChecker(), 0, CHECK_INTERVAL_MS, TimeUnit.MILLISECONDS); + } + + /** + * 尝试停止定时UI事件监听任务,但如果存在处理器,则不停止 + */ + public void stopSchedulerIfNecessary() { + if (!handlerList.isEmpty()) { + return; + } + if (scheduler != null && !scheduler.isShutdown()) { + scheduler.shutdownNow(); + } + } + + /** + * UI事件切面检查器 + */ + public class DispatchChecker implements Runnable { + @Override + public void run() { + LinkedList dispatches = EventDispatchThreadHangMonitor.INSTANCE.getDispatches(); + synchronized (dispatches) { + if (EventDispatchThreadHangMonitor.INSTANCE.ready()) { + handle(dispatches.getLast()); + } + } + } + } + + /** + * 处理UI事件 + * @param dispatchInfo ui事件信息 + */ + public void handle(DispatchInfo dispatchInfo) { + handlerList.forEach(handler -> handler.handle(dispatchInfo)); + } + +} diff --git a/designer-base/src/main/java/com/fr/design/remote/ui/debug/HeaderRenderer.java b/designer-base/src/main/java/com/fr/design/debug/remote/HeaderRenderer.java similarity index 97% rename from designer-base/src/main/java/com/fr/design/remote/ui/debug/HeaderRenderer.java rename to designer-base/src/main/java/com/fr/design/debug/remote/HeaderRenderer.java index 8427f61062..fa2c6a5009 100644 --- a/designer-base/src/main/java/com/fr/design/remote/ui/debug/HeaderRenderer.java +++ b/designer-base/src/main/java/com/fr/design/debug/remote/HeaderRenderer.java @@ -1,4 +1,4 @@ -package com.fr.design.remote.ui.debug; +package com.fr.design.debug.remote; import com.fine.theme.icon.LazyIcon; import com.fine.theme.light.ui.FineTableHeaderUI; diff --git a/designer-base/src/main/java/com/fr/design/remote/ui/debug/RemoteDesignNetWorkAction.java b/designer-base/src/main/java/com/fr/design/debug/remote/RemoteDesignNetWorkAction.java similarity index 75% rename from designer-base/src/main/java/com/fr/design/remote/ui/debug/RemoteDesignNetWorkAction.java rename to designer-base/src/main/java/com/fr/design/debug/remote/RemoteDesignNetWorkAction.java index 337bcf3319..15c2900e75 100644 --- a/designer-base/src/main/java/com/fr/design/remote/ui/debug/RemoteDesignNetWorkAction.java +++ b/designer-base/src/main/java/com/fr/design/debug/remote/RemoteDesignNetWorkAction.java @@ -1,22 +1,19 @@ -package com.fr.design.remote.ui.debug; +package com.fr.design.debug.remote; +import com.fine.theme.utils.FineUIUtils; import com.fr.design.actions.UpdateAction; import com.fr.design.dialog.FineJOptionPane; import com.fr.design.mainframe.DesignerContext; -import com.fr.design.mainframe.DesignerFrame; import com.fr.design.utils.gui.GUICoreUtils; import com.fr.workspace.WorkContext; import javax.swing.JDialog; import javax.swing.KeyStroke; -import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; -import static com.fine.theme.utils.FineUIScale.createScaleDimension; -import static com.fine.theme.utils.FineUIScale.unscale; import static com.fr.design.gui.syntax.ui.rtextarea.RTADefaultInputMap.DEFAULT_MODIFIER; /** @@ -42,7 +39,7 @@ public class RemoteDesignNetWorkAction extends UpdateAction { return; } JDialog jDialog = new JDialog(DesignerContext.getDesignerFrame(), TITLE); - jDialog.setSize(calculatePaneDimension()); + jDialog.setSize(FineUIUtils.calPaneDimensionByContext(0.8, 0.6)); RemoteDesignNetWorkTablePane netWorkPane = new RemoteDesignNetWorkTablePane(); jDialog.add(netWorkPane); jDialog.addWindowListener(new WindowAdapter() { @@ -56,10 +53,5 @@ public class RemoteDesignNetWorkAction extends UpdateAction { jDialog.setVisible(true); } - private static Dimension calculatePaneDimension() { - DesignerFrame parent = DesignerContext.getDesignerFrame(); - return createScaleDimension((int) (unscale(parent.getWidth()) * 0.8), - (int) (unscale(parent.getHeight()) * 0.6)); - } } diff --git a/designer-base/src/main/java/com/fr/design/remote/ui/debug/RemoteDesignNetWorkHelper.java b/designer-base/src/main/java/com/fr/design/debug/remote/RemoteDesignNetWorkHelper.java similarity index 98% rename from designer-base/src/main/java/com/fr/design/remote/ui/debug/RemoteDesignNetWorkHelper.java rename to designer-base/src/main/java/com/fr/design/debug/remote/RemoteDesignNetWorkHelper.java index ba08d0586f..b5639cf2a7 100644 --- a/designer-base/src/main/java/com/fr/design/remote/ui/debug/RemoteDesignNetWorkHelper.java +++ b/designer-base/src/main/java/com/fr/design/debug/remote/RemoteDesignNetWorkHelper.java @@ -1,4 +1,4 @@ -package com.fr.design.remote.ui.debug; +package com.fr.design.debug.remote; import com.fr.stable.StringUtils; diff --git a/designer-base/src/main/java/com/fr/design/remote/ui/debug/RemoteDesignNetWorkTablePane.java b/designer-base/src/main/java/com/fr/design/debug/remote/RemoteDesignNetWorkTablePane.java similarity index 95% rename from designer-base/src/main/java/com/fr/design/remote/ui/debug/RemoteDesignNetWorkTablePane.java rename to designer-base/src/main/java/com/fr/design/debug/remote/RemoteDesignNetWorkTablePane.java index ab0ea82f98..5569919560 100644 --- a/designer-base/src/main/java/com/fr/design/remote/ui/debug/RemoteDesignNetWorkTablePane.java +++ b/designer-base/src/main/java/com/fr/design/debug/remote/RemoteDesignNetWorkTablePane.java @@ -1,4 +1,4 @@ -package com.fr.design.remote.ui.debug; +package com.fr.design.debug.remote; import com.fanruan.workplace.http.debug.RequestInfo; import com.fine.theme.icon.LazyIcon; @@ -24,9 +24,9 @@ import java.awt.Component; import java.util.concurrent.atomic.AtomicLong; import static com.fanruan.workplace.http.debug.RemoteDesignDebugEvent.REMOTE_HTTP_REQUEST; -import static com.fr.design.remote.ui.debug.RemoteDesignNetWorkHelper.dateFormat; -import static com.fr.design.remote.ui.debug.RemoteDesignNetWorkHelper.simpleSize; -import static com.fr.design.remote.ui.debug.RemoteDesignNetWorkHelper.simpleTime; +import static com.fr.design.debug.remote.RemoteDesignNetWorkHelper.dateFormat; +import static com.fr.design.debug.remote.RemoteDesignNetWorkHelper.simpleSize; +import static com.fr.design.debug.remote.RemoteDesignNetWorkHelper.simpleTime; /** * 远程设计网络调试面板 diff --git a/designer-base/src/main/java/com/fr/design/remote/ui/debug/RemoteDesignNetWorkTableRowSorter.java b/designer-base/src/main/java/com/fr/design/debug/remote/RemoteDesignNetWorkTableRowSorter.java similarity index 96% rename from designer-base/src/main/java/com/fr/design/remote/ui/debug/RemoteDesignNetWorkTableRowSorter.java rename to designer-base/src/main/java/com/fr/design/debug/remote/RemoteDesignNetWorkTableRowSorter.java index 4f1270f1c7..2fe366ce02 100644 --- a/designer-base/src/main/java/com/fr/design/remote/ui/debug/RemoteDesignNetWorkTableRowSorter.java +++ b/designer-base/src/main/java/com/fr/design/debug/remote/RemoteDesignNetWorkTableRowSorter.java @@ -1,4 +1,4 @@ -package com.fr.design.remote.ui.debug; +package com.fr.design.debug.remote; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableRowSorter; diff --git a/designer-base/src/main/java/com/fr/design/remote/ui/debug/SizeColorCellRenderer.java b/designer-base/src/main/java/com/fr/design/debug/remote/SizeColorCellRenderer.java similarity index 88% rename from designer-base/src/main/java/com/fr/design/remote/ui/debug/SizeColorCellRenderer.java rename to designer-base/src/main/java/com/fr/design/debug/remote/SizeColorCellRenderer.java index 9099907a6c..00091b5353 100644 --- a/designer-base/src/main/java/com/fr/design/remote/ui/debug/SizeColorCellRenderer.java +++ b/designer-base/src/main/java/com/fr/design/debug/remote/SizeColorCellRenderer.java @@ -1,10 +1,10 @@ -package com.fr.design.remote.ui.debug; +package com.fr.design.debug.remote; import com.fine.theme.light.ui.FineTableHeaderUI; import java.awt.Color; -import static com.fr.design.remote.ui.debug.RemoteDesignNetWorkHelper.DEFAULT_COLOR; +import static com.fr.design.debug.remote.RemoteDesignNetWorkHelper.DEFAULT_COLOR; /** * 大小多颜色渲染 diff --git a/designer-base/src/main/java/com/fr/design/remote/ui/debug/TimeColorCellRenderer.java b/designer-base/src/main/java/com/fr/design/debug/remote/TimeColorCellRenderer.java similarity index 87% rename from designer-base/src/main/java/com/fr/design/remote/ui/debug/TimeColorCellRenderer.java rename to designer-base/src/main/java/com/fr/design/debug/remote/TimeColorCellRenderer.java index 7955fd0218..0a87f60c36 100644 --- a/designer-base/src/main/java/com/fr/design/remote/ui/debug/TimeColorCellRenderer.java +++ b/designer-base/src/main/java/com/fr/design/debug/remote/TimeColorCellRenderer.java @@ -1,10 +1,10 @@ -package com.fr.design.remote.ui.debug; +package com.fr.design.debug.remote; import com.fine.theme.light.ui.FineTableHeaderUI; import java.awt.Color; -import static com.fr.design.remote.ui.debug.RemoteDesignNetWorkHelper.DEFAULT_COLOR; +import static com.fr.design.debug.remote.RemoteDesignNetWorkHelper.DEFAULT_COLOR; /** * 时间多颜色渲染 diff --git a/designer-base/src/main/java/com/fr/design/debug/ui/LatencyInfo.java b/designer-base/src/main/java/com/fr/design/debug/ui/LatencyInfo.java new file mode 100644 index 0000000000..ea21c8a46b --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/debug/ui/LatencyInfo.java @@ -0,0 +1,48 @@ +package com.fr.design.debug.ui; + +/** + * UI卡顿信息Bean + * + * @author Levy.Xie + * @since 11.0 + * Created on 2024/11/07 + */ +public class LatencyInfo { + + // swing事件编号 + private long seq; + // 耗时 ms + private long cost; + // 堆栈信息 + private StackTraceElement[] detailStack; + + public LatencyInfo(long seq, long cost, StackTraceElement[] detailStack) { + this.seq = seq; + this.cost = cost; + this.detailStack = detailStack; + } + + public long getCost() { + return cost; + } + + public void setCost(long cost) { + this.cost = cost; + } + + public StackTraceElement[] getDetailStack() { + return detailStack; + } + + public void setDetailStack(StackTraceElement[] detailStack) { + this.detailStack = detailStack; + } + + public long getSeq() { + return seq; + } + + public void setSeq(long seq) { + this.seq = seq; + } +} diff --git a/designer-base/src/main/java/com/fr/design/debug/ui/LatencyMonitorEvent.java b/designer-base/src/main/java/com/fr/design/debug/ui/LatencyMonitorEvent.java new file mode 100644 index 0000000000..77881ef57a --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/debug/ui/LatencyMonitorEvent.java @@ -0,0 +1,17 @@ +package com.fr.design.debug.ui; + +import com.fr.event.Event; + +/** + * UI性能监控事件 + * + * @author Levy.Xie + * @since 11.0 + * Created on 2024/11/08 + */ +public enum LatencyMonitorEvent implements Event { + /** + * 超出卡顿阈值 + */ + OFF_THRESHOLD_EVENT +} diff --git a/designer-base/src/main/java/com/fr/design/debug/ui/UIInspectorHolder.java b/designer-base/src/main/java/com/fr/design/debug/ui/UIInspectorHolder.java new file mode 100644 index 0000000000..fddcaa9b0d --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/debug/ui/UIInspectorHolder.java @@ -0,0 +1,56 @@ +package com.fr.design.debug.ui; + +import com.fanruan.gui.UiInspector; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * UIInspectorHolder 单例管理 + * + * @author Levy.Xie + * @since 11.0 + * Created on 2024/11/08 + */ +public class UIInspectorHolder { + + private UiInspector uiInspector; + private final AtomicBoolean installed = new AtomicBoolean(false); + + private final static class InstanceHolder { + static final UIInspectorHolder INSTANCE = new UIInspectorHolder(); + } + + /** + * 单例 + */ + public static UIInspectorHolder getInstance() { + return UIInspectorHolder.InstanceHolder.INSTANCE; + } + + /** + * 是否已启用UIInspector + * @return 是否启用 + */ + public boolean isInstalled() { + return installed.get(); + } + + /** + * 启用UIInspector + */ + public void install() { + if (installed.compareAndSet(false, true)) { + uiInspector = new UiInspector(); + } + } + + /** + * 注销UIInspector + */ + public void uninstall() { + if (uiInspector != null) { + uiInspector.dispose(); + installed.set(false); + } + } +} diff --git a/designer-base/src/main/java/com/fr/design/debug/ui/UILatencyInfoHandler.java b/designer-base/src/main/java/com/fr/design/debug/ui/UILatencyInfoHandler.java new file mode 100644 index 0000000000..3bfc7c129e --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/debug/ui/UILatencyInfoHandler.java @@ -0,0 +1,46 @@ +package com.fr.design.debug.ui; + +import com.fr.design.carton.DispatchInfo; +import com.fr.design.carton.latency.AbstractUIDispatchHandler; + +/** + * UI卡顿实时监控Handler + * + * @author Levy.Xie + * @since 11.0 + * Created on 2024/11/07 + */ +public class UILatencyInfoHandler extends AbstractUIDispatchHandler { + + private long threshold = 200; + + private final static class InstanceHolder { + static final UILatencyInfoHandler INSTANCE = new UILatencyInfoHandler(); + } + + /** + * 单例 + */ + public static UILatencyInfoHandler getInstance() { + return UILatencyInfoHandler.InstanceHolder.INSTANCE; + } + + public long getThreshold() { + return threshold; + } + + public void setThreshold(long threshold) { + this.threshold = threshold; + } + + @Override + protected boolean accept(DispatchInfo info) { + return info.timeSoFar() > threshold; + } + + @Override + protected void doHandle(DispatchInfo info) { + LatencyInfo infoBean = new LatencyInfo(info.getEventSeq(), info.timeSoFar(), info.getEventDispatchThread().getStackTrace()); + UILatencyWorker.getInstance().submit(infoBean); + } +} diff --git a/designer-base/src/main/java/com/fr/design/debug/ui/UILatencyWorker.java b/designer-base/src/main/java/com/fr/design/debug/ui/UILatencyWorker.java new file mode 100644 index 0000000000..3fa729d3ab --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/debug/ui/UILatencyWorker.java @@ -0,0 +1,124 @@ +package com.fr.design.debug.ui; + +import com.fr.design.carton.latency.UIDispatchManager; +import com.fr.event.EventDispatcher; +import com.fr.third.guava.cache.Cache; +import com.fr.third.guava.cache.CacheBuilder; + +import java.util.Collection; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +/** + * UI性能监控Worker + * + * @author Levy.Xie + * @since 11.0 + * Created on 2024/11/07 + */ +public class UILatencyWorker { + + private ExecutorService executorService; + private final AtomicBoolean initialized = new AtomicBoolean(false); + + // 默认允许存储300卡顿信息 + private final Cache infoContainer = CacheBuilder.newBuilder() + .maximumSize(300).build(); + + private final static class InstanceHolder { + static final UILatencyWorker INSTANCE = new UILatencyWorker(); + } + + /** + * 单例 + */ + public static UILatencyWorker getInstance() { + return InstanceHolder.INSTANCE; + } + + /** + * 开始监控: 启动异步提交线程、启动定时堆栈检测任务 + */ + public void start() { + if (initialized.compareAndSet(false, true)) { + executorService = Executors.newSingleThreadExecutor(); + UIDispatchManager.getInstance().registerHandler(UILatencyInfoHandler.getInstance()); + UIDispatchManager.getInstance().startScheduler(); + } + } + + /** + * 停止监控:关闭异步提交线程及定时堆栈任务 + */ + public void stop() { + UIDispatchManager.getInstance().unregisterHandler(UILatencyInfoHandler.getInstance()); + UIDispatchManager.getInstance().stopSchedulerIfNecessary(); + if (this.executorService != null) { + this.executorService.shutdown(); + } + initialized.set(false); + } + + /** + * 是否监控中 + * + * @return 是否监控中 + */ + public boolean isMonitoring() { + return initialized.get(); + } + + /** + * 设置UI卡顿堆栈阈值 + * @param threshold 阈值 ms + */ + public void resetThreshold(long threshold) { + UILatencyInfoHandler.getInstance().setThreshold(threshold); + } + + /** + * 提交卡顿堆栈信息 + * @param latencyInfo 卡顿信息 + */ + public void submit(LatencyInfo latencyInfo) { + executorService.submit(() -> { + if (UIMonitorHelper.isIgnoreEvent(latencyInfo.getDetailStack())) { + return; + } + LatencyInfo existInfo = infoContainer.getIfPresent(latencyInfo.getSeq()); + // 确保记录的是最深的堆栈信息 + if (existInfo == null || latencyInfo.getDetailStack().length > existInfo.getDetailStack().length) { + infoContainer.put(latencyInfo.getSeq(), latencyInfo); + EventDispatcher.fire(LatencyMonitorEvent.OFF_THRESHOLD_EVENT, latencyInfo); + } + }); + } + + /** + * 输出卡顿文本信息 + * + * @return 卡顿文本信息 + */ + public String getLatencyData() { + return getAllLatencyInfo().stream().map(info -> "seq:" + info.getSeq() + "\n" + + "cost:" + info.getCost() + "ms\n" + + "stack:" + UIMonitorHelper.convertStack(info.getDetailStack()) + "\n").collect(Collectors.joining("\n")); + } + + /** + * 获取当前记录的所有卡顿信息 + * @return 全量卡顿信息 + */ + public Collection getAllLatencyInfo() { + return infoContainer.asMap().values(); + } + + /** + * 清空全量卡顿信息 + */ + public void clearData() { + infoContainer.invalidateAll(); + } +} diff --git a/designer-base/src/main/java/com/fr/design/debug/ui/UIMonitorAction.java b/designer-base/src/main/java/com/fr/design/debug/ui/UIMonitorAction.java new file mode 100644 index 0000000000..3d0a87926a --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/debug/ui/UIMonitorAction.java @@ -0,0 +1,40 @@ +package com.fr.design.debug.ui; + +import com.fine.theme.utils.FineUIUtils; +import com.fr.design.actions.UpdateAction; +import com.fr.design.mainframe.DesignerContext; +import com.fr.design.utils.gui.GUICoreUtils; + +import javax.swing.JDialog; +import javax.swing.KeyStroke; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; + +import static com.fr.design.gui.syntax.ui.rtextarea.RTADefaultInputMap.DEFAULT_MODIFIER; + +/** + * UI实时监控 + * + * @author Levy.Xie + * @since 11.0 + * Created on 2024/11/07 + */ +public class UIMonitorAction extends UpdateAction { + + public static final String TITLE = "UI Monitor"; + + public UIMonitorAction() { + this.setName(TITLE); + this.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_U, DEFAULT_MODIFIER)); + } + + @Override + public void actionPerformed(ActionEvent e) { + JDialog jDialog = new JDialog(DesignerContext.getDesignerFrame(), TITLE); + jDialog.setSize(FineUIUtils.calPaneDimensionByContext(0.5, 0.7)); + UIMonitorPane monitorPane = new UIMonitorPane(); + jDialog.add(monitorPane); + GUICoreUtils.centerWindow(jDialog); + jDialog.setVisible(true); + } +} diff --git a/designer-base/src/main/java/com/fr/design/debug/ui/UIMonitorHelper.java b/designer-base/src/main/java/com/fr/design/debug/ui/UIMonitorHelper.java new file mode 100644 index 0000000000..6781239921 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/debug/ui/UIMonitorHelper.java @@ -0,0 +1,44 @@ +package com.fr.design.debug.ui; + +import java.util.Arrays; +import java.util.stream.Collectors; + +/** + * UI性能监控工具类 + * + * @author Levy.Xie + * @since 11.0 + * Created on 2024/11/08 + */ +public class UIMonitorHelper { + + /** + * 判断是否特定的堆栈 + */ + 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 isIgnoreEvent(StackTraceElement[] currentStack) { + + return currentStack != null && currentStack.length >= 1 && ( + stackTraceElementIs(currentStack[0], "java.lang.Object", "wait", true) + || stackTraceElementIs(currentStack[0], "sun.misc.Unsafe", "park", true) + ); + } + + /** + * 堆栈格式化 + * @param stackTrace 堆栈信息 + * + * @return 格式化后的堆栈信息 + */ + public static String convertStack(StackTraceElement[] stackTrace) { + return Arrays.stream(stackTrace) + .map(st -> "\t" + st.toString()).collect(Collectors.joining("\n")); + } + +} diff --git a/designer-base/src/main/java/com/fr/design/debug/ui/UIMonitorPane.java b/designer-base/src/main/java/com/fr/design/debug/ui/UIMonitorPane.java new file mode 100644 index 0000000000..e2fad4b2db --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/debug/ui/UIMonitorPane.java @@ -0,0 +1,274 @@ +package com.fr.design.debug.ui; + +import com.fine.swing.ui.layout.Row; +import com.fine.theme.icon.LazyIcon; +import com.fine.theme.utils.FineClientProperties; +import com.fine.theme.utils.FineUIUtils; +import com.formdev.flatlaf.util.ScaledEmptyBorder; +import com.fr.base.extension.FileExtension; +import com.fr.design.border.FineBorderFactory; +import com.fr.design.carton.latency.LatencyLevel; +import com.fr.design.dialog.BasicDialog; +import com.fr.design.dialog.BasicPane; +import com.fr.design.gui.ibutton.UIButton; +import com.fr.design.gui.icheckbox.UICheckBox; +import com.fr.design.gui.icombobox.UIComboBox; +import com.fr.design.gui.icontainer.UIScrollPane; +import com.fr.design.gui.icontainer.UITableScrollPane; +import com.fr.design.gui.ilable.UILabel; +import com.fr.design.gui.itable.FineUITable; +import com.fr.design.gui.itextarea.UITextArea; +import com.fr.design.gui.itoolbar.UIToolbar; +import com.fr.design.mainframe.DesignerContext; +import com.fr.event.Event; +import com.fr.event.EventDispatcher; +import com.fr.event.Listener; +import com.fr.file.FILE; +import com.fr.file.FILEChooserPane; +import com.fr.file.filter.ChooseFileFilter; +import com.fr.general.GeneralUtils; +import com.fr.log.FineLoggerFactory; +import org.jetbrains.annotations.NotNull; + +import javax.swing.JPanel; +import javax.swing.JTable; +import javax.swing.JToolBar; +import javax.swing.SwingUtilities; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; +import java.awt.BorderLayout; +import java.awt.Point; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.util.Arrays; +import java.util.Date; + +import static com.fine.swing.ui.layout.Layouts.cell; +import static com.fine.swing.ui.layout.Layouts.column; +import static com.fine.swing.ui.layout.Layouts.row; + +/** + * UI监控面板 + * + * @author Levy.Xie + * @since 11.0 + * Created on 2024/11/07 + */ +public class UIMonitorPane extends JPanel { + + private DefaultTableModel model; + private UICheckBox inspector; + private UICheckBox monitor; + + public UIMonitorPane() { + setLayout(new BorderLayout()); + setBorder(new ScaledEmptyBorder(10, 10, 10, 10)); + initComponent(); + } + + private void initComponent() { + UITableScrollPane tablePane = initLatencyTable(); + Row topSettingRow = initTopSettingRow(); + inspector = new UICheckBox("Open UI Inspector"); + monitor = new UICheckBox("Open Latency Monitor"); + + JPanel monitorPane = column(10, + cell(monitor), cell(topSettingRow), cell(tablePane).weight(1) + ).getComponent(); + + add(column(10, + cell(FineUIUtils.wrapComponentWithTitle(inspector, "UI Inspector")), + cell(FineUIUtils.wrapComponentWithTitle(monitorPane, "UI Latency Monitor")) + ).getComponent(), BorderLayout.CENTER); + + topSettingRow.setVisible(false); + tablePane.setVisible(false); + + initMonitorStatus(topSettingRow, tablePane); + } + + private void initMonitorStatus(Row topSettingRow, UITableScrollPane tablePane) { + inspector.setSelected(UIInspectorHolder.getInstance().isInstalled()); + monitor.setSelected(UILatencyWorker.getInstance().isMonitoring()); + // 注册事件监听 + inspector.addChangeListener(e -> { + if (inspector.isSelected()) { + UIInspectorHolder.getInstance().install(); + } else { + UIInspectorHolder.getInstance().uninstall(); + } + }); + monitor.addChangeListener(e -> { + topSettingRow.setVisible(monitor.isSelected()); + tablePane.setVisible(monitor.isSelected()); + if (monitor.isSelected()) { + startMonitor(); + } else { + stopMonitor(); + } + }); + // 初始化卡顿堆栈表 + if (monitor.isSelected()) { + SwingUtilities.invokeLater(() -> UILatencyWorker.getInstance().getAllLatencyInfo() + .forEach(info -> model.addRow(parseInfo2Row(info)))); + + } + } + + private Row initTopSettingRow() { + UIComboBox comboBox = initThresholdComboBox(); + UIButton export = new UIButton(new LazyIcon("export")); + export.setToolTipText("Export latency log"); + UIButton clear = new UIButton(new LazyIcon("remove")); + clear.setToolTipText("Clear latency log"); + + JToolBar toolbar = new UIToolbar(); + toolbar.add(comboBox); + toolbar.add(clear); + toolbar.add(export); + + export.addActionListener(e -> exportData()); + clear.addActionListener(e -> { + model.setRowCount(0); + UILatencyWorker.getInstance().clearData(); + }); + + return row(5, cell(new UILabel("Latency Threshold")), cell(toolbar)).getComponent(); + } + + private static @NotNull UIComboBox initThresholdComboBox() { + UIComboBox comboBox = new UIComboBox(Arrays.stream(LatencyLevel.values()) + .filter(it -> it != LatencyLevel.FLASH).map(LatencyLevel::getStart).toArray()); + comboBox.putClientProperty(FineClientProperties.COMBO_BOX_TYPE, FineClientProperties.ADAPTIVE_COMBO_BOX); + comboBox.setSelectedItem(UILatencyInfoHandler.getInstance().getThreshold()); + comboBox.addActionListener(e -> { + if (comboBox.getSelectedItem() != null) { + UILatencyWorker.getInstance().resetThreshold((Long) comboBox.getSelectedItem()); + } + }); + // 阈值初始化 + comboBox.setSelectedItem(comboBox.getSelectedItem()); + return comboBox; + } + + private UITableScrollPane initLatencyTable() { + model = new DefaultTableModel(); + model.addColumn("seq"); + model.addColumn("cost(ms)"); + model.addColumn("stack"); + FineUITable table = new FineUITable(model) { + public boolean isCellEditable(int row, int column) { + return false; + } + }; + UITableScrollPane tablePane = new UITableScrollPane(table); + table.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS); + table.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + int row = table.rowAtPoint(e.getPoint()); + if (row >= 0) { + String stack = (String) table.getValueAt(row, 2); + StackPane stackPane = new StackPane(stack); + BasicDialog dialog = stackPane.showLargeWindow(SwingUtilities.getWindowAncestor(e.getComponent()), null); + dialog.setAlwaysOnTop(true); + dialog.setVisible(true); + } + } + }); + TableColumnModel columnModel = table.getColumnModel(); + adjustColumnWidth(columnModel.getColumn(0)); + adjustColumnWidth(columnModel.getColumn(1)); + return tablePane; + } + + private void adjustColumnWidth(TableColumn column) { + column.setPreferredWidth(100); + column.setMinWidth(100); + column.setMaxWidth(100); + } + + /** + * 开启性能监控 + */ + public void startMonitor() { + EventDispatcher.listen(LatencyMonitorEvent.OFF_THRESHOLD_EVENT, latencyInfoListener); + UILatencyWorker.getInstance().start(); + } + + /** + * 关闭性能监控 + */ + public void stopMonitor() { + UILatencyWorker.getInstance().stop(); + EventDispatcher.stopListen(latencyInfoListener); + model.setRowCount(0); + } + + private void exportData() { + // 导出为txt文件 + FILEChooserPane fileChooserPane = FILEChooserPane.getMultiEnvInstance(true, false); + String fileName = "latency_log_" + GeneralUtils.objectToString(new Date()).replaceAll(":", "_"); + fileChooserPane.setFileNameTextField(fileName, ".txt"); + fileChooserPane.addChooseFILEFilter(new ChooseFileFilter(FileExtension.TXT)); + int saveValue = fileChooserPane.showSaveDialog(DesignerContext.getDesignerFrame()); + if (saveValue == FILEChooserPane.JOPTIONPANE_OK_OPTION || saveValue == FILEChooserPane.OK_OPTION) { + FILE target = fileChooserPane.getSelectedFILE(); + try { + target.mkfile(); + try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(target.getPath(), true))) { + bufferedWriter.write(UILatencyWorker.getInstance().getLatencyData()); + } + } catch (Exception exp) { + FineLoggerFactory.getLogger().error("[Latency] Error export latency log.", exp); + } + } + } + + private final Listener latencyInfoListener = new Listener() { + @Override + public void on(Event event, LatencyInfo latencyInfo) { + SwingUtilities.invokeLater(() -> { + // 存量卡顿堆栈信息更新 + for (int i = 0; i < model.getRowCount(); i++) { + if (latencyInfo.getSeq() == (Long) model.getValueAt(i, 0)) { + model.removeRow(i); + break; + } + } + model.addRow(parseInfo2Row(latencyInfo)); + }); + } + }; + + private Object[] parseInfo2Row(LatencyInfo latencyInfo) { + return new Object[]{ + latencyInfo.getSeq(), + latencyInfo.getCost(), + UIMonitorHelper.convertStack(latencyInfo.getDetailStack())}; + } + + static class StackPane extends BasicPane { + + public StackPane(String stack) { + setLayout(new BorderLayout()); + UITextArea textArea = new UITextArea(); + textArea.setBorder(null); + textArea.setEditable(false); + textArea.setText(stack); + UIScrollPane scrollPane = new UIScrollPane(textArea); + scrollPane.setBorder(FineBorderFactory.createWrappedRoundBorder()); + add(scrollPane); + SwingUtilities.invokeLater(() -> scrollPane.getViewport().setViewPosition(new Point(0, 0))); + } + + @Override + protected String title4PopupWindow() { + return "Latency Stack"; + } + } + +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/toolbar/DebugModeMenuDef.java b/designer-base/src/main/java/com/fr/design/mainframe/toolbar/DebugModeMenuDef.java index 989e88b5c5..9d1e91cf67 100644 --- a/designer-base/src/main/java/com/fr/design/mainframe/toolbar/DebugModeMenuDef.java +++ b/designer-base/src/main/java/com/fr/design/mainframe/toolbar/DebugModeMenuDef.java @@ -1,13 +1,11 @@ package com.fr.design.mainframe.toolbar; -import com.fanruan.gui.UiInspector; import com.fine.theme.light.ui.laf.FineDarkLaf; import com.fine.theme.light.ui.laf.FineLightLaf; -import com.fr.design.actions.UpdateAction; import com.fr.design.gui.UILookAndFeel; -import com.fr.design.mainframe.DesignerContext; import com.fr.design.menu.MenuDef; -import com.fr.design.remote.ui.debug.RemoteDesignNetWorkAction; +import com.fr.design.debug.remote.RemoteDesignNetWorkAction; +import com.fr.design.debug.ui.UIMonitorAction; import java.awt.event.ActionEvent; @@ -24,15 +22,7 @@ public class DebugModeMenuDef extends MenuDef { super("Debug"); addLookAndFeelMenu(); addRemotePane(); - } - - private void addUIInspect() { - this.addShortCut(new UpdateAction() { - @Override - public void actionPerformed(ActionEvent e) { - new UiInspector().showInspector(DesignerContext.getDesignerFrame()); - } - }); + addUIMonitorPane(); } private void addLookAndFeelMenu() { @@ -48,4 +38,8 @@ public class DebugModeMenuDef extends MenuDef { private void addRemotePane() { this.addShortCut(new RemoteDesignNetWorkAction()); } + + private void addUIMonitorPane() { + this.addShortCut(new UIMonitorAction()); + } } diff --git a/designer-chart/src/main/java/com/fr/design/mainframe/chart/gui/ChartOtherPane.java b/designer-chart/src/main/java/com/fr/design/mainframe/chart/gui/ChartOtherPane.java index 36bc56d6b8..db6a6387dd 100644 --- a/designer-chart/src/main/java/com/fr/design/mainframe/chart/gui/ChartOtherPane.java +++ b/designer-chart/src/main/java/com/fr/design/mainframe/chart/gui/ChartOtherPane.java @@ -17,6 +17,7 @@ import com.fr.design.mainframe.chart.info.ChartInfoCollector; import com.fr.van.chart.designer.component.richText.VanChartRichEditorPane; import javax.swing.JPanel; +import javax.swing.SwingUtilities; import java.util.ArrayList; import java.util.List; import java.awt.BorderLayout; @@ -114,7 +115,10 @@ public class ChartOtherPane extends AbstractChartAttrPane { if (ChartOtherPane.this.isHaveCondition()) { VanChartRichEditorPane.refreshCommonChartFieldNames(chart); ColSelectedWithSummaryMethodEditor.refreshCommonChartFieldNames(chart); - conditionAttrPane.populateBean(chart); + SwingUtilities.invokeLater(() -> { + conditionAttrPane.populateBean(chart); + initListener(conditionAttrPane); + }); } } diff --git a/designer-chart/src/main/java/com/fr/design/mainframe/chart/gui/ChartStylePane.java b/designer-chart/src/main/java/com/fr/design/mainframe/chart/gui/ChartStylePane.java index 242cd98bae..2ff5a79f3e 100644 --- a/designer-chart/src/main/java/com/fr/design/mainframe/chart/gui/ChartStylePane.java +++ b/designer-chart/src/main/java/com/fr/design/mainframe/chart/gui/ChartStylePane.java @@ -30,6 +30,7 @@ public class ChartStylePane extends AbstractChartAttrPane { private Chart chart; private AttributeChangeListener listener; private BasicPane chartAxisPane; + private boolean chartStyleInitialized = false; protected Chart getChart() { return chart; @@ -64,11 +65,15 @@ public class ChartStylePane extends AbstractChartAttrPane { @Override public void populate(ChartCollection collection) { this.chart = collection.getSelectedChart(); - this.remove(leftContentPane); - initContentPane(); - this.removeAttributeChangeListener(); - stylePane.populateBean(chart); - this.addAttributeChangeListener(listener); + // 子组件布局初始化,只在第一次进入的时候绘制 + if (!chartStyleInitialized) { + this.remove(leftContentPane); + initContentPane(); + this.removeAttributeChangeListener(); + this.addAttributeChangeListener(listener); + this.initAllListeners(); + chartStyleInitialized = true; + } this.initAllListeners(); } diff --git a/designer-realize/src/main/java/com/fanruan/boot/adaptation/ReportAdaptationComponent.java b/designer-realize/src/main/java/com/fanruan/boot/adaptation/ReportAdaptationComponent.java index f3de414e70..2bb03fc45d 100644 --- a/designer-realize/src/main/java/com/fanruan/boot/adaptation/ReportAdaptationComponent.java +++ b/designer-realize/src/main/java/com/fanruan/boot/adaptation/ReportAdaptationComponent.java @@ -4,6 +4,7 @@ import com.fanruan.carina.Carina; import com.fanruan.carina.annotions.DependsOn; import com.fanruan.carina.annotions.FineComponent; import com.fanruan.carina.annotions.Start; +import com.fanruan.carina.context.ContextListener; import com.fanruan.plugins.resource.PluginResourceHelper; import com.fanruan.portal.FinePortal; import com.fanruan.portal.module.PortalModule; @@ -23,7 +24,6 @@ import com.fr.general.InterProviderImpl; import com.fr.locale.InterMutableKey; import com.fr.locale.LocaleMarker; import com.fr.locale.LocaleScope; -import com.fr.plugin.ExtraClassManager; import com.fr.plugin.context.PluginContext; import com.fr.plugin.injectable.PluginModule; import com.fr.plugin.observer.PluginEventType; @@ -167,25 +167,31 @@ public class ReportAdaptationComponent { } private void registerPluginModules() { - // 注册插件模块 - try { - Set systemOptionProviders = ExtraClassManager.getInstance().getArray(SystemOptionProvider.XML_TAG); - if (!CollectionUtils.isEmpty(systemOptionProviders)) { - // 资源引入采用新的方式,WebCoalition接口不再继承使用,这里只处理模块注册 - for (SystemOptionProvider optionProvider : systemOptionProviders) { - PortalModule portalModule = PortalModule.create(optionProvider.id(), optionProvider.displayName()) - .sortIndex(optionProvider.sortIndex()) - .dynamicControl(m -> new PluginPortalModuleDevice(optionProvider.parentId(), m)); - - FinePortal.registerModule(optionProvider.parentId(), portalModule); - - // 插件资源注册 - PluginResourceHelper.getInstance().registerAtom2Portal(optionProvider); + + Carina.getApplicationContext().addListener(new ContextListener() { + @Override + public void onStart() { + // 注册插件模块 + try { + Set systemOptionProviders = ExtraDecisionClassManager.getInstance().getArray(SystemOptionProvider.XML_TAG); + if (!CollectionUtils.isEmpty(systemOptionProviders)) { + // 资源引入采用新的方式,WebCoalition接口不再继承使用,这里只处理模块注册 + for (SystemOptionProvider optionProvider : systemOptionProviders) { + PortalModule portalModule = PortalModule.create(optionProvider.id(), optionProvider.displayName()) + .sortIndex(optionProvider.sortIndex()) + .dynamicControl(m -> new PluginPortalModuleDevice(optionProvider.parentId(), m)); + + FinePortal.registerModule(optionProvider.parentId(), portalModule); + + // 插件资源注册 + PluginResourceHelper.getInstance().registerAtom2Portal(optionProvider); + } + } + } catch (Exception e) { + throw new RuntimeException(e); } } - } catch (Exception e) { - throw new RuntimeException(e); - } + }); } private void checkI18n() { for (LocaleMarker marker : Carina.getApplicationContext().group(InterMutableKey.class).getAll()) { diff --git a/designer-realize/src/main/java/com/fanruan/boot/env/DesignPluginComponent.java b/designer-realize/src/main/java/com/fanruan/boot/env/DesignPluginComponent.java index 3e232bfaab..bab4a43c1d 100644 --- a/designer-realize/src/main/java/com/fanruan/boot/env/DesignPluginComponent.java +++ b/designer-realize/src/main/java/com/fanruan/boot/env/DesignPluginComponent.java @@ -15,6 +15,7 @@ import com.fr.invoke.ClassFactory; import com.fr.json.JSONObject; import com.fr.plugin.beforeload.embed.PluginEmbedInfo; import com.fr.plugin.config.PluginConfigContext; +import com.fr.plugin.db.PluginDBManager; import com.fr.plugin.injectable.PluginInjectionFilter; import com.fr.plugin.manage.PluginManager; import com.fr.plugin.manage.PluginSyncModuleType; @@ -46,7 +47,7 @@ import java.util.function.Supplier; * Created on 2024/5/17 */ @FineComponent(name = "design_plugin") -@DependsOn(dependencies = {"design_env_prepare"}) +@DependsOn(dependencies = {"design_version"}) public class DesignPluginComponent extends PluginComponent { @@ -91,6 +92,7 @@ public class DesignPluginComponent extends PluginComponent { } }); registerPluginClassFinder(); + PluginDBManager.getInstance().init(); } @Override @@ -143,5 +145,6 @@ public class DesignPluginComponent extends PluginComponent { @Stop public void stop() { super.stop(); + PluginDBManager.getInstance().destroy(); } } diff --git a/designer-realize/src/main/java/com/fanruan/boot/env/DesignVersionComponent.java b/designer-realize/src/main/java/com/fanruan/boot/env/DesignVersionComponent.java new file mode 100644 index 0000000000..5ca9cb6808 --- /dev/null +++ b/designer-realize/src/main/java/com/fanruan/boot/env/DesignVersionComponent.java @@ -0,0 +1,31 @@ +package com.fanruan.boot.env; + +import com.fanruan.boot.VersionComponent; +import com.fanruan.carina.annotions.DependsOn; +import com.fanruan.carina.annotions.FineComponent; +import com.fanruan.carina.annotions.Start; +import com.fanruan.version.ServiceVersion; +import com.fanruan.version.VersionCenter; +import com.fanruan.version.VersionConstants; + +/** + * 设计器版本模块 + * + * @author Leo.Qin + * @since 11.0 + * Created on 2024/11/11 + */ +@FineComponent(name = "design_version") +@DependsOn(dependencies = {"design_env_prepare"}) +public class DesignVersionComponent extends VersionComponent { + + @Start + @Override + public void start() { + super.start(); + ServiceVersion serviceVersion = VersionCenter.getInstance().getServiceVersion(); + serviceVersion.setName(VersionConstants.SERVICE_NAME_FR); + VersionCenter.getInstance().register(serviceVersion); + } + +} diff --git a/designer-realize/src/main/java/com/fr/start/CarinaDesigner.java b/designer-realize/src/main/java/com/fr/start/CarinaDesigner.java index 775eaf06fa..91178d4008 100644 --- a/designer-realize/src/main/java/com/fr/start/CarinaDesigner.java +++ b/designer-realize/src/main/java/com/fr/start/CarinaDesigner.java @@ -6,12 +6,10 @@ import com.fanruan.carina.Carina; import com.fanruan.carina.context.CarinaApplicationContext; import com.fanruan.carina.standard.PartitionManager; import com.fanruan.carina.standard.PartitionManagerImpl; -import com.fanruan.gui.UiInspector; import com.fr.base.StateHubContext; import com.fr.design.backup.DesignContext; import com.fr.design.carton.SwitchForSwingChecker; import com.fr.design.carton.latency.DesignerLatencyMetric; -import com.fr.design.mainframe.DesignerUIModeConfig; import com.fr.log.FineLoggerFactory; import com.fr.runtime.FineRuntime; import com.fr.start.common.DesignerStartupContext; @@ -39,7 +37,6 @@ public class CarinaDesigner extends MainDesigner{ * main */ public static void main(String[] args) { - installUIDevModeTools(); DesignerStartupContext.getRecorder().start(); PartitionManager manager = new PartitionManagerImpl(); StateHubContext.setReady(false); @@ -68,13 +65,4 @@ public class CarinaDesigner extends MainDesigner{ DesignerLatencyMetric.getInstance().start(); } - /** - * 进入UI开发者模式 - */ - private static void installUIDevModeTools() { - if (DesignerUIModeConfig.getInstance().isUIDevMode()) { - new UiInspector(); - } - } - }