refactor: 启动监控 Startup 命名统一为 Launch

- demo 页面与诊断类重命名:StartupLogin/StartupNetwork/StartupPrivacy → LaunchLogin/LaunchNetwork/LaunchPrivacy,StartupDiagnostics → LaunchDiagnostics(含 main_pages.json 注册与全部引用)
- 生产监控类重命名:StartupMonitor → LaunchMonitor,StartupElkBuilder → LaunchElkBuilder(含 EntryAbility/Index/LauncherPage/NetworkAnomalyPage 引用)
- 命名与 APP_LAUNCH 事件及 launch 模块对齐
- 接入方案文档同步页面名与类名,并修正 6 处过时内容:4 维度(is_first_install 入维度出候选)、分桶 4 维度、4.1 阶段时间已上传、§6 章节顺序(6.1 在 6.2 前)、§2 双通道链路
- demo 改名后 devecocli build 编译通过;真机运行回归待设备恢复补跑

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
clz
2026-08-06 14:24:31 +08:00
co-authored by Claude
parent 6fe0d2ec7a
commit 9e29282be3
14 changed files with 253 additions and 229 deletions
@@ -44,7 +44,7 @@ import { ConnectionInitiator } from '../communication/ConnectionInitiator';
import { APMHelper } from '../monitor/APMHelper';
import { cbasService } from '../register/cbasregister/CbasServiceRegister';
import { eventSubscription } from '../monitor/AppEvent';
import { StartupMonitor } from '../monitor/StartupMonitor';
import { LaunchMonitor } from '../monitor/LaunchMonitor';
import { StockGroupConstants } from 'biz_selfcode';
import { StockGroupHandler } from '../groupsub/business/StockGroupHandler';
import { VarietyGroupHandler } from '../groupsub/business/VarietyGroupHandler';
@@ -142,7 +142,7 @@ export default class EntryAbility extends UIAbility {
// =====================================
// 初始化启动性能监控(必须在 early onCreate 注册,确保 reportDrawnCompleted 前 watcher 已就绪)
StartupMonitor.init();
LaunchMonitor.init();
DebugToolUtil.TARGET_NAME = BuildProfile.TARGET_NAME
DebugToolUtil.PRODUCT_NAME = BuildProfile.PRODUCT_NAME
@@ -8,12 +8,12 @@ import { inspector } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
import { HXEventMonitor, DataMonitorBuilder } from '@kernel/app_monitor_event';
const TAG: string = 'StartupMonitor';
const TAG: string = 'LaunchMonitor';
/**
* 启动性能监控:订阅 APP_LAUNCH 事件,采集冷启动耗时(extend_time),
*/
export class StartupMonitor {
export class LaunchMonitor {
private static readonly MAX_EVENT_KEYS: number = 20;
/**
* 补报判定容差(ms):正常启动链间隔毫秒~秒级,补报(上次启动遗留事件)至少早 5 秒
@@ -41,29 +41,29 @@ export class StartupMonitor {
abilityContext: common.UIAbilityContext,
pageName: string
): void {
if (StartupMonitor.drawReportSubmitted) {
if (LaunchMonitor.drawReportSubmitted) {
HXLog.i(TAG, `Skip duplicate draw registration for page: ${pageName}`)
return
}
StartupMonitor.disposeFirstFrame(StartupMonitor.activeObserverOwner)
LaunchMonitor.disposeFirstFrame(LaunchMonitor.activeObserverOwner)
try {
const observer = createObserver();
const onDraw = () => {
observer.off('draw', onDraw);
if (StartupMonitor.activeObserverOwner === pageName) {
StartupMonitor.activeObserverOwner = ''
StartupMonitor.activeDisposer = undefined
if (LaunchMonitor.activeObserverOwner === pageName) {
LaunchMonitor.activeObserverOwner = ''
LaunchMonitor.activeDisposer = undefined
}
// 必须在系统调用前设置进程级提交状态,防止后续页面或重复回调再次提交
if (StartupMonitor.drawReportSubmitted) {
if (LaunchMonitor.drawReportSubmitted) {
HXLog.i(TAG, `Skip duplicate draw callback for page: ${pageName}`)
return
}
StartupMonitor.drawReportSubmitted = true
StartupMonitor.capturedEntryPage = pageName
LaunchMonitor.drawReportSubmitted = true
LaunchMonitor.capturedEntryPage = pageName
try {
abilityContext.reportDrawnCompleted(() => {
@@ -71,14 +71,14 @@ export class StartupMonitor {
HXLog.i(TAG, `reportDrawnCompleted submitted for page: ${pageName}`)
} catch (e) {
// 同步异常表示系统调用尚未提交,允许后续入口页面重试。
StartupMonitor.drawReportSubmitted = false
StartupMonitor.capturedEntryPage = 'unknown'
LaunchMonitor.drawReportSubmitted = false
LaunchMonitor.capturedEntryPage = 'unknown'
HXLog.e(TAG, `reportDrawnCompleted failed synchronously: ${JSON.stringify(e)}`)
}
};
StartupMonitor.activeObserverOwner = pageName
StartupMonitor.activeDisposer = () => {
LaunchMonitor.activeObserverOwner = pageName
LaunchMonitor.activeDisposer = () => {
observer.off('draw', onDraw);
};
observer.on('draw', onDraw);
@@ -92,21 +92,21 @@ export class StartupMonitor {
* 取消入口页面的首帧绘制监听。
*/
static disposeFirstFrame(pageName: string): void {
if (pageName === '' || StartupMonitor.activeObserverOwner !== pageName) {
if (pageName === '' || LaunchMonitor.activeObserverOwner !== pageName) {
return
}
StartupMonitor.activeDisposer?.();
StartupMonitor.activeObserverOwner = ''
StartupMonitor.activeDisposer = undefined;
LaunchMonitor.activeDisposer?.();
LaunchMonitor.activeObserverOwner = ''
LaunchMonitor.activeDisposer = undefined;
}
/** 注册 APP_LAUNCH watcher(幂等)。应在 onCreate 尽早调用,确保事件生成前完成注册 */
static init(): void {
if (StartupMonitor.watcherInitialized) {
if (LaunchMonitor.watcherInitialized) {
return
}
StartupMonitor.watcherRegisterTime = Date.now()
StartupMonitor.ensureBucketMetric()
LaunchMonitor.watcherRegisterTime = Date.now()
LaunchMonitor.ensureBucketMetric()
hiAppEvent.addWatcher({
name: "startupWatcher",
appEventFilters: [{
@@ -115,11 +115,11 @@ export class StartupMonitor {
}],
onReceive: async (domain: string, appEventGroups: Array<hiAppEvent.AppEventGroup>) => {
HXLog.i(TAG, `APP_LAUNCH onReceive: domain=${domain}`)
StartupMonitor.processAppLaunchEvents(appEventGroups)
LaunchMonitor.processAppLaunchEvents(appEventGroups)
}
});
StartupMonitor.watcherInitialized = true
HXLog.i(TAG, 'StartupMonitor watcher registered')
LaunchMonitor.watcherInitialized = true
HXLog.i(TAG, 'LaunchMonitor watcher registered')
}
/** 处理 APP_LAUNCH:过滤冷启动且 extend_time 有效,去重后上传 */
@@ -146,21 +146,21 @@ export class StartupMonitor {
const eventTime = eventInfo.params['time'] as number;
const processName = (eventInfo.params['process_name'] as string) ?? '';
const eventKey = `${eventTime}|${iconInputTime}|${startType}|${processName}`;
if (StartupMonitor.processedEventKeys.indexOf(eventKey) !== -1) {
if (LaunchMonitor.processedEventKeys.indexOf(eventKey) !== -1) {
HXLog.i(TAG, `Skip duplicate APP_LAUNCH: ${eventKey}`)
return
}
StartupMonitor.processedEventKeys.push(eventKey)
if (StartupMonitor.processedEventKeys.length > StartupMonitor.MAX_EVENT_KEYS) {
StartupMonitor.processedEventKeys.shift()
LaunchMonitor.processedEventKeys.push(eventKey)
if (LaunchMonitor.processedEventKeys.length > LaunchMonitor.MAX_EVENT_KEYS) {
LaunchMonitor.processedEventKeys.shift()
}
HXLog.i(TAG,
`cold_start_time=${extendTime}ms, ` +
`icon_input_time=${iconInputTime}, ` +
`entry_page=${StartupMonitor.capturedEntryPage}`)
`entry_page=${LaunchMonitor.capturedEntryPage}`)
StartupMonitor.uploadColdStartTime(extendTime, startType, eventInfo)
LaunchMonitor.uploadColdStartTime(extendTime, startType, eventInfo)
})
})
} catch (e) {
@@ -177,7 +177,7 @@ export class StartupMonitor {
try {
const iconInputTime = eventInfo.params['icon_input_time'] as number;
// 补投递事件(上次启动遗留)的 entry_page 是当前进程快照,无意义,置 unknown
const isBackfillEvent = StartupMonitor.isBackfillEvent(iconInputTime);
const isBackfillEvent = LaunchMonitor.isBackfillEvent(iconInputTime);
// 启动生命周期各阶段时间(ms):response_latency 需 API 22+,两个 *dur 仅冷启动存在
const responseLatency = eventInfo.params['response_latency'] as number;
const animationFinishTime = eventInfo.params['animation_finish_time'] as number;
@@ -188,18 +188,18 @@ export class StartupMonitor {
const metricPayload: ColdStartMetricPayload = {
cold_start_time: coldStartTime,
start_type: startType,
entry_page: isBackfillEvent ? 'unknown' : StartupMonitor.capturedEntryPage,
app_version: StartupMonitor.getAppVersion(),
is_first_install: StartupMonitor.getIsFirstInstall(),
entry_page: isBackfillEvent ? 'unknown' : LaunchMonitor.capturedEntryPage,
app_version: LaunchMonitor.getAppVersion(),
is_first_install: LaunchMonitor.getIsFirstInstall(),
response_latency: responseLatency,
animation_finish_time: animationFinishTime,
startability_processstart_dur: startabilityProcessStartDur,
appattach_to_appforeground_dur: appattachToAppForegroundDur,
git_commit: StartupMonitor.getGitCommit(),
git_commit: LaunchMonitor.getGitCommit(),
is_backfill: isBackfillEvent,
}
const builder = new StartupElkBuilder('i')
const builder = new LaunchElkBuilder('i')
builder.userid(HXUserService.getInstance().getUserId())
builder.messageKey('cold_start_time')
builder.messageValue(JSON.stringify(metricPayload))
@@ -214,9 +214,9 @@ export class StartupMonitor {
const factory = HXEventMonitor.getEventFactory('launch')
const monitor = factory.getEventMonitor('cold_start_time')
monitor.setDimension_1(startType.toString())
monitor.setDimension_2(isBackfillEvent ? 'unknown' : StartupMonitor.capturedEntryPage)
monitor.setDimension_3(StartupMonitor.getAppVersion())
monitor.setDimension_4(StartupMonitor.getIsFirstInstall().toString())
monitor.setDimension_2(isBackfillEvent ? 'unknown' : LaunchMonitor.capturedEntryPage)
monitor.setDimension_3(LaunchMonitor.getAppVersion())
monitor.setDimension_4(LaunchMonitor.getIsFirstInstall().toString())
monitor.record(coldStartTime)
HXLog.i(TAG, `Cold start bucket metric recorded: ${coldStartTime}ms`)
} catch (e) {
@@ -230,7 +230,7 @@ export class StartupMonitor {
* TODO(api-confirm): DataMonitorBuilder / setBuckets / create 签名需以 .d.ts 核对
*/
private static ensureBucketMetric(): void {
if (StartupMonitor.bucketMetricInitialized) {
if (LaunchMonitor.bucketMetricInitialized) {
return
}
const builder = new DataMonitorBuilder()
@@ -241,13 +241,13 @@ export class StartupMonitor {
builder.setDimension4Name('is_first_install')
builder.setBuckets([500, 600, 700, 800, 1000, 1500, 2000, 3000])
HXEventMonitor.getEventFactory('launch').create(builder)
StartupMonitor.bucketMetricInitialized = true
LaunchMonitor.bucketMetricInitialized = true
HXLog.i(TAG, 'launch bucket metric created: cold_start_time')
}
/** 补投递判定:icon_input_time 早于 watcher 注册时间超过容差(2s)视为上次启动遗留事件 */
private static isBackfillEvent(iconInputTime: number): boolean {
return iconInputTime < StartupMonitor.watcherRegisterTime - StartupMonitor.BACKFILL_TOLERANCE_MS
return iconInputTime < LaunchMonitor.watcherRegisterTime - LaunchMonitor.BACKFILL_TOLERANCE_MS
}
private static getAppVersion(): string {
@@ -266,7 +266,7 @@ export class StartupMonitor {
}
/** ELK 消息构造器,业务来源为 launch 模块(命名约束见文档第 4 节) */
class StartupElkBuilder extends ElkUploadMessageBuilder {
class LaunchElkBuilder extends ElkUploadMessageBuilder {
constructor(biz_level: string = 'i') {
super(Date.now(), 'launch', biz_level);
}
@@ -281,7 +281,7 @@ interface ColdStartMetricPayload {
cold_start_time: number
// 维度:启动类型(0=冷启动,当前仅统计冷启动)
start_type: number
// 维度:入口页面(Index/StartupLogin 等;补报事件为 unknown)
// 维度:入口页面(Index/LauncherPage 等;补报事件为 unknown)
entry_page: string
// 维度:应用内部版本号
app_version: string
@@ -8,7 +8,7 @@ import { HXNetwork } from './HXNetwork'
import { ConnectionSeq } from '@kernel/lib_communication'
import { AuthProviderService } from 'biz_auth'
import { AdvertisingMonitorService } from 'biz_hxservice'
import { StartupMonitor } from '../monitor/StartupMonitor'
import { LaunchMonitor } from '../monitor/LaunchMonitor'
const SettingWant: Want = {
bundleName: 'com.huawei.hmos.settings',
@@ -49,13 +49,13 @@ struct NetworkAnomalyPage {
aboutToAppear(): void {
this.setSystemBar()
StartupMonitor.reportFirstFrameOnDraw(
LaunchMonitor.reportFirstFrameOnDraw(
() => this.getUIContext().getUIInspector().createComponentObserver('HOME_ROOT'),
this.context, 'NetworkAnomalyPage');
}
aboutToDisappear(): void {
StartupMonitor.disposeFirstFrame('NetworkAnomalyPage')
LaunchMonitor.disposeFirstFrame('NetworkAnomalyPage')
}
onExit() {
@@ -48,7 +48,7 @@ import { enableDarkMode, ThemeManagerEmitterConstants } from '@kernel/theme_mana
import { CmeTraceForeignManager } from 'biz_quote/src/main/ets/manager/CmeTraceForeignManager';
import { HQUGCManager } from 'biz_market';
import { AdsManager } from '@b2c-f/fuhm-ads';
import { StartupMonitor } from '../monitor/StartupMonitor';
import { LaunchMonitor } from '../monitor/LaunchMonitor';
const TAG: string = 'Struct Index';
@@ -311,7 +311,7 @@ struct IndexM {
aboutToDisappear() {
HXLog.d(TAG, 'Index aboutToDisappear triggered at ' + Date.now())
// 页面退出时取消首帧绘制监听
StartupMonitor.disposeFirstFrame('Index');
LaunchMonitor.disposeFirstFrame('Index');
emitter.off(EmitterConstants.SWIPER_CHANGETOINDEX_1_TEMP)
emitter.off(EmitterConstants.TAB_UI_MANAGER_CHANGE_INDEX)
emitter.off(QuoteSettingEvents.SETTING_UPDATE, this.onSettingUpdateCallback)
@@ -366,7 +366,7 @@ struct IndexM {
DrawLineInit.init()
// 注册首帧绘制监听,绘制完成时自动调用 reportDrawnCompleted
StartupMonitor.reportFirstFrameOnDraw(
LaunchMonitor.reportFirstFrameOnDraw(
() => this.getUIContext().getUIInspector().createComponentObserver('HOME_ROOT'),
this.context, 'Index');
}
@@ -9,7 +9,7 @@ import { window } from '@kit.ArkUI'
import { ConnectionSeq } from '@kernel/lib_communication/src/main/ets/data/CommunicationConstants'
import { deviceInfo } from '@kit.BasicServicesKit'
import { AdvertisingMonitorService } from 'biz_hxservice'
import { StartupMonitor } from '../monitor/StartupMonitor'
import { LaunchMonitor } from '../monitor/LaunchMonitor'
/**
* 启动页面
@@ -47,13 +47,13 @@ struct LauncherPage {
aboutToAppear(): void {
this.setSystemBar()
this.userPrivacyDialogVisible = !PreferenceService.getBooleanValueSync('Privacy', 'isUserAgree', false);
StartupMonitor.reportFirstFrameOnDraw(
LaunchMonitor.reportFirstFrameOnDraw(
() => this.getUIContext().getUIInspector().createComponentObserver('HOME_ROOT'),
this.context, 'LauncherPage');
}
aboutToDisappear(): void {
StartupMonitor.disposeFirstFrame('LauncherPage')
LaunchMonitor.disposeFirstFrame('LauncherPage')
}
onCancel() {