Browse Source
* commit '3cf8cdbfbf6e6dc29fea75fc96030b3c3f11e02c': REPORT-136866 feat:NewUI性能监控工具 REPORT-139356 插件数据源注册添加回来 REPORT-139356 插件数据源注册添加回来 REPORT-139789 fix: FBP SystemOptionProvider 放到 extra-decision REPORT-128071 插件引擎适配微服务-适配支持特定服务 REPORT-137630 feat: 东区图表Tab切换性能优化fbp/feature
Richard.Fang-方超
2 weeks ago
29 changed files with 1016 additions and 241 deletions
@ -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); |
||||
} |
||||
|
||||
|
||||
} |
@ -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); |
||||
} |
||||
} |
||||
|
||||
} |
@ -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<AbstractUIDispatchHandler> 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<DispatchInfo> 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)); |
||||
} |
||||
|
||||
} |
@ -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; |
@ -1,4 +1,4 @@
|
||||
package com.fr.design.remote.ui.debug; |
||||
package com.fr.design.debug.remote; |
||||
|
||||
import com.fr.stable.StringUtils; |
||||
|
@ -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; |
@ -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; |
||||
|
||||
/** |
||||
* 大小多颜色渲染 |
@ -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; |
||||
|
||||
/** |
||||
* 时间多颜色渲染 |
@ -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; |
||||
} |
||||
} |
@ -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<LatencyInfo> { |
||||
/** |
||||
* 超出卡顿阈值 |
||||
*/ |
||||
OFF_THRESHOLD_EVENT |
||||
} |
@ -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); |
||||
} |
||||
} |
||||
} |
@ -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); |
||||
} |
||||
} |
@ -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<Long, LatencyInfo> 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<LatencyInfo> getAllLatencyInfo() { |
||||
return infoContainer.asMap().values(); |
||||
} |
||||
|
||||
/** |
||||
* 清空全量卡顿信息 |
||||
*/ |
||||
public void clearData() { |
||||
infoContainer.invalidateAll(); |
||||
} |
||||
} |
@ -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); |
||||
} |
||||
} |
@ -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")); |
||||
} |
||||
|
||||
} |
@ -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<LatencyInfo> latencyInfoListener = new Listener<LatencyInfo>() { |
||||
@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"; |
||||
} |
||||
} |
||||
|
||||
} |
@ -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); |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue