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;
+ }
+
+}