- name
- ohos-dev-distributed-mmi-ram-rom-measurement
- description
- Use when measuring RAM/ROM impact of OpenHarmony multimodal input (MMI) subsystem changes on DAYU200 (RK3568). Covers ROM measurement via readelf section analysis and file size comparison, RAM measurement via /proc/PID/smaps, smaps_rollup, hidumper --mem, and baseline/current .so swap methodology. Includes code-level theoretical analysis for heap-allocated data structures.
- metadata
- {"author":"openharmony","scope":"domain","stage":"development","domain":"mmi","capability":"ram-rom-measurement","version":"0.2.0","status":"draft","tags":["multimodalinput","ram","rom","performance","binary-size"],"related-skills":["ohos-dev-distributed-mmi-device-test-harness"]}
# MMI RAM/ROM 影响度量
衡量 multimodalinput 子系统代码变更前后的 ROM(二进制体积)和 RAM(运行时内存)占用变化。
**前置 skill**: 编译部署、设备连接等基础设施见 `ohos-dev-distributed-mmi-device-test-harness`。
## When to Use
- 提交 PR 前需要量化代码变更对二进制体积的影响
- 需要证明重构/新特性不引入内存膨胀
- 评审要求提供 RAM/ROM 数据
## Safety and Comparability Gates
Replacing system `.so` files, remounting `/` read-write, and rebooting a device are destructive test
operations. Do them only after explicit user approval, keep `.bak` copies of every replaced library,
and restore the device to the requested final state before ending the workflow.
A RAM/ROM conclusion is only comparable when these conditions are recorded:
- baseline and current image/build or `.so` provenance
- exact libraries replaced
- whether the same functional path was executed before both captures
- whether process-level numbers are idle-vs-idle, exercised-vs-exercised, or mixed
If baseline is idle but current is post-exercise, do not present process-level PSS delta as feature
RAM cost. Use it only as context and rely on library PSS plus code-level theoretical analysis.
## 度量三支柱
本 skill 要求从三个互相佐证的维度度量 RAM 影响:
| 维度 | 方法 | 反映什么 | 局限 |
|------|------|---------|------|
| **ROM 段级** | `readelf -S` | 静态映射内存(.text/.rodata/.bss) | 不反映堆分配 |
| **RAM 实测** | smaps + hidumper 真机采集 | 进程实际物理内存 | 受采集条件/编译差异影响 |
| **RAM 理论** | git diff 分析新增容器 + 结构体估算 | 堆分配和运行时数据结构 | 人工估算,依赖正确的结构体大小分析 |
三个维度**必须同时完成**,互相交叉验证。仅做段级估算会遗漏堆分配;仅做实测会受噪声影响;仅做理论分析无法确认实际表现。
## 度量目标库和进程范围
先根据代码变更确定影响面,不要默认只看服务端进程。至少检查改动是否触达以下库:
| 库 | 角色 | RAM 分析范围 |
|----|------|-------------|
| `libmmi-server.z.so` | 服务端主逻辑 | `multimodalinput` 进程 |
| `libmmi-server-common.z.so` | 服务端公共 | `multimodalinput` 进程 |
| `libmmi-util.z.so` | 工具库 | 所有加载该库的目标进程 |
| `libmmi-client.z.so` | 客户端 API/代理 | 触发该 API 的客户端进程,不能用服务端进程 PSS 代表 |
规则:
- ROM 分析应覆盖所有受影响的 libmmi `.so`,包括 `libmmi-client.z.so`。
- RAM 分析必须按实际加载进程归属。`libmmi-client.z.so` 通常不在 `multimodalinput`
服务端进程中加载;如果客户端 API、proxy、NAPI 或测试二进制路径受影响,需要选择一个会加载
`libmmi-client.z.so` 的客户端进程执行功能路径并采集该进程的 smaps/hidumper 数据。
- 如果改动只影响服务端内部路径,可以说明 client so 未加载到服务端进程,但仍要用代码 diff、符号
或段级数据确认 `libmmi-client.z.so` 是否真的无变化或不参与本次 RAM 结论。
- 最终报告要列出“库 → 进程 → 是否执行功能路径 → 是否纳入 RAM 结论”的映射,避免把服务端 PSS
误当成全量 MMI RAM 成本。
## ROM 度量方法
### 1. 文件级对比(快速)
```bash
BUILD_OUT="code/out/rk3568/multimodalinput/input"
ls -la $BUILD_OUT/libmmi-*.z.so
```
### 2. 段级对比(精确,推荐)
使用 `readelf -S` 分析关键段:
```bash
readelf -S libmmi-server.z.so | grep -E '\.text|\.rodata|\.data|\.bss'
```
| 段 | 含义 | 影响 |
|----|------|------|
| `.text` | 可执行代码 | 直接影响 ROM;映射为 shared clean 影响 RAM PSS |
| `.rodata` | 只读数据(字符串常量等) | 影响 ROM 和 shared clean PSS |
| `.data` | 已初始化全局变量 | 影响 ROM 和 private dirty |
| `.bss` | 零初始化全局变量 | 不影响 ROM;影响 anonymous RAM |
| `.data.rel.ro` | 重定位后只读 | 影响 ROM |
### 3. 基线获取策略
**方案 A:设备 .bak 文件**(推荐)
部署修改版 .so 前,先备份原始 .so:
```bash
$HDC shell "cp /system/lib/libmmi-server.z.so /data/local/tmp/libmmi-server.z.so.bak"
# 部署新 .so...
# 之后拉取基线
$HDC file recv /data/local/tmp/libmmi-server.z.so.bak ./libmmi-server.z.so.bak
```
**方案 B:CI 基线构建**
从 CI 每日构建镜像中提取同版本 .so 作为基线。
## RAM 度量方法
### 1. 找到进程 PID
```bash
$HDC shell "pidof multimodalinput"
```
**注意 PID 稳定性**:multimodalinput 是系统服务,可能被 watchdog 重启导致 PID 变化。在多条 hdc 命令之间 PID 可能不同。**解决方案**:使用命令替换在单次 hdc shell 调用中完成 PID 查找和数据采集:
```bash
$HDC shell "cat /proc/\$(pidof multimodalinput)/smaps_rollup"
```
### 2. smaps_rollup(进程级汇总)
```bash
$HDC shell "cat /proc/\$(pidof multimodalinput)/smaps_rollup"
```
关键字段:
- **Pss**: 按共享比例分摊后的实际物理内存(最能反映真实占用)
- **Rss**: 驻留物理内存(包含共享页全量)
- **Size**: 虚拟地址空间大小
### 3. smaps per-library PSS(重要)
```bash
$HDC shell "cat /proc/\$(pidof multimodalinput)/smaps" > smaps.txt
```
解析脚本和注意事项见 `references/smaps-pss-attribution.md`。关键要求是每个 mapping header 都要重置当前库名,避免把 anonymous PSS 错归到上一个 `.so`。
### 4. hidumper --mem(系统级)
```bash
$HDC shell "hidumper --mem \$(pidof multimodalinput)"
```
输出包含:native heap、.so mmap PSS、total PSS 等分类。
**注意**:hidumper 输出可能很长,通过 hdc shell 可能被截断。可重定向到文件后拉取:
```bash
$HDC shell "hidumper --mem \$(pidof multimodalinput) > /data/local/tmp/hidumper_mem.txt"
$HDC file recv /data/local/tmp/hidumper_mem.txt ./hidumper_mem.txt
```
### 5. RAM 增量归因(必须)
每一个 RAM 增长结论都必须回答“增长来自哪里”。不能只报告 total PSS 或
`multimodalinput` 进程 PSS 差值。至少把增长拆成以下来源并给出证据:
| 来源 | 证据 | 处理要求 |
|------|------|----------|
| 新增/变化的 `.so` 映射 | baseline/current smaps 中的 mapped `.so` 列表和 PSS | 列出每个 `.so` 的基线 PSS、当前 PSS、差值、是否新增加载 |
| libmmi 代码段变化 | `readelf -S` + smaps per-library PSS | 单独列出 libmmi 各库 PSS 增量和对应 section 差异 |
| native heap 增长 | `hidumper --mem` native heap + 代码 diff | 对应到新增容器、对象、缓存、RS 资源或生命周期状态 |
| anonymous/file PSS 增长 | smaps_rollup `Pss_Anon`/`Pss_File` 或 smaps 分类 | 说明是否来自堆、匿名映射、文件映射、测试框架或系统状态 |
| 测试环境/框架噪声 | 新增加载的测试二进制依赖、input-test 框架等 `.so` | 与目标特性分开,不得直接归因给代码变更 |
| 未归因残差 | total delta - 已解释 delta | 单独列出;残差过大时结论只能标记为“不完整归因” |
推荐从 smaps 中按完整路径聚合所有 `.so` PSS,而不是只匹配 `libmmi`;参考脚本见 `references/smaps-pss-attribution.md`。
对 baseline/current 两份结果做 outer join,生成“新增/变化的已加载 `.so`”表:
| 进程 | `.so` 路径 | 基线 PSS(kB) | 当前 PSS(kB) | 差值(kB) | 是否新增加载 | 归因 |
|------|-----------|--------------|--------------|----------|--------------|------|
| multimodalinput | /system/lib/libmmi-server.z.so | X | Y | +/-Z | no | libmmi server 代码段变化 |
| multimodalinput | /system/lib/libxxx.z.so | 0 | Y | +Y | yes | 功能路径触发 / 测试框架 / 环境噪声 |
如果内存增长主要来自加载了新的 `.so`,报告必须列出这些 `.so` 的名称、路径、
PSS 增量和触发原因;不能只写“.so mmap PSS 增长”。如果无法证明某个新加载库由目标
特性触发,应归入“环境/测试框架差异”或“未归因”,不要归入目标特性成本。
### 6. 基线 RAM 获取
**精确方法**:临时恢复 .bak .so → 重启 → 采集 → 恢复修改版 .so
```bash
# 1. 备份当前修改版
$HDC shell "mount -o rw,remount /" # RK3568 根文件系统在 /,不是 /system
$HDC shell "cp /system/lib/libmmi-server.z.so /data/local/tmp/libmmi-server.z.so.new"
# 对 libmmi-server-common.z.so, libmmi-util.z.so 同样操作
# 2. 恢复基线
$HDC shell "cp /data/local/tmp/libmmi-server.z.so.bak /system/lib/libmmi-server.z.so"
# 对每个库同样恢复 .bak
$HDC shell "reboot"
# 等待重启 (~60s)...
# 3. 采集基线 RAM
$HDC shell "cat /proc/\$(pidof multimodalinput)/smaps_rollup"
$HDC shell "cat /proc/\$(pidof multimodalinput)/smaps" > smaps_baseline.txt
$HDC shell "hidumper --mem \$(pidof multimodalinput) > /data/local/tmp/hidumper_baseline.txt"
# 4. 恢复修改版
$HDC shell "mount -o rw,remount /"
$HDC shell "cp /data/local/tmp/libmmi-server.z.so.new /system/lib/libmmi-server.z.so"
# 对每个库同样恢复 .new
$HDC shell "reboot"
```
> **RK3568 mount 注意**:根文件系统挂载在 `/`,不是 `/system`。`mount -o rw,remount /system` 会报 "not in /proc/mounts" 错误。正确命令是 `mount -o rw,remount /`。
**估算方法**(不需要重启):
基于段级分析推算 RAM 变化:
- `.text` + `.rodata` 差值 → 影响 shared clean PSS
- `.bss` 差值 → 影响 anonymous 内存
- `.data` 差值 → 影响 private dirty
当段差值很小(< 1%)且 `.bss` 无变化时,可以直接用估算方法而无需重启。
### 7. 采集前必须执行功能路径
**关键约束**:仅部署 .so 并重启是不够的。如果变更引入了懒分配(lazy allocation)的数据结构,必须在采集前执行完整的功能路径,确保所有运行时状态被实际分配:
```
部署 .so → 重启 → 等待系统稳定 (30s) → 执行功能路径 → 等待稳定 (5s) → 采集
```
功能路径按变更内容定义,不要固定成某一个特性用例。通用模板:
1. 初始化变更依赖的系统状态(如 display、window、device、session、permission)。
2. 调用新增或修改的 public API、IPC、server handler、callback 或事件路径。
3. 触发懒分配和缓存路径(如首次事件、重复事件、边界设备、窗口、显示组)。
4. 覆盖清理路径(解绑、销毁、断连、窗口删除、服务重启前后状态)。
5. 记录实际执行的步骤,并说明哪些受影响库和进程被该路径覆盖。
例如 display group 绑定可以用 `UpdateDisplayInfo`、`UpdateWindowInfo`、
`BindDeviceToDisplayGroupByDisplay` 和事件注入触发;其他特性必须替换成自己的最小完备功能路径。
不执行功能路径的 RAM 采集会**低估**实际内存占用,因为懒分配的容器仍为空。
## 代码理论分析方法(第三支柱)
段级估算只能反映静态映射内存,无法反映堆分配(new/malloc)。对于引入新数据结构的特性变更,**必须**分析代码 diff 中的新增容器和对象。
详细估算规则见 `references/theoretical-ram-estimation.md`。主流程只要求输出:
- 新增容器、对象、缓存和 RS 资源清单。
- 每类对象的典型场景和上界场景估算。
- 分配时机、清理路径和泄漏风险。
- 理论估算与实测 libmmi PSS/native heap/Pss_Anon 的交叉验证结论。
## 输出报告模板
```markdown
## ROM 影响
### 文件级对比
| 库 | 基线(bytes) | 当前(bytes) | 差值 | 比例 |
|----|------------|------------|------|------|
| libmmi-server.z.so | X | Y | +/-Z | +/-N% |
| ... | | | | |
| **合计** | | | | |
### libmmi-server.z.so 段级分析
| 段 | 基线 | 当前 | 差值 |
|----|------|------|------|
| .text | X | Y | +/-Z |
| .rodata | | | |
| .data | | | |
| .bss | | | |
### 影响范围映射
| 库 | 受影响代码路径 | 目标进程 | 是否采集 RAM | 说明 |
|----|--------------|----------|--------------|------|
| libmmi-server.z.so | server handler / event path | multimodalinput | yes/no | |
| libmmi-client.z.so | client API / proxy / NAPI | <client-process-or-test-binary> | yes/no | |
## RAM 影响
### RAM 增长原因归因
#### 进程级增量拆解
| 指标/类别 | 基线(kB) | 当前(kB) | 差值(kB) | 证据来源 | 归因 |
|-----------|----------|----------|----------|----------|------|
| total PSS | X | Y | +/-Z | smaps_rollup / hidumper | 汇总,仅作入口 |
| .so mmap PSS | X | Y | +/-Z | hidumper + smaps `.so` 聚合 | 见“已加载 .so 增量”表 |
| native heap | X | Y | +/-Z | hidumper | 见“heap/anonymous 增量”表 |
| Pss_Anon | X | Y | +/-Z | smaps_rollup | heap/anonymous/运行时状态 |
| Pss_File | X | Y | +/-Z | smaps_rollup | 文件映射或加载库变化 |
#### 已加载 .so 增量
| 进程 | `.so` 路径 | 基线 PSS(kB) | 当前 PSS(kB) | 差值(kB) | 是否新增加载 | 触发路径/归因 |
|------|-----------|--------------|--------------|----------|--------------|---------------|
| multimodalinput | /system/lib/libmmi-server.z.so | X | Y | +/-Z | no | 目标 libmmi 代码变化 |
| <process> | /system/lib/libxxx.z.so | 0 | Y | +Y | yes | 功能路径触发 / 测试框架 / 环境噪声 |
#### libmmi PSS 增量
| 库 | 基线 PSS (kB) | 当前 PSS (kB) | 差值 | section 证据 | 归因 |
GitHubで見る