Destiny.Lin
3 months ago
17 changed files with 412 additions and 44 deletions
@ -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<WorkspaceSwitchProcess> 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<String, Object> 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<Entry> entries = convert2SortSet(moduleUsedJO); |
||||
return convert2JO(entries); |
||||
} |
||||
|
||||
@NotNull |
||||
private static JSONObject convert2JO(TreeSet<Entry> 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<Entry> convert2SortSet(JSONObject moduleUsedJO) { |
||||
|
||||
TreeSet<Entry> 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<Entry> 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; |
||||
} |
||||
} |
||||
} |
@ -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()); |
||||
} |
||||
} |
@ -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<String, Object> 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; |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue