FilesLast commitLast update
3 months ago
3 months ago
3 months ago
4 months ago
4 months ago
README

@cptf/benchmark

CPTF benchmark 插件 —— host 侧(Node.js 测试运行器内)。提供 renderTime / unmountTime 两个指标的采集器,桥接设备侧 WebSocket 事件到 CPTF 框架的 MetricCollectorFactory。

与 @cptf/benchmark-rn(RN 设备侧)互为兄弟;@cptf/benchmark-rn 依赖 @cptf/benchmark/metric-types 导入指标类型常量。本包不含任何 react-native 依赖,可被任意平台的 CPTF 用例复用。


指标定义

本包提供的指标

指标 类型常量 含义 采集方式
renderTime RENDER_TIME_METRIC_TYPE = 'renderTime' 从点击渲染按钮到首批子组件首帧绘制完成的时间(ms) 订阅 WebSocket 事件;回退读 benchmark-render-time 元素文本
unmountTime UNMOUNT_TIME_METRIC_TYPE = 'unmountTime' 从点击重置按钮到 MeasureContainer 及子组件完全从原生视图树移除的时间(ms) 订阅 WebSocket 事件;回退读 benchmark-unmount-time 元素文本

采集链路:设备侧原生组件(onDraw / onDetachedFromWindow)测量 → WebSocket 上报 → MetricWebSocketServer → global.__metricEventBus → EventBusMetricBridge → 本包 collector 订阅 → ExecutionEngine.collect() → 报告。

回退机制:当 WebSocket 事件未到达时,renderTime collector 用 waitForTestId 轮询 15s 等待 benchmark-render-time 元素出现;unmountTime collector 用 findByTestId 单次查找 benchmark-unmount-time 元素(非轮询)。

框架内置指标(非本包提供)

以下指标由 @cptf/framework 的 PerformanceCollector 通过系统命令采集,与本包无关:

指标 采集来源 说明
fps HarmonyOS: hidumper -s RenderService / Android: dumpsys gfxinfo 渲染帧率
memoryPeak HarmonyOS: hidumper --mem <PID> / Android: dumpsys meminfo <PID> 进程内存峰值
blankScreenRatio ScreenRecorder 录屏 → 逐帧分析白屏比例 白屏率

用例中 collect: ['renderTime', 'unmountTime', 'fps', 'memoryPeak'] 前两个由本包提供,后两个由框架内置。


模块

文件 职责
src/metric-types.ts 指标类型常量:RENDER_TIME_METRIC_TYPE = 'renderTime'、UNMOUNT_TIME_METRIC_TYPE = 'unmountTime'。两端(本包 + 设备侧 @cptf/benchmark-rn)共用。
src/metric-bridge.ts MetricBridge 契约:subscribe(eventName, onValue, onError?) => Unsubscribe。平台无关的事件源抽象。
src/event-bus-metric-bridge.ts EventBusMetricBridge:包装 global.__metricEventBus(CPTF 的 WS server 已把 metric 事件喂入该 bus)。默认桥实现。
src/preset-collectors/render-time-collector.ts RenderTimeCollector:onStart 订阅 bridge 'renderTime';collect 先检查 WS 事件值,回退读 benchmark-render-time 文本 /Took (\d+)ms/。
src/preset-collectors/unmount-time-collector.ts UnmountTimeCollector:onStart 订阅 bridge 'unmountTime';collect 先检查 WS 事件值,回退读 benchmark-unmount-time 文本(findByTestId 单次查找,非轮询)。
src/index.ts registerBenchmarkMetrics(bridge?):用 bridge 构造 collector 并注册到 MetricCollectorFactory。底部自动调用(默认 EventBusMetricBridge),故 import '@cptf/benchmark' 即完成注册。

工作流程

设备 app (@cptf/benchmark-rn device-metric-provider)
  └─ WS send {type:'renderTime', value, markerName, deviceId} ─→
        宿主 @cptf/framework MetricWebSocketServer (acquireMetricServer 全局单例, port 18081)
          └─ emit global.__metricEventBus
               └→ 本包 EventBusMetricBridge 订阅
                    └→ RenderTimeCollector / UnmountTimeCollector
                         └→ MetricCollectorFactory.register (覆盖框架 stub)
                              └→ ExecutionEngine.collect() → TestResult.extraMetrics → 报告

