Compare commits
2 Commits
96f183c2c0
...
ec8cf06c66
| Author | SHA1 | Date | |
|---|---|---|---|
| ec8cf06c66 | |||
| 8060805c84 |
@@ -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 == 1(STATUS_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_install,false→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 {
|
||||
// 插件初始化时即把固定维度配置注入 LaunchMonitor(user/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;
|
||||
}
|
||||
Reference in New Issue
Block a user