feat: 启动监控扩展追溯字段并修复补报事件语义

- StartupMonitor 上报新增时间拆解与客户端环境字段:response_latency、startability_processstart_dur、appattach_to_appforeground_dur、bundle_name、device_type、system_version、build_mode、target_name、git_commit、is_first_install
- 新增 is_backfill 补报标识:以 watcher 注册时间为锚点判定系统补投递的上次启动遗留事件,entry_page 置 unknown,避免错误归因
- CrashDiagnosticsDemo 同步字段展示与补报判定逻辑,devecocli build 构建通过
- 真机验证(nova 14 Pro):冷启动/多入口拦截/热启动/5 秒退出/补报判定/全场景 3 组全部通过,耗时 126~180ms
- 接入方案文档更新:第 4 节追溯字段与时间拆解、第 6 节 5 秒退出边界、第 8 节验证记录

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
clz
2026-08-05 19:09:22 +08:00
co-authored by Claude
parent d0949704ed
commit 10bbfc4c82
3 changed files with 248 additions and 6 deletions
@@ -2,9 +2,12 @@ import hiAppEvent from '@ohos.hiviewdfx.hiAppEvent';
import { HXLog } from 'biz_common/src/main/ets/logger/HXLog';
import { HXUserService } from 'biz_hxservice';
import { ElkService, ElkUploadMessage, ElkUploadMessageBuilder } from 'service_monitor';
import { AppConfigManager } from 'biz_common';
import { AppConfigManager, StartManager } from 'biz_common';
import { GIT_COMMIT } from 'biz_common/src/main/ets/utils/debug/GitBuildConfig';
import { inspector } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
import { deviceInfo } from '@kit.BasicServicesKit';
import BuildProfile from 'BuildProfile';
const TAG: string = 'StartupMonitor';
@@ -21,6 +24,17 @@ const TAG: string = 'StartupMonitor';
*/
export class StartupMonitor {
private static readonly MAX_EVENT_KEYS: number = 20;
/**
* 补报事件判定容差(ms)。
* 本次启动链中 icon_input_time(点击图标)与 watcher 注册时间间隔在毫秒~秒级;
* 系统补投递的上次启动遗留事件,其 icon_input_time 至少早于注册时间 5 秒(事件送达延迟),
* 因此容差取 2s 可稳定区分两者。
*/
private static readonly BACKFILL_TOLERANCE_MS: number = 2000;
/**
* watcher 注册时间戳(epoch ms),用于识别系统补投递的上次启动遗留事件。
*/
private static watcherRegisterTime: number = 0;
/**
* 首帧绘制完成时快照的入口页面名称,在 onDraw 回调中赋值。
@@ -115,6 +129,8 @@ export class StartupMonitor {
if (StartupMonitor.watcherInitialized) {
return
}
// 记录注册时间戳,用于区分本次启动事件与系统补投递的上次启动遗留事件
StartupMonitor.watcherRegisterTime = Date.now()
hiAppEvent.addWatcher({
name: "startupWatcher",
appEventFilters: [{
@@ -192,22 +208,46 @@ export class StartupMonitor {
): void {
try {
const bundleVersion = (eventInfo.params['bundle_version'] as string) ?? '';
const bundleName = (eventInfo.params['bundle_name'] as string) ?? '';
const animationFinishTime = eventInfo.params['animation_finish_time'] as number;
// 时间拆解字段(详见接入方案文档第 4 节):
// response_latency 需要 API 22+;startability_processstart_dur / appattach_to_appforeground_dur 仅冷启动存在
const responseLatency = eventInfo.params['response_latency'] as number;
const startabilityProcessStartDur = eventInfo.params['startability_processstart_dur'] as number;
const appattachToAppForegroundDur = eventInfo.params['appattach_to_appforeground_dur'] as number;
const iconInputTime = eventInfo.params['icon_input_time'] as number;
// 补投递事件(上次启动提交绘制但进程在事件送达前退出)的 entry_page 无意义,
// capturedEntryPage 是当前进程的快照,不能错误归因到本次启动的页面。
const isBackfillEvent = StartupMonitor.isBackfillEvent(iconInputTime);
const metricPayload: Record<string, Object> = {
// 核心指标
cold_start_time: coldStartTime,
// 维度
start_type: startType,
entry_page: StartupMonitor.capturedEntryPage,
entry_page: isBackfillEvent ? 'unknown' : StartupMonitor.capturedEntryPage,
app_version: StartupMonitor.getAppVersion(),
bundle_version: bundleVersion,
// 补报标识:true 表示系统补投递的上次启动遗留事件(entry_page 无意义),看板可据此过滤或单独统计
is_backfill: isBackfillEvent,
// 原始系统字段,用于后端校验和去重
event_time: eventInfo.params['time'],
icon_input_time: eventInfo.params['icon_input_time'],
process_name: eventInfo.params['process_name'],
extend_time: eventInfo.params['extend_time'],
animation_finish_time: animationFinishTime,
// 追溯字段:系统时间拆解
bundle_name: bundleName,
response_latency: responseLatency,
startability_processstart_dur: startabilityProcessStartDur,
appattach_to_appforeground_dur: appattachToAppForegroundDur,
// 追溯字段:客户端环境
device_type: StartupMonitor.getDeviceType(),
system_version: StartupMonitor.getSystemVersion(),
build_mode: StartupMonitor.getBuildMode(),
target_name: StartupMonitor.getTargetName(),
git_commit: StartupMonitor.getGitCommit(),
is_first_install: StartupMonitor.getIsFirstInstall(),
}
const builder = new StartupElkBuilder('i')
@@ -218,15 +258,51 @@ export class StartupMonitor {
const message = new ElkUploadMessage(builder)
// 异步上传,避免阻塞启动流程
ElkService.getInstance().pushBusinessLog(message, true)
HXLog.i(TAG, `Cold start metric uploaded: ${coldStartTime}ms`)
HXLog.i(TAG, `Cold start metric uploaded: ${coldStartTime}ms${isBackfillEvent ? ' (backfill)' : ''}`)
} catch (e) {
HXLog.e(TAG, `uploadColdStartTime failed: ${JSON.stringify(e)}`)
}
}
/**
* 判断事件是否为系统补投递的上次启动遗留事件。
* 本次启动链中 icon_input_time 与 watcher 注册时间间隔在容差内;
* 补报事件的 icon_input_time 早于注册时间超过容差(至少 5 秒的送达延迟)。
*/
private static isBackfillEvent(iconInputTime: number): boolean {
return iconInputTime < StartupMonitor.watcherRegisterTime - StartupMonitor.BACKFILL_TOLERANCE_MS
}
private static getAppVersion(): string {
return AppConfigManager.getInstance().getConfigProvider()?.getInnerVersionFull() ?? 'unknown'
}
private static getDeviceType(): string {
return deviceInfo.deviceType
}
private static getSystemVersion(): string {
return deviceInfo.osFullName
}
private static getBuildMode(): string {
return BuildProfile.BUILD_MODE_NAME.toString()
}
private static getTargetName(): string {
return BuildProfile.TARGET_NAME.toString()
}
private static getGitCommit(): string {
return GIT_COMMIT
}
/**
* 是否安装后首次进入(StartManager 在 EntryAbility.onCreate 中已 init)
*/
private static getIsFirstInstall(): boolean {
return StartManager.getInstance().getAppInstallStatus()
}
}
/**