Compare commits

...

2 Commits

Author SHA1 Message Date
clz ec8cf06c66 docs: 补充策略门控机制源码级验证结论(§5.2)
- 新增 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>
2026-08-07 14:16:31 +08:00
clz 8060805c84 feat: 按真实落地实现接入 HXLaunchMonitorPlugin APM 插件系统
- 新增 monitor/plugin/HXLaunchMonitorPlugin.ets:仿 HXCrashMonitorPlugin 接入 APM 插件系统(基类 AbstractHXBasePlugin),initPlugin 注入固定维度配置、startPlugin 创建分桶指标(解决 create 早于 SDK 初始化的时序问题)
- 新增 monitor/plugin/ILaunchMonitorConfig.ets:7 方法配置接口(userId/deviceType/deviceModel/appVersion/buildType/gitCommit/appInstallStatus)
- LaunchMonitor 同步 v2:15 字段负载(新增 device_type/device_model/user_id/build_type),维度全部经 launchConfig 注入(不再直接依赖 AppConfigManager/StartManager/deviceInfo),ensureColdStartMetric 惰性兜底(插件未启动时 record 前自建指标),watcher 更名 launchWatcher
- APMHelper 同步:注册 HXLaunchMonitorPlugin + HXLaunchMonitorConfig(含 AppInstallStatus 三值枚举映射、deviceInfo.marketName 设备型号)
- 同步自完整 workspace 真实实现(entry_clean.tar.gz,已编译验证 BUILD_SUCCESS)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-07 13:41:28 +08:00
5 changed files with 217 additions and 46 deletions
@@ -104,6 +104,16 @@ time + icon_input_time + start_type + process_name
**判定生效**:后台下发后重启应用,冷启动日志中应消失 `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()`,默认"有策略且开启",`HXLaunchMonitorPlugin` override 为 `true` 强制通过)仅控制插件生命周期;**指标级**(`DataMonitorModule.create/getEventMonitor` 检查 `_strategy?.isOpen()`)独立生效——`launch` 无策略 bean 时 `_strategy=null``create`/`getEventMonitor` 返回 `NullDataMonitorImp``record` 空转打印 `eventId has not created!`。**插件强制开启绕不过指标级门控,必须后台下发策略。**
- **`isOpen()` 语义**`status == 1STATUS_OPEN` 且抽样通过(`isOpenByRandom`,对应 `sampling_rate`)。
- **聚合上传触发**`onRecord → isNeedUpload`——累计条数 ≥ `aggregatorSizeThreshold``aggre_count`)或距上次上传 ≥ `aggregatorTimeThreshold``aggre_time`)即触发 `push`
- **重启生效机制**`LaunchMonitor.ensureColdStartMetric()` 的幂等标记在 `create` 被策略拒绝(返回空实现)时仍置 `true`——本进程内策略下发后不会重试创建,**必须重启应用**(与上节"判定生效"一致);策略未下发时链路 A(Elk 追溯)不受影响、正常上传。
随后为该模块补充 ELK 索引模板,在 Grafana 建立看板面板(建议形态):
```json
@@ -9,6 +9,7 @@ import {
import { HXPerformancePlugin } from "@kernel/app_monitor_performance";
import { HXWebMonitorPlugin } from '@kernel/app_monitor_web';
import { HXUserService } from "biz_hxservice";
import { deviceInfo } from "@kit.BasicServicesKit";
import { ElkService, ElkUploadMessage, ElkUploadMessageBuilder } from "service_monitor";
import {
AuthLogMonitor,
@@ -20,9 +21,11 @@ import {
import { HXAuthLogMonitor } from "biz_auth";
import { HXHttpMonitorPlugin } from "@kernel/app_monitor_http";
import { HXCrashMonitorPlugin, ICrashMonitorConfig } from "@kernel/app_monitor_crash";
import { HXLaunchMonitorPlugin } from "./plugin/HXLaunchMonitorPlugin";
import { ILaunchMonitorConfig } from "./plugin/ILaunchMonitorConfig";
import { router } from "@kit.ArkUI";
import { GIT_COMMIT } from "biz_common/src/main/ets/utils/debug/GitBuildConfig";
import { AppConfigManager, getConnectionManager } from "biz_common";
import { AppConfigManager, StartManager, AppInstallStatus, getConnectionManager } from "biz_common";
import { HxCBASAgentProvider } from "@b2c-spi/hx_cbas_api";
import {
AppContextLike,
@@ -65,6 +68,7 @@ export class APMHelper {
.plugin(new HXWebMonitorPlugin())
.plugin(new HXHttpMonitorPlugin())
.plugin(new HXCrashMonitorPlugin(new HXCrashMonitorConfig()))
.plugin(new HXLaunchMonitorPlugin(new HXLaunchMonitorConfig()))
.build()
.startAllPlugins(new APMMonitorOnStartCallback())
APMHelper.initLeakWatcher(context)
@@ -192,6 +196,51 @@ class HXMonitorAppConfig extends IMonitorAppConfig {
}
}
/**
* 启动监控插件配置:提供冷启动指标固定维度(user/app-version/build
*/
class HXLaunchMonitorConfig implements ILaunchMonitorConfig {
provideUserId(): string {
return HXUserService.getInstance().getUserIdFromLocal()
}
provideDeviceType(): string {
return deviceInfo.deviceType
}
provideDeviceModel(): string {
// 营销名(设备名称),如 nova 14 Pro;无则回退品牌
return deviceInfo.marketName || deviceInfo.brand
}
provideAppVersion(): string {
if (APMHelper.isDebugOrTestBuild()) {
return DEBUG_VERSION
}
return AppConfigManager.getInstance().getConfigProvider()?.getInnerVersionFull() ?? DEBUG_VERSION
}
provideBuildType(): string {
return BuildProfile.BUILD_MODE_NAME.toString()
}
provideGitCommit(): string {
return GIT_COMMIT
}
provideAppInstallStatus(): string {
switch (StartManager.getInstance().getAppInstallStatus()) {
case AppInstallStatus.NEW:
return 'new_install'
case AppInstallStatus.OVER_WRITTEN:
return 'over_written'
case AppInstallStatus.NORMAL:
default:
return 'normal'
}
}
}
/**
* tcp监控配置
*/
@@ -1,22 +1,26 @@
/**
* author:cheliangzhao
* created on 2026/08/06
* desc: 启动性能监控 v2,订阅 APP_LAUNCH 采集冷启动耗时,走 ElkService 追溯 + HXEventMonitor 分桶双通道
*/
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, 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 { HXEventMonitor, DataMonitorBuilder } from '@kernel/app_monitor_event';
import { ILaunchMonitorConfig } from './plugin/ILaunchMonitorConfig';
const TAG: string = 'LaunchMonitor';
/**
* 启动性能监控:订阅 APP_LAUNCH 事件,采集冷启动耗时(extend_time),
* 并通过 ElkService(追溯)与 HXEventMonitor(分桶)双通道上报。
*/
export class LaunchMonitor {
private static readonly MAX_EVENT_KEYS: number = 20;
/**
* 补报判定容差(ms):正常启动链间隔毫秒~秒级,补报(上次启动遗留事件)至少早 5
* 补报判定容差(ms):正常启动链间隔毫秒~秒级,补报(上次启动遗留事件)至少早 2
*/
private static readonly BACKFILL_TOLERANCE_MS: number = 2000;
/** watcher 注册时间戳(epoch ms),补报判定的锚点 */
@@ -25,6 +29,13 @@ export class LaunchMonitor {
/** 首帧绘制完成时快照的入口页面,在 onDraw 回调中赋值 */
private static capturedEntryPage: string = 'unknown';
/** 固定维度配置(由 HXLaunchMonitorPlugin 注入 user/app-version/build 等) */
private static launchConfig: ILaunchMonitorConfig | null = null;
static setLaunchConfig(config: ILaunchMonitorConfig): void {
LaunchMonitor.launchConfig = config
}
private static watcherInitialized: boolean = false;
private static bucketMetricInitialized: boolean = false;
private static drawReportSubmitted: boolean = false;
@@ -106,9 +117,8 @@ export class LaunchMonitor {
return
}
LaunchMonitor.watcherRegisterTime = Date.now()
LaunchMonitor.ensureBucketMetric()
hiAppEvent.addWatcher({
name: "startupWatcher",
name: "launchWatcher",
appEventFilters: [{
domain: hiAppEvent.domain.OS,
names: [hiAppEvent.event.APP_LAUNCH]
@@ -189,18 +199,22 @@ export class LaunchMonitor {
cold_start_time: coldStartTime,
start_type: startType,
entry_page: isBackfillEvent ? 'unknown' : LaunchMonitor.capturedEntryPage,
app_version: LaunchMonitor.getAppVersion(),
app_install_status: LaunchMonitor.getAppInstallStatus(),
device_type: LaunchMonitor.launchConfig?.provideDeviceType() ?? 'unknown',
device_model: LaunchMonitor.launchConfig?.provideDeviceModel() ?? 'unknown',
user_id: LaunchMonitor.launchConfig?.provideUserId() ?? 'unknown',
app_version: LaunchMonitor.launchConfig?.provideAppVersion() ?? 'unknown',
build_type: LaunchMonitor.launchConfig?.provideBuildType() ?? 'unknown',
app_install_status: LaunchMonitor.launchConfig?.provideAppInstallStatus() ?? 'unknown',
response_latency: responseLatency,
animation_finish_time: animationFinishTime,
startability_processstart_dur: startabilityProcessStartDur,
appattach_to_appforeground_dur: appattachToAppForegroundDur,
git_commit: LaunchMonitor.getGitCommit(),
git_commit: LaunchMonitor.launchConfig?.provideGitCommit() ?? 'unknown',
is_backfill: isBackfillEvent,
}
const builder = new LaunchElkBuilder('i')
builder.userid(HXUserService.getInstance().getUserId())
const builder = new LaunchElkBuilder()
builder.userid(LaunchMonitor.launchConfig?.provideUserId() ?? 'unknown')
builder.messageKey('cold_start_time')
builder.messageValue(JSON.stringify(metricPayload))
@@ -210,14 +224,21 @@ export class LaunchMonitor {
HXLog.i(TAG, `Cold start metric uploaded: ${coldStartTime}ms${isBackfillEvent ? ' (backfill)' : ''}`)
// 分桶指标(链路 B):记录原始耗时,SDK 按桶自动归集,负责看板聚合
// TODO(api-confirm): setDimension_1 / record 签名需以 .d.ts 核对
// 兜底:若 APM 插件尚未执行 startPlugin 创建指标,此处惰性创建
LaunchMonitor.ensureColdStartMetric()
const factory = HXEventMonitor.getEventFactory('launch')
const monitor = factory.getEventMonitor('cold_start_time')
monitor.setDimension_1(startType.toString())
monitor.setDimension_2(isBackfillEvent ? 'unknown' : LaunchMonitor.capturedEntryPage)
monitor.setDimension_3(LaunchMonitor.getAppVersion())
monitor.setDimension_4(LaunchMonitor.getAppInstallStatus())
const dim1 = LaunchMonitor.launchConfig?.provideAppVersion() ?? 'unknown'
const dim2 = LaunchMonitor.launchConfig?.provideBuildType() ?? 'unknown'
const dim3 = LaunchMonitor.launchConfig?.provideDeviceType() ?? 'unknown'
const dim4 = LaunchMonitor.launchConfig?.provideAppInstallStatus() ?? 'unknown'
monitor.setDimension1(dim1)
monitor.setDimension2(dim2)
monitor.setDimension3(dim3)
monitor.setDimension4(dim4)
monitor.record(coldStartTime)
HXLog.i(TAG, `Cold start bucket metric recorded: ${coldStartTime}ms`)
} catch (e) {
HXLog.e(TAG, `uploadColdStartTime failed: ${JSON.stringify(e)}`)
@@ -227,17 +248,17 @@ export class LaunchMonitor {
/**
* 创建 launch 分桶指标(幂等):桶 [500,600,700,800,1000,1500,2000,3000]
* 按实测主体分布(600-800ms)细分,尾部留劣化告警锚点。
* TODO(api-confirm): DataMonitorBuilder / setBuckets / create 签名需以 .d.ts 核对
* 由 HXLaunchMonitorPlugin.startPlugin() 在 APM 引擎启动后调用,避免 create 早于 SDK 初始化。
*/
private static ensureBucketMetric(): void {
static ensureColdStartMetric(): void {
if (LaunchMonitor.bucketMetricInitialized) {
return
}
const builder = new DataMonitorBuilder()
builder.setMetricName('cold_start_time')
builder.setDimension1Name('start_type')
builder.setDimension2Name('entry_page')
builder.setDimension3Name('app_version')
builder.setDimension1Name('app_version')
builder.setDimension2Name('build_type')
builder.setDimension3Name('device_type')
builder.setDimension4Name('app_install_status')
builder.setBuckets([500, 600, 700, 800, 1000, 1500, 2000, 3000])
HXEventMonitor.getEventFactory('launch').create(builder)
@@ -250,35 +271,17 @@ export class LaunchMonitor {
return iconInputTime < LaunchMonitor.watcherRegisterTime - LaunchMonitor.BACKFILL_TOLERANCE_MS
}
private static getAppVersion(): string {
return AppConfigManager.getInstance().getConfigProvider()?.getInnerVersionFull() ?? 'unknown'
}
/** 代码级追溯:Git 提交号(biz_common 构建时注入) */
private static getGitCommit(): string {
return GIT_COMMIT
}
/**
* 维度:安装状态(normal/new_install/over_written)。
* 真实实现取三值安装来源;当前以 StartManager 布尔值映射(true→new_installfalse→normal),
* over_written 需安装来源信息,待完整 workspace 对齐。
*/
private static getAppInstallStatus(): string {
return StartManager.getInstance().getAppInstallStatus() ? 'new_install' : 'normal'
}
}
/** ELK 消息构造器,业务来源为 launch 模块(命名约束见文档第 4 节) */
/** ELK 消息构造器,业务来源标记为 launch 模块 */
class LaunchElkBuilder extends ElkUploadMessageBuilder {
constructor(biz_level: string = 'i') {
super(Date.now(), 'launch', biz_level);
constructor() {
super(Date.now(), 'launch', 'i');
}
}
/**
* 冷启动指标上报负载(ElkService 追溯通道,messageValue 的 JSON 结构)。
* 最小必要字段:指标/维度/补报标识;其余字段按需加回(见文档第 4 节)。
*/
interface ColdStartMetricPayload {
// 核心指标:冷启动耗时(ms),即系统事件的 extend_time
@@ -287,10 +290,17 @@ interface ColdStartMetricPayload {
start_type: number
// 维度:入口页面(Index/LauncherPage 等;补报事件为 unknown
entry_page: string
// 维度:设备类型(phone/tablet/2in1
device_type: string
// 维度:设备型号(如 nova 14 Pro / MIA-AL00
device_model: string
// 维度:用户ID
user_id: string
// 维度:应用内部版本号
app_version: string
// 维度:安装状态(normal 常规 / new_install 全新安装 / over_written 覆盖安装),
// 首启耗时显著偏高,需从常规分布中区分
// 维度:构建类型(debug/release/forTest 等)
build_type: string
// 维度:安装状态语义名("normal"/"new_install"/"over_written",见 AppInstallStatus;新装耗时显著偏高需区分)
app_install_status: string
// 系统响应段:离手到动效开始耗时(ms,需 API 22+)
response_latency: number
@@ -304,4 +314,4 @@ interface ColdStartMetricPayload {
git_commit: string
// 补报标识:true 表示系统补投递的上次启动遗留事件(entry_page 无意义)
is_backfill: boolean
}
}
@@ -0,0 +1,66 @@
/**
* author:cheliangzhao
* created on 2026/08/06
* desc: 冷启动监控插件,仿 HXCrashMonitorPlugin 接入 APM 插件系统;在插件启动后创建冷启动分桶指标,
* 并把固定维度配置(user/app-version/build)注入 LaunchMonitor。
* 解决分桶 create 早于 HXEventMonitor SDK 而失效的时序问题
*/
import { AbstractHXBasePlugin, IMonitorStrategy, StrategyModuleConfig, HXMonitorLogger } from '@kernel/app_monitor_lib';
import { LaunchMonitor } from '../LaunchMonitor';
import { ILaunchMonitorConfig } from './ILaunchMonitorConfig';
/**
* 冷启动监控插件
*
* 职责:在 APM 插件引擎启动后,创建 launch 分桶指标(链路B),并把固定维度配置
* user/app-version/build 等)经 LaunchMonitor.setLaunchConfig 注入。APP_LAUNCH
* 事件订阅、Elk 追溯上传与首帧埋点仍由 LaunchMonitor 承接(在 onCreate 早注册)。
*/
export class HXLaunchMonitorPlugin extends AbstractHXBasePlugin {
private TAG: string = 'HXLaunchMonitorPlugin';
private static MODULE_NAME = 'launch';
private readonly config: ILaunchMonitorConfig;
constructor(config: ILaunchMonitorConfig) {
super();
this.config = config;
}
getModuleName(): string {
return HXLaunchMonitorPlugin.MODULE_NAME;
}
/**
* 新指标无后台策略下发,强制随主配置开启;后续如需开关控制可改读 isSwitchOpen
*/
isSupport(): boolean {
return true
}
parseMonitorStrategy(config: StrategyModuleConfig): IMonitorStrategy | null {
return null
}
isSwitchOpen(gatherSwitch: number): boolean {
return true
}
initPlugin(): void {
// 插件初始化时即把固定维度配置注入 LaunchMonitoruser/app-version/build 等)
LaunchMonitor.setLaunchConfig(this.config)
}
startPlugin(): void {
// 指标创建延后到 startPlugin(此时 HX 引擎已初始化)
LaunchMonitor.ensureColdStartMetric()
HXMonitorLogger.d(this.TAG, 'startPlugin: launch cold_start_time metric ready')
}
stopPlugin(): void {
// 无长连接需要释放
}
destroyPlugin(): void {
// 无需清理
}
}
@@ -0,0 +1,36 @@
/**
* author:cheliangzhao
* created on 2026/08/06
* desc: 冷启动指标固定维度(user/app-version/build 等)的配置提供接口。
* 由 HXLaunchMonitorConfig 实现传入,LaunchMonitor 据此注入指标维度。
*/
export interface ILaunchMonitorConfig {
/**
* 提供用户ID(Elk 消息用户标识)
*/
provideUserId(): string;
/**
* 提供设备类型(如 phone/tablet/2in1
*/
provideDeviceType(): string;
/**
* 提供设备品牌(如 HUAWEI)
*/
provideDeviceModel(): string;
/**
* 提供应用内部版本号
*/
provideAppVersion(): string;
/**
* 提供构建类型
*/
provideBuildType(): string;
/**
* 提供代码级追溯 Git 提交号
*/
provideGitCommit(): string;
/**
* 提供安装状态语义名(normal/new_install/over_written
*/
provideAppInstallStatus(): string;
}