- 新增 5.2 策略门控机制:注入链(后台 JSON → parseMonitorStrategy → EventStrategyBean → configMap → DataMonitorModule)、两级门控(插件级可绕过,指标级不可绕过——插件强制开启仍必须后台下发策略)、isOpen 语义(status==1 且抽样通过)、聚合上传触发(aggre_count/aggre_time)、重启生效机制(幂等标记无条件置 true,策略下发后需重启) - 基于 @kernel/app_monitor_event 与 app_monitor_lib 源码核实(~/Work/apm) Co-Authored-By: Claude <noreply@anthropic.com>
25 KiB
HarmonyOS 启动性能监控接入方案
1. 目标
建立冷启动耗时的线上采集、聚合和看板能力,用于发现版本或分支引入的启动性能劣化。统计口径以“用户点击图标”至“首页首帧完成”为准,单位为毫秒。
2. 总体链路
APP_LAUNCH 系统事件提供启动类型和系统耗时;首个有效入口页完成首次绘制后,通过 reportDrawnCompleted 标记终点。当前客户端双通道上报:ElkService 异步上传结构化追溯明细(11 字段),HXEventMonitor 分桶指标记录原始耗时由 SDK 按桶聚合(详见 6.2),后台再按模块配置阈值、聚合和看板。若后续具备 HarmonyOS 自定义 APM SDK,可替换上传层,但保留事件采集和去重逻辑。
3. 客户端接入
实现位于 future_entry_demo/src/main/ets/monitor/LaunchMonitor.ets。EntryAbility.onCreate 必须尽早调用 LaunchMonitor.init(),先注册 watcher,再允许入口页提交首帧完成事件。init() 本身应保持幂等,避免同一进程重复注册 watcher。
监听 APP_LAUNCH 后读取:
start_type:启动类型;仅0纳入冷启动统计。extend_time:从系统记录的icon_input_time到应用报告绘制完成的耗时。time、icon_input_time、process_name:标识启动事件并用于去重。animation_finish_time、bundle_version:用于分析和问题追溯。
3.1 首帧单次提交
LauncherPage、NetworkAnomalyPage 和 Index 都应尝试监听根节点首次 draw,以最先有效绘制的页面作为 entry_page。必须遵循以下约束:
- 在调用
reportDrawnCompleted之前设置进程级已提交状态;异步回调尚未执行时,其他页面也不得再次提交。 - observer 触发后立即注销,页面
aboutToDisappear仅释放与自身pageName匹配的 observer,避免旧页面销毁新页面的监听。 - 仅同步调用异常表示任务未提交,此时恢复未提交状态,允许后续入口页重试;页面销毁不得重置已经提交的状态。
3.2 事件过滤与去重
仅处理 start_type === 0 且 extend_time > 0 的记录。上传前按以下键去重:
time + icon_input_time + start_type + process_name
客户端保留最近 20 个事件键,兼容同一回调组内重复记录和系统重新投递,同时避免缓存无限增长。
4. 指标定义
| 项目 | 建议值 |
|---|---|
| 模块 | launch |
| 指标名 | cold_start_time |
| 指标值 | 冷启动耗时,单位 ms |
| 维度 | start_type、entry_page、app_version、app_install_status |
| 追溯字段 | response_latency、animation_finish_time、startability_processstart_dur、appattach_to_appforeground_dur、git_commit、is_backfill |
| 分桶 | [500, 600, 700, 800, 1000, 1500, 2000, 3000] |
上传负载为最小必要集:指标 1、维度 4(含 app_install_status)、启动生命周期各阶段时间 4(劣化分段定位)、git_commit(代码级追溯)、is_backfill(补报标识);去重为客户端行为(第 3.2 节),负载不含去重键。其余候选字段按需加回,无需改动采集逻辑。
模块名小写+连字符,指标名不含连字符;维度避免高基数(用户 ID、完整 URL 等)。
分桶指标由 HXEventMonitor 实现(8 桶、4 维度,与追溯一致),record(extendTime) 由 SDK 归桶聚合;桶边界按实测主体(600-800ms)细分、尾部留告警锚点,分桶变更需后台重建并档(历史分布不可比)。维度 app_install_status 取值 normal/new_install/over_written(安装状态:常规/全新安装/覆盖安装),与 StartManager 安装状态对齐。
4.1 启动时间拆解
cold_start_time 之外,追溯通道已随上传携带各分段耗时(见上表),可拆分冷启动定位劣化归属:
离手 ──response_latency──► 动效开始 ──(animation_finish_time − response_latency)──► 动效完成 ──(extend_time − animation_finish_time)──► 首帧绘制完成
系统响应段 系统动效段 应用可优化段
response_latency:离手到动效开始的耗时(需 API 22+),反映系统响应快慢。startability_processstart_dur:系统启动 Ability 到进程创建完成(仅冷启动),反映系统侧进程启动。appattach_to_appforeground_dur:进程初始化完成到应用切前台(仅冷启动),反映系统侧应用挂载。extend_time − animation_finish_time:动效完成到应用首帧的耗时,即应用侧可优化的独占部分;看板可据此判断劣化发生在系统侧还是应用侧。注意animation_finish_time在部分机型为0(系统未填充),此时该段等于extend_time全量,聚合时需兼容。
未上传的候选字段(按需启用):环境 build_mode/target_name 区分 debug/forTest/Official 环境,避免测试包污染线上看板;device_type/system_version 排除设备差异;版本 bundle_version/bundle_name;去重键 time/icon_input_time/process_name(去重为客户端行为,见第 3.2 节)。git_commit 已上传(代码级追溯)。
5. 后台与看板配置
5.1 模块策略下发(链路 B 激活前提)
链路 B 是策略驱动的:SDK 侧 HXEventMonitorPlugin.parseMonitorStrategy() 读取后台策略注入 EventStrategyBean,若 launch 模块无开启策略,create()/record() 一律空转并打印 Monitor.Event cold_start_time eventId has not created!,应用侧无法绕开。因此必须在 APM 后台为 module=launch 下发如下策略:
{
"module": "launch",
"config": {
"status": 1,
"sampling_rate": 1.0,
"interval": 5000,
"aggre_time": 60000,
"aggre_count": 10
}
}
字段说明(对齐 EventStrategyBean.fromJson,仅这些字段生效):
| 字段 | 取值 | 说明 |
|---|---|---|
module |
launch |
必须与代码 HXEventMonitor.getEventFactory('launch') 一致 |
status |
1 |
必须 1(STATUS_OPEN);否则 isOpen() 为 false,create/record 全空转 |
sampling_rate |
0~1 |
抽样率,isOpen() 需 status==1 且抽样通过才生效 |
interval |
ms | 采集间隔 |
aggre_time |
ms | 聚合时间阈值,达到即触发一次 push |
aggre_count |
条 | 聚合数量阈值,达到即触发一次 push |
客户端 record 后由 SDK 本地聚合,达到聚合周期或条数任一触发条件后自动上报至 APM 平台(发送通道为 HXMonitor 注入的 IElkService,即 APMElkService 所走的 ElkService 通道)。
判定生效:后台下发后重启应用,冷启动日志中应消失 Monitor.Event cold_start_time eventId has not created!、出现 Cold start bucket metric recorded: <ms> 与 SDK 聚合 push 日志,看板出现 launch 模块 cold_start_time 桶分布。
5.2 策略门控机制(源码级验证,2026-08-07,@kernel/app_monitor_event/app_monitor_lib 源码)
策略门控发生在 SDK 指标层,插件无法绕过:
- 注入链:后台策略 JSON →
HXEventMonitorPlugin.parseMonitorStrategy()→EventStrategyBean.fromJson()→EventMonitorStrategy.addStrategy(module, bean)(全局configMap)→DataMonitorModule构造时getStrategy('launch')取策略。 - 两级门控:插件级(
AbstractHXBasePlugin.isSupport(),默认"有策略且开启",HXLaunchMonitorPluginoverride 为true强制通过)仅控制插件生命周期;指标级(DataMonitorModule.create/getEventMonitor检查_strategy?.isOpen())独立生效——launch无策略 bean 时_strategy=null,create/getEventMonitor返回NullDataMonitorImp,record空转打印eventId has not created!。插件强制开启绕不过指标级门控,必须后台下发策略。 isOpen()语义:status == 1(STATUS_OPEN)且抽样通过(isOpenByRandom,对应sampling_rate)。- 聚合上传触发:
onRecord → isNeedUpload——累计条数 ≥aggregatorSizeThreshold(aggre_count)或距上次上传 ≥aggregatorTimeThreshold(aggre_time)即触发push。 - 重启生效机制:
LaunchMonitor.ensureColdStartMetric()的幂等标记在create被策略拒绝(返回空实现)时仍置true——本进程内策略下发后不会重试创建,必须重启应用(与上节"判定生效"一致);策略未下发时链路 A(Elk 追溯)不受影响、正常上传。
随后为该模块补充 ELK 索引模板,在 Grafana 建立看板面板(建议形态):
{
"panels": [
{
"title": "冷启动 P50/P95",
"type": "percentile",
"metric": "cold_start_time",
"group_by": ["app_version", "start_type"],
"window": "1d"
},
{
"title": "超 2 秒占比",
"type": "ratio",
"condition": "cold_start_time >= 2000",
"group_by": ["app_version"],
"window": "1d"
},
{
"title": "分桶分布",
"type": "bucket_distribution",
"metric": "cold_start_time",
"buckets": [500, 600, 700, 800, 1000, 1500, 2000, 3000],
"group_by": ["entry_page", "app_install_status"],
"window": "1d"
}
]
}
看板按版本、启动类型和时间聚合;app_install_status 维度(new_install/over_written)用于排除安装后首启的偏高数据。
6. 兼容与兜底
链路 A(ElkService 追溯)上传 cold_start_time、4 维度和阶段时间字段,不具备分桶聚合语义,由链路 B(HXEventMonitor 分桶指标,策略驱动见 5.1)承担看板聚合;后台需补充 ELK 索引模板、聚合规则和看板。链路 B 的 DataMonitorBuilder/record API 形态以 @kernel/app_monitor_event 的 .d.ts 为准(已随 HXLaunchMonitorPlugin 编译验证),不直接复用 Android 文档中的 Java API。
APP_LAUNCH 的事件解析必须与崩溃事件分开处理:崩溃事件依赖异常和日志字段,启动事件不包含这些字段。上传应异步执行,不阻塞首页渲染;以 time + icon_input_time + start_type + process_name 去重,兼容系统重新投递。
6.1 启动 5 秒内退出的边界行为
系统 extend_time 的定义:手指离手到 reportDrawnCompleted 的耗时,若 5 秒内未调用则该值为 0。真机实测(nova 14 Pro)启动后立即退出的行为链:
- 退出时首帧已提交(
reportDrawnCompleted已调用,真实 app 首帧通常在 1 秒内):事件正常生成、extend_time有效;若进程在事件送达(约 5 秒延迟)前退出,事件被系统持久化,下次启动 watcher 收到并补报,数据有效。 - 退出时首帧未提交(启动极慢或立即强杀):
reportDrawnCompleted从未调用,5 秒窗口过期,系统未生成有效事件(实测无补投递),不产生上报。
补报事件的 entry_page 语义:capturedEntryPage 是进程级变量,补报事件到达新进程时该值为新进程的快照(或默认值),不能归因到新启动页面。因此客户端以 watcher 注册时间戳为锚点判定补报事件:icon_input_time 早于注册时间超过 2 秒容差(正常启动链间隔为毫秒~秒级,补报至少早 5 秒)即视为补报,entry_page 置 'unknown' 上报,避免错误归因。同时上报字段新增 is_backfill 标识(true 为补报事件),看板可据此过滤补报数据或单独统计:补报的耗时数据有效可纳入 P50/P95 统计,但 entry_page 分布分析应排除。
真机实测确认(2026-08-05):部署新版本后先 force-stop 再冷启动,新进程收到旧进程的遗留事件,判定 补报事件=是(icon_input_time 早于注册时间约 4 秒);随后正常冷启动收到自身事件,判定 补报事件=否。双向判定均正确,正常启动无误判。
6.2 双通道上报
启动指标同时走两条通道,职责分离:
- 分桶指标(
HXEventMonitor,module=launch):record(extendTime)后由 SDK 按桶聚合,负责看板聚合统计(P50/P95、超 2 秒占比、分桶分布)。策略驱动:无后台下发策略时create()/record()空转(见 5.1),record失败/空转不影响启动流程,聚合逻辑在 SDK 侧。实现为HXLaunchMonitorPlugin(仿HXCrashMonitorPlugin,startPlugin()中建指标),在APMHelper的.plugin(...)列表注册。 - 追溯明细(
ElkService,biz=launch):11 字段最小集(指标/4 维度含app_install_status/启动生命周期各阶段时间/git_commit/is_backfill),负责明细追溯与后端校验;其他字段按需加回。分桶指标同步 4 个维度(setDimension4Name('app_install_status')),与追溯维度一致。
两通道独立失败互不影响;HXEventMonitor 的 module 参数为显式传入的 'launch',与文档模块定义一致,不经过 APMElkService 固定的 'apm' 来源。
7. 验收标准
- 单次冷启动中,隐私页、网络页和首页连续跳转时,
reportDrawnCompleted仍只提交一次。 - 未登录启动以登录/启动页为入口;已登录启动以
Index为入口,entry_page不被后续页面覆盖。 - 每次冷启动只产生一条可查询记录,且
extend_time > 0;热启动、无效耗时和重复事件不上传。 - 页面在系统异步回调前销毁时,不影响已经提交的上报;旧页面不会释放新页面 observer。
- 本地日志与后台数据量级一致;看板可查询 P50、P95、超 2 秒占比及分桶分布。
- 断网或上传失败不影响启动流程,网络恢复后按既有上传机制重试。
- 双通道上传验证:完整 workspace 编译通过(
HXEventMonitor的DataMonitorBuilder/getEventFactory/setBuckets/record/setDimension_4已随HXLaunchMonitorPlugin编译验证);真机冷启动 hilog 出现Cold start metric uploaded(链路 A)与Cold start bucket metric recorded(链路 B,需后台下发策略,见 5.1);ELK 索引可查biz='launch'的 11 字段记录,APM 平台launch模块出现分桶分布。 - 维度与分桶:分桶分布可按 4 个维度(
start_type/entry_page/app_version/app_install_status)分组查询;分桶边界变更需后台重建或并档(历史分布不可比)。
8. 已验证结果
8.1 首次回归(2026-08-05,CrashDiagnosticsDemo,nova 14 Pro)
未登录冷启动入口为 LaunchLogin,extend_time 约 158ms;持久化登录后冷启动入口为 Index,extend_time 约 155ms。网络异常、网络恢复、隐私确认及页面返回场景均保持一次 draw 回调和一次系统提交,后续页面注册被全局保护拦截;start_type=1 的热启动事件被正确忽略。
8.2 追溯字段扩展后的真机验证(2026-08-05,CrashDiagnosticsDemo,nova 14 Pro,OpenHarmony-6.1.1.120)
| # | 场景 | 操作 | 结果 |
|---|---|---|---|
| 1 | 已登录冷启动 | aa force-stop 后启动 |
入口 Index,draw 1 次、提交 1 次、成功 1 次,extend_time=144ms,start_type=0 |
| 2 | 已登录冷启动(复测) | aa force-stop 后启动 |
入口 Index,extend_time=137ms,单次提交 |
| 3 | 未登录冷启动 | bm clean -d 清数据后启动 |
入口 LaunchLogin,extend_time=152ms,单次提交 |
| 4 | 多入口连续跳转 | 登录页 → 网络异常页 → 返回 → 隐私确认页 | LaunchNetwork、LaunchPrivacy 重复首帧注册均被拦截(DrawReportState 保护),无重复提交 |
| 5 | 时间拆解字段捕获 | 观察三次冷启动的 APP_LAUNCH 详情 | response_latency=19~24ms、startability_processstart_dur=57~83ms、appattach_to_appforeground_dur=28~33ms,均为非零有效值 |
| 6 | 热启动(进程存活) | 对运行中进程再次 aa start |
无新 APP_LAUNCH 事件产生(系统对运行中单例不视为启动),不产生上报 |
验证结论:新增字段在真机全部可捕获且数值合理(response_latency 反映系统响应,两个 *_dur 拆解系统侧进程启动与挂载耗时)。已登录/未登录冷启动耗时(137~152ms)与首次回归(155/158ms)量级一致,字段扩展未引入回归。
发现的边界情况:animation_finish_time 在真机上为 0(系统未填充该字段),因此计算"应用可优化段(extend_time − animation_finish_time)"时需处理 0 值场景——此时该段等于 extend_time 全量,应在看板聚合时兼容。
8.3 完整重跑确认(2026-08-05 当天,同设备)
全部场景重新模拟一遍,结果与 8.2 一致:
| # | 场景 | 结果 |
|---|---|---|
| 1 | 清数据后未登录冷启动 | 入口 LaunchLogin,extend_time=161ms,单次提交 |
| 2 | 登录后同进程跳转 Index |
Index 重复首帧注册被拦截,无重复提交 |
| 3 | 已登录冷启动 | 入口 Index,extend_time=138ms,单次提交 |
| 4 | 热启动(进程存活) | 无新 APP_LAUNCH 事件,不产生上报 |
8.4 连续 3 次冷启动稳定性验证(2026-08-05 当天,同设备)
已登录态下连续 3 次 force-stop → 启动,每次均为独立进程、单次 draw 回调、单次 reportDrawnCompleted 提交与成功回调、独立有效 APP_LAUNCH 事件:
| 次数 | 进程 | 入口 | 提交次数 | extend_time |
start_type |
|---|---|---|---|---|---|
| 1 | 43415 | Index |
1 | 131ms | 0 |
| 2 | 43785 | Index |
1 | 132ms | 0 |
| 3 | 43914 | Index |
1 | 126ms | 0 |
连续启动耗时稳定在 126~138ms,无事件串扰、无重复上报。
8.5 全场景矩阵 3 组验证(2026-08-05 当天,同设备)
每组完整链路:清数据 → 未登录冷启动 → 网络异常页跳转(拦截)→ 返回 → 隐私确认页跳转(拦截)→ 返回 → 模拟登录 → Index 跳转(拦截)→ 已登录冷启动 → 热启动(无新事件)。3 组结果:
| 组 | 未登录冷启动(入口 LaunchLogin) |
网络页拦截 | 隐私页拦截 | Index 拦截 |
已登录冷启动(入口 Index) |
|---|---|---|---|---|---|
| 1 | extend_time=162ms(进程 44062) |
✓ | ✓ | ✓ | extend_time=133ms(进程 44644) |
| 2 | extend_time=157ms(进程 44927) |
✓ | ✓ | ✓ | extend_time=131ms(进程 45200) |
| 3 | extend_time=159ms(进程 45571) |
✓ | ✓ | ✓ | extend_time=135ms(进程 45862) |
每组 6 个场景全部按预期执行:单次冷启动只提交一次 reportDrawnCompleted,后续页面(网络异常页、隐私页、Index)的重复首帧注册全部被拦截,热启动不产生新事件。未登录冷启动耗时稳定在 157162ms,已登录稳定在 131135ms,组间一致,验证可复现。
8.6 启动 5 秒内退出场景验证(2026-08-05 当天,同设备)
系统 extend_time 语义:手指离手 5 秒内未调用 reportDrawnCompleted 则该值为 0。真机实测退出场景:
| # | 场景 | 操作 | 结果 |
|---|---|---|---|
| 1 | 启动后 1 秒内退出(首帧已提交) | aa start → 1 秒后 force-stop |
reportDrawnCompleted 已在退出前提交,事件正常生成、extend_time 有效;事件送达时进程已退出,由后续进程补投递处理 |
| 2 | 启动后立即强杀(首帧未提交) | aa start → 立即 force-stop(无间隔) |
仅有"已监听 draw"日志,reportDrawnCompleted 从未调用;5 秒窗口过期,系统未生成有效事件,重启后无补投递,不产生上报 |
| 3 | 补报判定(修复验证) | 部署新版本后 force-stop → 冷启动 |
新进程收到旧进程遗留事件,icon_input_time 早于注册时间约 4 秒,判定 补报事件=是,entry_page 按 unknown 处理 |
| 4 | 正常冷启动(修复验证) | 干净 force-stop → 启动 |
收到自身事件,判定 补报事件=否,无误判 |
结论:首帧未提交时退出由 extend_time<=0 过滤兜底不上报;首帧已提交后退出的补报数据有效,修复后补报事件与正常事件可稳定区分(判定逻辑见 6.1)。
8.7 全场景(含 5 秒退出)3 组验证(2026-08-05 当天,同设备)
每组 8 步链路:清数据 → 未登录冷启动 → 网络页拦截 → 隐私页拦截 → 登录 Index 拦截 → 已登录冷启动 → 热启动 → 立即强杀(首帧未提交)→ 1 秒退出(首帧已提交)→ 补报判定。3 组结果:
| 步骤 | 组 1 | 组 2 | 组 3 |
|---|---|---|---|
未登录冷启动(LaunchLogin) |
158ms / 补报=否 | 180ms / 补报=否 | 180ms / 补报=否 |
| 网络页重复注册拦截 | ✓ | ✓ | ✓ |
| 隐私页重复注册拦截 | ✓ | ✓ | ✓ |
Index 重复注册拦截 |
✓ | ✓ | ✓ |
已登录冷启动(Index) |
129ms / 补报=否 | 132ms / 补报=否 | 131ms / 补报=否 |
| 热启动(进程存活) | 无新事件 | 无新事件 | 无新事件 |
| 立即强杀(首帧未提交)后重启 | 被强杀进程无有效事件,重启进程正常捕获 | 同左 | 同左 |
| 1 秒退出(首帧已提交)后重启 | 先收遗留事件补报=是,再收自身事件补报=否 | 同左 | 同左 |
24 个断言(3 组 × 8 步)全部通过,组间一致。补报判定在每组中均双向正确:遗留事件稳定判定为补报(entry_page 不归因),自身事件稳定判定为非补报(entry_page 正常归因)。
8.8 重装 + is_first_install 维度全场景验证(2026-08-06,CrashDiagnosticsDemo,nova 14 Pro)
前置改动:demo 同步 is_first_install 维度(preferences 无启动记录视为安装后首启,putSync 后 flushSync 同步落盘——初版遗漏 flushSync 导致 force-stop 后数据丢失、二次启动误判首启,对比 AuthSession 用法定位修复);页面重命名为 LaunchLogin/LaunchNetwork/LaunchPrivacy,诊断类更名为 LaunchDiagnostics。
验证方式:bm uninstall -n 完全卸载重装(真实安装后首启语义),随后全场景 3 组 × 8 步(冷启动/网络页拦截/隐私页拦截/登录跳转拦截/已登录冷启动/热启动/立即强杀/1 秒退出补报)。表中数据为页面重命名前的同一逻辑验证(页面名仅为日志展示用途,不影响捕获语义);重命名后 devecocli build 编译通过,真机运行回归待设备恢复后补跑。
| 步骤 | 组 1 | 组 2 | 组 3 |
|---|---|---|---|
未登录冷启动(入口 LaunchLogin) |
165ms / 首启=true / 补报=否 | 165ms / 首启=true | 161ms / 首启=true |
网络页/隐私页/Index 重复注册拦截 |
✓ ×3 | ✓ ×3 | ✓ ×3 |
已登录冷启动(入口 Index) |
134ms / 首启=false | 129ms / 首启=false | 131ms / 首启=false |
| 热启动(进程存活) | 无新事件 | 无新事件 | 无新事件 |
| 立即强杀后重启 | 142ms / 补报=否 | 132ms / 补报=否 | 127ms / 补报=否 |
| 1 秒退出后重启 | 补报=是 + 自身=否 | 同左 | 同左 |
24 断言全部通过。关键结论:
is_first_install维度语义正确:安装/清数据后首启 =true(161165ms),非首启 =134ms);首启耗时系统性偏高约 30ms,该维度可将其从常规分布中区分,避免拉高 P50/P95。false(127- 补报判定 3 组双向正确,拦截保护 3 组 × 3 页无漏防。
- 耗时稳定(127~165ms);此前观测到的
response_latency=5086ms异常值本次未复现(偶发)。 - 注意:demo 的
is_first_install(boolean)基于preferences记录,bm clean清数据后该标记重置(再次显示首启);生产实现为app_install_status(normal/new_install/over_written三值,见第 4 节),以生产为准。
8.9 真实应用验证(2026-08-06,完整 workspace,nova 14 Pro)
在完整 workspace 落地(HXLaunchMonitorPlugin 插件 + APMHelper 注册)后的真机数据:
| 场景 | 结果 |
|---|---|
全新安装首启(new_install) |
链路 A:cold_start_time=1141ms,Entry=Index,Elk 上报 ✅ |
二次冷启动(normal) |
链路 A:cold_start_time=1717ms,Elk 上报 ✅ |
| 链路 B(分桶) | 待后台下发 launch 策略(5.1)后复测 |
编译:claude_compile.sh → BUILD_SUCCESS(链路 B API 随插件编译验证通过)。
真实应用耗时(1.11.7s)远高于 demo(127165ms),符合生产首页复杂度的预期;app_install_status 维度可区分首启与常规启动的耗时差异。