From 94006395f5bc8571d0fa0a99eeda2216a8e79441 Mon Sep 17 00:00:00 2001 From: "Bruce.Deng" Date: Thu, 12 Sep 2024 22:08:43 +0800 Subject: [PATCH] =?UTF-8?q?REPORT-130080=20=E3=80=90FR-FBP=E3=80=91?= =?UTF-8?q?=E5=9F=8B=E7=82=B9=E6=96=B9=E6=A1=88=E6=A2=B3=E7=90=86=E4=B8=8E?= =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/fr/design/EnvChangeEntrance.java | 12 +- .../com/fr/design/EnvSwitcherSubmitTask.java | 214 ++++++++++++++++++ .../design/metric/AbstractDesignerMetric.java | 39 ++++ .../boot/env/DesignEnvChooseComponent.java | 19 +- .../java/com/fr/start/CarinaDesigner.java | 3 +- .../com/fr/start/DesignerStartupMetric.java | 94 ++++++++ 6 files changed, 369 insertions(+), 12 deletions(-) create mode 100644 designer-base/src/main/java/com/fr/design/EnvSwitcherSubmitTask.java create mode 100644 designer-base/src/main/java/com/fr/design/metric/AbstractDesignerMetric.java create mode 100644 designer-realize/src/main/java/com/fr/start/DesignerStartupMetric.java diff --git a/designer-base/src/main/java/com/fr/design/EnvChangeEntrance.java b/designer-base/src/main/java/com/fr/design/EnvChangeEntrance.java index e2a08bbbc3..33be0b7fc4 100644 --- a/designer-base/src/main/java/com/fr/design/EnvChangeEntrance.java +++ b/designer-base/src/main/java/com/fr/design/EnvChangeEntrance.java @@ -2,8 +2,6 @@ package com.fr.design; import com.fr.common.report.ReportState; import com.fr.design.backup.EnvBackupHelper; -import com.fr.design.mainframe.manager.clip.TemplateTreeClipboard; -import com.fr.design.plugin.remind.PluginErrorDesignReminder; import com.fr.design.data.DesignTableDataManager; import com.fr.design.dialog.BasicDialog; import com.fr.design.dialog.DialogActionAdapter; @@ -52,7 +50,6 @@ import com.fr.workspace.engine.base.FineObjectPool; import com.fr.workspace.engine.channel.http.FunctionalHttpRequest; import com.fr.workspace.engine.exception.WorkspaceConnectionException; import com.fr.workspace.engine.rpc.WorkspaceProxyPool; -import static javax.swing.JOptionPane.QUESTION_MESSAGE; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; @@ -68,9 +65,10 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Optional; -import java.util.Properties; import java.util.Set; +import static javax.swing.JOptionPane.QUESTION_MESSAGE; + public class EnvChangeEntrance { private static final String BRANCH_TAG = "#"; private static final String BRANCH_BEGIN = "-"; @@ -158,11 +156,11 @@ public class EnvChangeEntrance { } /** - * 由云端运维,触发切换埋点 - * 内部空实现 - * 见 实现 + * 异步提交埋点 + * */ private void triggerSwitchMetric() { + EnvSwitcherSubmitTask.asyncSubmit(); } /** diff --git a/designer-base/src/main/java/com/fr/design/EnvSwitcherSubmitTask.java b/designer-base/src/main/java/com/fr/design/EnvSwitcherSubmitTask.java new file mode 100644 index 0000000000..33edfc66cb --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/EnvSwitcherSubmitTask.java @@ -0,0 +1,214 @@ +package com.fr.design; + +import com.fr.concurrent.FineExecutors; +import com.fr.concurrent.NamedThreadFactory; +import com.fr.design.mainframe.SiteCenterToken; +import com.fr.design.metric.AbstractDesignerMetric; +import com.fr.general.CloudCenter; +import com.fr.general.http.HttpToolbox; +import com.fr.json.JSONArray; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.stable.StringUtils; +import com.fr.workspace.Workspace; +import com.fr.workspace.WorkspaceSwitchProcess; +import com.fr.workspace.WorkspaceSwitchStatics; +import com.fr.workspace.switcher.WorkspaceSwitchHistory; +import org.jetbrains.annotations.NotNull; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.TreeSet; +import java.util.concurrent.ExecutorService; + +/** + * 设计器环境切换埋点提交 + * + * @author Bruce.Deng + * @since 11.0 + * Created on 2024/9/5 + */ +public class EnvSwitcherSubmitTask extends AbstractDesignerMetric { + + private static final String TAG_MODULE = "module"; + private static final String TAG_SELF_USED = "selfUsed"; + private static final String TAG_SUB = "sub"; + private static final String WORK_DIRECTORY_TYPE_BEFORE_SWITCHING = "workDirectoryTypeBeforeSwitching"; + private static final String WORK_DIRECTORY_TYPE_AFTER_SWITCHING = "workDirectoryTypeAfterSwitching"; + private static final String SWITCH_COMPLETED = "switchCompleted"; + private static final String EXCHANGE_TIME = "exchangeTime"; + private static final String START_SLOW_MODULE_NAME = "startSlowModuleName"; + private static final String STOP_SLOW_MODULE_NAME = "stopSlowModuleName"; + private static final int MIN_LIMIT = 100; + private static final String SWITCH_TABLE_SUFFIX = "record_of_fbp_remoteSwitch/single"; + private final ExecutorService service; + + private EnvSwitcherSubmitTask() { + service = FineExecutors.newSingleThreadExecutor(new NamedThreadFactory("DesignEnvSwitchMetricSubmit")); + } + + /** + * 异步提交环境切换的埋点 + */ + public static void asyncSubmit() { + new EnvSwitcherSubmitTask().run(); + } + + /** + * 执行任务 + */ + private void run() { + + WorkspaceSwitchHistory.consume(workspaceSwitchProcesses -> { + Iterator workspaceSwitchProcessIterator = workspaceSwitchProcesses.descendingIterator(); + while (workspaceSwitchProcessIterator.hasNext()) { + WorkspaceSwitchProcess next = workspaceSwitchProcessIterator.next(); + // 过滤掉 source 为空的启动过程 + if (next != null && next.getSource() != null) { + submitProcess(next); + } + // 提交之后要移除掉 + workspaceSwitchProcessIterator.remove(); + } + }); + } + + private void submitProcess(WorkspaceSwitchProcess process) { + service.submit(new Runnable() { + @Override + public void run() { + collectAndSubmit(process); + } + }); + } + + private void collectAndSubmit(WorkspaceSwitchProcess process) { + Map para = new HashMap<>(); + para.put("token", SiteCenterToken.generateToken()); + para.put("content", collect(process)); + try { + HttpToolbox.post(getUrl(), para); + FineLoggerFactory.getLogger().debug("[EnvSwitcher] submit env switcher metric to cloud."); + } catch (Throwable t) { + FineLoggerFactory.getLogger().debug(t,"[EnvSwitcher] failed to submit env switcher metric to cloud."); + } + } + + private JSONObject collect(WorkspaceSwitchProcess process) { + JSONObject info = new JSONObject(); + addDefaultMetric(info); + addMeta(info, process); + addStatics(info, process.getStatics()); + return info; + } + + private static void addMeta(JSONObject info, WorkspaceSwitchProcess process) { + Workspace source = process.getSource(); + info.put(WORK_DIRECTORY_TYPE_BEFORE_SWITCHING, workspaceType(source)); + Workspace target = process.getTarget(); + info.put(WORK_DIRECTORY_TYPE_AFTER_SWITCHING, workspaceType(target)); + info.put(SWITCH_COMPLETED, switchCompleted(process)); + } + + @NotNull + private static String switchCompleted(WorkspaceSwitchProcess process) { + + return process.isSwitchSuccess() ? "yes" : "no"; + } + + @NotNull + private static String workspaceType(Workspace workspace) { + + return workspace.isLocal() ? "local" : "remote"; + } + + private void addStatics(JSONObject info, WorkspaceSwitchStatics statics) { + info.put(EXCHANGE_TIME, statics.getElapsed()); + JSONObject startSlowModuleName = convert2CloudModel(statics.getStartModuleUsed()); + info.put(START_SLOW_MODULE_NAME, startSlowModuleName); + JSONObject stopSlowModuleName = convert2CloudModel(statics.getStopModuleUsed()); + info.put(STOP_SLOW_MODULE_NAME, stopSlowModuleName); + } + + /* convert */ + + @NotNull + private JSONObject convert2CloudModel(JSONObject moduleUsedJO) { + + TreeSet entries = convert2SortSet(moduleUsedJO); + return convert2JO(entries); + } + + @NotNull + private static JSONObject convert2JO(TreeSet startEntries) { + + JSONObject moduleNames = new JSONObject(); + startEntries.stream() + .filter((e) -> e.getSelfUsed() > MIN_LIMIT) + .forEach((e) -> moduleNames.put(e.getModuleName(), e.getSelfUsed())); + return moduleNames; + } + + @NotNull + private TreeSet convert2SortSet(JSONObject moduleUsedJO) { + + TreeSet entries = new TreeSet<>(Comparator.comparingInt(Entry::getSelfUsed)); + sortBySelfUsed(moduleUsedJO, entries); + return entries; + } + + /** + * 循环处理 JSON, 并降序排序 + * + * @param moduleUsed 模块用时 {@link com.fr.module.engine.FineModule} + * @param entries 降序排序的树集合 + */ + private void sortBySelfUsed(JSONObject moduleUsed, TreeSet entries) { + + if (moduleUsed == null || moduleUsed.isEmpty()) { + return; + } + + String moduleName = moduleUsed.optString(TAG_MODULE); + int selfUsed = moduleUsed.optInt(TAG_SELF_USED); + entries.add(new Entry(moduleName, selfUsed)); + + JSONArray subModules = moduleUsed.optJSONArray(TAG_SUB); + if (subModules != null) { + int length = subModules.length(); + for (int i = 0; i < length; i++) { + JSONObject subModuleUsed = subModules.optJSONObject(i); + sortBySelfUsed(subModuleUsed, entries); + } + } + } + + private String getUrl() { + String monitorEntry = CloudCenter.getInstance().acquireUrlByKind("cloud.monitor.api.entrypoint"); + String url = (StringUtils.isNotEmpty(monitorEntry) ? monitorEntry : DEFAULT_MONITOR_URL) + + SWITCH_TABLE_SUFFIX; + return url; + } + + private static class Entry { + + private final String moduleName; + + private final Integer selfUsed; + + public Entry(String moduleName, Integer selfUsed) { + this.moduleName = moduleName; + this.selfUsed = selfUsed; + } + + public String getModuleName() { + return moduleName; + } + + public Integer getSelfUsed() { + return selfUsed; + } + } +} diff --git a/designer-base/src/main/java/com/fr/design/metric/AbstractDesignerMetric.java b/designer-base/src/main/java/com/fr/design/metric/AbstractDesignerMetric.java new file mode 100644 index 0000000000..97c9cfac48 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/metric/AbstractDesignerMetric.java @@ -0,0 +1,39 @@ +package com.fr.design.metric; + +import com.fanruan.carina.Carina; +import com.fanruan.config.bbs.FineBBSConfigProvider; +import com.fr.config.MarketConfig; +import com.fr.design.DesignerEnvManager; +import com.fr.general.GeneralUtils; +import com.fr.json.JSONObject; +import org.jetbrains.annotations.NotNull; + +/** + * 设计器埋点抽象 + * + * @author Bruce.Deng + * @since 11.0 + * Created on 2024/9/10 + */ +public abstract class AbstractDesignerMetric { + + public static final String TIME = "time"; + + public static final String DESIGNER_ID = "designerId"; + + public static final String DESIGNER_VERSION = "designerVersion"; + + public static final String USERID = "userId"; + + public static final String APPID = "appId"; + + public static final String DEFAULT_MONITOR_URL = "https://cloud.fanruan.com/api/monitor/"; + + protected void addDefaultMetric(@NotNull JSONObject info) { + info.put(TIME, System.currentTimeMillis()); + info.put(DESIGNER_ID, DesignerEnvManager.getEnvManager().getUUID()); + info.put(DESIGNER_VERSION, GeneralUtils.getVersion()); + info.put(USERID, Carina.config(FineBBSConfigProvider.class).getBbsUid()); + info.put(APPID, MarketConfig.getInstance().getCloudOperationMaintenanceId()); + } +} diff --git a/designer-realize/src/main/java/com/fanruan/boot/env/DesignEnvChooseComponent.java b/designer-realize/src/main/java/com/fanruan/boot/env/DesignEnvChooseComponent.java index d655e511f1..cfc29edad3 100644 --- a/designer-realize/src/main/java/com/fanruan/boot/env/DesignEnvChooseComponent.java +++ b/designer-realize/src/main/java/com/fanruan/boot/env/DesignEnvChooseComponent.java @@ -1,8 +1,6 @@ package com.fanruan.boot.env; -import com.fr.design.ConfigHelper; import com.fanruan.boot.key.StartupArgsShell; -import com.fr.design.mem.MemConfigRepositoryBuilder; import com.fanruan.carina.Carina; import com.fanruan.carina.annotions.FineComponent; import com.fanruan.carina.annotions.Start; @@ -15,15 +13,18 @@ import com.fanruan.config.realm.local.LocalConfigRepositoryBuilder; import com.fr.base.operator.org.OrganizationOperator; import com.fr.base.rpc.encrypt.EncryptOperator; import com.fr.decision.service.context.ServiceContext; +import com.fr.design.ConfigHelper; import com.fr.design.DesignerEnvManager; import com.fr.design.EnvChangeEntrance; import com.fr.design.PluginClassRefreshManager; +import com.fr.design.backup.DesignContext; import com.fr.design.constants.DesignerLaunchStatus; import com.fr.design.editlock.ConnectionLockChangeChecker; import com.fr.design.editlock.ServerTableDataLockChangeChecker; import com.fr.design.env.DesignerWorkspaceGenerator; import com.fr.design.env.DesignerWorkspaceInfo; import com.fr.design.file.HistoryTemplateListCache; +import com.fr.design.mem.MemConfigRepositoryBuilder; import com.fr.design.plugin.remind.PluginErrorDesignReminder; import com.fr.env.utils.WorkspaceUtils; import com.fr.event.Event; @@ -36,12 +37,12 @@ import com.fr.report.lock.DefaultLockInfoOperator; import com.fr.report.lock.LocalLockInfoOperator; import com.fr.report.lock.LockInfoOperator; import com.fr.report.lock.ServerLockInfoOperator; -import com.fr.design.backup.DesignContext; import com.fr.start.module.StartupArgs; import com.fr.value.NotNullLazyValue; import com.fr.workspace.WorkContext; import com.fr.workspace.Workspace; import com.fr.workspace.WorkspaceEvent; +import com.fr.workspace.WorkspaceSwitchProcess; import com.fr.workspace.base.WorkspaceKey; import com.fr.workspace.pool.WorkRPCRegister; import com.fr.workspace.pool.WorkRPCType; @@ -101,6 +102,8 @@ import com.fr.workspace.server.vcs.v2.scheduler.VcsAutoCleanOperator; import com.fr.workspace.server.vcs.v2.scheduler.VcsAutoCleanService; import org.jetbrains.annotations.NotNull; +import java.util.Optional; + /** * 环境选择模块 * @@ -118,10 +121,20 @@ public class DesignEnvChooseComponent extends ResourceAffiliate { BootstrapFactory.get().reboot("design_env_prepare"); // 环境切换后,等到模块重启更新一下当前的系统信息 WorkplaceConstants.updateBean(); + recordModuleStartStop(); } catch (Exception e) { throw new RuntimeException(e); } } + + private void recordModuleStartStop() { + + WorkspaceSwitchProcess process = WorkContext.getSwitcher().getProcess(); + Optional.ofNullable(process) + .ifPresent((e) -> e.recordModuleStartUsed(() -> BootstrapFactory.get().profileStart("design_env_prepare"))); + Optional.ofNullable(process) + .ifPresent((e) -> e.recordModuleStopUsed(() -> BootstrapFactory.get().profileStop("design_env_prepare"))); + } }; private Listener beforeSwitch4Max = new Listener(Integer.MAX_VALUE) { 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 5e44ed8425..775eaf06fa 100644 --- a/designer-realize/src/main/java/com/fr/start/CarinaDesigner.java +++ b/designer-realize/src/main/java/com/fr/start/CarinaDesigner.java @@ -10,11 +10,9 @@ 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; import com.fr.start.module.StartupArgs; @@ -65,6 +63,7 @@ public class CarinaDesigner extends MainDesigner{ FineLoggerFactory.getLogger().info("Designer started.Time used {} ms", DesignerStartupContext.getRecorder().getTime(TimeUnit.MILLISECONDS)); DesignerStartupContext.getRecorder().stop(); + DesignerStartupMetric.getInstance().asyncSubmit(); SwitchForSwingChecker.initThreadMonitoring(); DesignerLatencyMetric.getInstance().start(); } diff --git a/designer-realize/src/main/java/com/fr/start/DesignerStartupMetric.java b/designer-realize/src/main/java/com/fr/start/DesignerStartupMetric.java new file mode 100644 index 0000000000..2dc2712799 --- /dev/null +++ b/designer-realize/src/main/java/com/fr/start/DesignerStartupMetric.java @@ -0,0 +1,94 @@ +package com.fr.start; + +import com.fr.concurrent.FineExecutors; +import com.fr.concurrent.NamedThreadFactory; +import com.fr.design.mainframe.SiteCenterToken; +import com.fr.design.metric.AbstractDesignerMetric; +import com.fr.general.CloudCenter; +import com.fr.general.http.HttpToolbox; +import com.fr.json.JSONObject; +import com.fr.json.revise.EmbedJson; +import com.fr.log.FineLoggerFactory; +import com.fr.stable.StringUtils; +import com.fr.start.common.DesignerStartupContext; +import com.fr.startup.metric.DesignerMetrics; +import com.fr.startup.metric.DesignerStartupModel; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * fbp设计器启动埋点提交 + * + * @author Bruce.Deng + * @since 11.0 + * Created on 2024/9/5 + */ +public class DesignerStartupMetric extends AbstractDesignerMetric { + + private static final String LANDING_TIME = "landingTime"; + private static final String STARTING_TIME = "startingTime"; + private static final String MODE = "mode"; + private static final String INFO = "info"; + private static volatile DesignerStartupMetric instance = new DesignerStartupMetric(); + private static final String DESIGNER_START_TABLE_SUFFIX = "record_of_fbp_designerStartTime/single"; + + private DesignerStartupMetric() { + } + + /** + * 获取单例 + */ + public static DesignerStartupMetric getInstance() { + return instance; + } + + /** + * 延迟5分钟提交埋点数据 + * + */ + public void asyncSubmit() { + ScheduledExecutorService scheduledExecutorService = FineExecutors.newSingleThreadScheduledExecutor(new NamedThreadFactory("DesignerStartupMetricSubmit")); + scheduledExecutorService.schedule(new Runnable() { + @Override + public void run() { + collectAndSubmit(); + } + }, 5, TimeUnit.MINUTES); + scheduledExecutorService.shutdown(); + } + + private void collectAndSubmit() { + Map para = new HashMap<>(); + para.put("token", SiteCenterToken.generateToken()); + para.put("content", collect()); + try { + HttpToolbox.post(getUrl(), para); + FineLoggerFactory.getLogger().debug("[DesignerStartup] submit designer startup metric to cloud."); + } catch (Throwable t) { + FineLoggerFactory.getLogger().debug(t,"[DesignerStartup] failed to submit designer startup metric to cloud."); + } + } + + private String getUrl() { + String monitorEntry = CloudCenter.getInstance().acquireUrlByKind("cloud.monitor.api.entrypoint"); + String url = (StringUtils.isNotEmpty(monitorEntry) ? monitorEntry : DEFAULT_MONITOR_URL) + + DESIGNER_START_TABLE_SUFFIX; + return url; + } + + private JSONObject collect() { + JSONObject info = new JSONObject(); + DesignerMetrics designerMetrics = DesignerStartupContext.getInstance().getDesignerMetrics(); + DesignerStartupModel model = designerMetrics.getModel(); + info.put(LANDING_TIME, model.getLandingTime()); + info.put(STARTING_TIME, model.getStartingTime()); + info.put(MODE, model.getMode()); + info.put(INFO, EmbedJson.encode(model.getInfo())); + addDefaultMetric(info); + return info; + } + +}