事件格式:

{
  "type": "renderTime",
  "value": 437,
  "markerName": "views1500",
  "timestamp": 1783677892000,
  "deviceId": "7NXBB26324000068"
}

deviceId 过滤:collector 声明了 deviceId 字段用于多设备过滤,但当前实现未在 onStart 中赋值(this.deviceId 始终为 null),过滤逻辑实际为空操作。多设备场景需后续补充赋值逻辑。


用法

// app/tests/cases/performance/views-1500-render-perf.test.ts
import 'reflect-metadata';
import { Test, Step, PerformanceTest } from '@cptf/framework';
import '@cptf/benchmark';   // 副作用:registerBenchmarkMetrics() 自动注册

@Test({
  id: 'views-1500-render-perf',
  scene: 'views-1500',
  collect: ['renderTime', 'unmountTime', 'fps', 'memoryPeak'],
  //               ↑ 本包提供    ↑ 框架内置
})
class Views1500RenderPerfTest extends PerformanceTest { ... }

本包是 built dist(main: dist/index.js)。CPTF 运行器用 ts-node(transpileOnly,忽略 node_modules 内的 .ts),host 侧消费必须走编译产物——改源码后执行 pnpm run build。


扩展指南

1. 新增一个指标类型(host 侧 collector)

例:增加一个 firstPaint 指标。

  1. 在 src/metric-types.ts 加常量:export const FIRST_PAINT_METRIC_TYPE = 'firstPaint' as const;
  2. 在 src/preset-collectors/ 加 first-paint-collector.ts:
    import { BaseMetricCollector, MetricCollectorContext } from '@cptf/framework';
    import { MetricBridge, MetricEvent, Unsubscribe } from '../metric-bridge';
    import { FIRST_PAINT_METRIC_TYPE } from '../metric-types';
    export class FirstPaintCollector extends BaseMetricCollector {
      type = FIRST_PAINT_METRIC_TYPE;
      private value = 0; private unsub: Unsubscribe | null = null;
      constructor(private bridge: MetricBridge) { super(); }
      async onStart(ctx: MetricCollectorContext) { this.unsub = this.bridge.subscribe<MetricEvent>('firstPaint', e => { if (e && typeof e.value === 'number') this.value = e.value; }); }
      async onFinish(_ctx: MetricCollectorContext) { this.unsub?.(); this.unsub = null; }
      async collect(ctx: MetricCollectorContext) { return this.value; }
    }
    
  3. 在 src/index.ts 的 registerBenchmarkMetrics 里追加注册 + re-export 常量。
  4. 设备侧 deviceMetricProvider.report('firstPaint', value, markerName)。
  5. 用例 collect: ['firstPaint']。

2. 新增一个平台 bridge

MetricBridge 是平台无关契约。默认 EventBusMetricBridge(WS → __metricEventBus)。若你的平台用别的事件源,实现 MetricBridge:

import { MetricBridge, MetricEvent, Unsubscribe } from '@cptf/benchmark';
class MyBridge implements MetricBridge {
  subscribe<T>(eventName: string, onValue: (v: T) => void, onError?: (e: Error) => void): Unsubscribe {
    const off = myPlatform.on(eventName, (e: MetricEvent) => { try { onValue(e as T); } catch (err) { onError?.(err as Error); } });
    return () => off();
  }
}
registerBenchmarkMetrics(new MyBridge());

设计原则

  • 只管 renderTime / unmountTime:fps、memoryPeak、blankScreenRatio 等指标由 @cptf/framework 的 PerformanceCollector / ScreenRecorder 采集,与本包无关。
  • host/device 分离:本包只跑在宿主 Node 进程;不 import react-native;不进设备 bundle。
  • 零侵入核心:不改 @cptf/framework,纯靠 MetricCollectorFactory.register 扩展。
  • 事件驱动 collector:collector 不主动计时,只订阅事件——真实测量由设备侧原生完成,本包只搬运 + 回退兜底。