MMI RAM/ROM Impact Measurement
Measure changes in ROM (binary size) and RAM (runtime memory) usage of the
subsystem before and after code changes.
Prerequisite skill: For infrastructure such as build and deployment and device connectivity, see
ohos-dev-distributed-mmi-device-test-harness
.
When to Use
- You need to quantify the impact of a code change on binary size before submitting a PR
- You need to demonstrate that a refactoring or new feature does not introduce memory bloat
- A review requires RAM/ROM data
Safety and Comparability Gates
Replacing system
files, remounting
read-write, and rebooting a device are destructive test operations. Perform them only after explicit user approval, keep
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 the following conditions are recorded:
- The provenance of the baseline and current image/build or files
- The exact libraries replaced
- Whether the same functional path was executed before both captures
- Whether process-level numbers represent idle-vs-idle, exercised-vs-exercised, or mixed states
If the baseline is idle but the current measurement is post-exercise, do not present the process-level PSS delta as the feature's RAM cost. Use it only as context and rely on library PSS plus code-level theoretical analysis.
Three Pillars of Measurement
This skill requires RAM impact to be measured from three mutually corroborating dimensions:
| Dimension | Method | What It Reflects | Limitations |
|---|
| ROM section-level | | Statically mapped memory (//) | Does not reflect heap allocations |
| RAM measured | smaps + on-device hidumper collection | Actual physical memory used by a process | Affected by collection conditions and build differences |
| RAM theoretical | git diff analysis of new containers + structure-size estimation | Heap allocations and runtime data structures | Manual estimation that depends on correct structure-size analysis |
All three dimensionsmust be completed and cross-validated. Section-level estimation alone misses heap allocations; measurements alone are affected by noise; theoretical analysis alone cannot confirm actual behavior.
Target Libraries and Process Scope
First determine the impact scope from the code changes. Do not assume that only the server process needs to be inspected. At minimum, check whether the changes affect the following libraries:
| Library | Role | RAM Analysis Scope |
|---|
| Main server logic | process |
libmmi-server-common.z.so
| Shared server code | process |
| Utility library | Every target process that loads the library |
| Client API/proxy | The client process that invokes the API; server-process PSS is not a valid substitute |
Rules:
- ROM analysis must cover every affected libmmi , including .
- RAM analysis must follow the actual process that loads each library. is generally not loaded in the server process. If the client API, proxy, NAPI, or test-binary path is affected, select a client process that loads , execute the functional path in that process, and collect its smaps/hidumper data.
- If the changes affect only an internal server path, you may state that the client is not loaded in the server process. However, you must still use the code diff, symbols, or section-level data to confirm that is genuinely unchanged or does not participate in the RAM conclusion.
- The final report must include a “library → process → functional path executed → included in RAM conclusion” mapping to avoid presenting server-process PSS as the total MMI RAM cost.
ROM Measurement Methods
1. File-Level Comparison (Quick)
bash
BUILD_OUT="code/out/rk3568/multimodalinput/input"
ls -la $BUILD_OUT/libmmi-*.z.so
2. Section-Level Comparison (Precise, Recommended)
Use
to analyze the key sections:
bash
readelf -S libmmi-server.z.so | grep -E '\.text|\.rodata|\.data|\.bss'
| Section | Meaning | Impact |
|---|
| Executable code | Directly affects ROM; mapped as shared clean and affects RAM PSS |
| Read-only data, such as string constants | Affects ROM and shared clean PSS |
| Initialized global variables | Affects ROM and private dirty memory |
| Zero-initialized global variables | Does not affect ROM; affects anonymous RAM |
| Read-only after relocation | Affects ROM |
3. Baseline Acquisition Strategy
Option A: Device Files (Recommended)
Before deploying the modified
files, back up the original files:
bash
$HDC shell "cp /system/lib/libmmi-server.z.so /data/local/tmp/libmmi-server.z.so.bak"
# Deploy the new .so...
# Then pull the baseline
$HDC file recv /data/local/tmp/libmmi-server.z.so.bak ./libmmi-server.z.so.bak
Option B: CI Baseline Build
Extract the
files from the same version in the daily CI-built image as the baseline.
RAM Measurement Methods
1. Find the Process PID
bash
$HDC shell "pidof multimodalinput"
PID stability:
is a system service and may be restarted by the watchdog, causing its PID to change. The PID may differ between multiple HDC commands.
Solution: Use command substitution to find the PID and collect the data within a single HDC shell invocation:
bash
$HDC shell "cat /proc/\$(pidof multimodalinput)/smaps_rollup"
2. smaps_rollup (Process-Level Summary)
bash
$HDC shell "cat /proc/\$(pidof multimodalinput)/smaps_rollup"
Key fields:
- Pss: Actual physical memory after shared pages are proportionally allocated; it best reflects real usage
- Rss: Resident physical memory, including the full amount of shared pages
- Size: Virtual address-space size
3. smaps Per-Library PSS (Important)
bash
$HDC shell "cat /proc/\$(pidof multimodalinput)/smaps" > smaps.txt
See
references/smaps-pss-attribution.md
for the parsing script and precautions. A key requirement is to reset the current library name at every mapping header so that anonymous PSS is not incorrectly attributed to the preceding
.
4. hidumper --mem (System-Level)
bash
$HDC shell "hidumper --mem \$(pidof multimodalinput)"
The output includes categories such as native heap,
mmap PSS, and total PSS.
Note: The hidumper output may be very long and can be truncated when passed through HDC shell. Redirect it to a file and then pull the file:
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 Delta Attribution (Mandatory)
Every RAM-growth conclusion must answer “where did the growth come from?” It is not sufficient to report only the total PSS or the delta in the
process PSS. At minimum, break the growth down into the following sources and provide evidence:
| Source | Evidence | Processing Requirement |
|---|
| New or changed mappings | Mapped lists and PSS in the baseline/current smaps | List each 's baseline PSS, current PSS, delta, and whether it was newly loaded |
| Changes to libmmi code sections | + per-library smaps PSS | Separately list the PSS delta of each libmmi library and the corresponding section differences |
| Native heap growth | native heap + code diff | Correlate it with new containers, objects, caches, RS resources, or lifecycle state |
| Anonymous/file PSS growth | smaps_rollup / or smaps categories | Determine whether it comes from the heap, anonymous mappings, file mappings, the test framework, or system state |
| Test environment/framework noise | Newly loaded test-binary dependencies and input-test framework files | Keep them separate from the target feature; do not attribute them directly to the code change |
| Unattributed residual | total delta - explained delta | List separately; if the residual is too large, the conclusion must be marked as “incomplete attribution” |
It is recommended to aggregate PSS for all
files from smaps using their complete paths rather than matching only
. See
references/smaps-pss-attribution.md
for a reference script.
Perform an outer join on the baseline/current results to generate a table of newly loaded or changed
files:
| Process | Path | Baseline PSS (kB) | Current PSS (kB) | Delta (kB) | Newly Loaded | Attribution |
|---|
| multimodalinput | /system/lib/libmmi-server.z.so | X | Y | +/-Z | no | libmmi server code-section changes |
| multimodalinput | /system/lib/libxxx.z.so | 0 | Y | +Y | yes | Functional path trigger / test framework / environment noise |
If memory growth mainly comes from newly loaded
files, the report must list each library's name, path, PSS delta, and trigger. It is not sufficient to state only that “.so mmap PSS increased.” If a newly loaded library cannot be proven to have been triggered by the target feature, classify it as an “environment/test-framework difference” or “unattributed,” not as the target feature's cost.
6. Baseline RAM Acquisition
Precise method: Temporarily restore the
files → reboot → collect data → restore the modified
files
bash
# 1. Back up the current modified files
$HDC shell "mount -o rw,remount /" # The RK3568 root filesystem is mounted at /, not /system
$HDC shell "cp /system/lib/libmmi-server.z.so /data/local/tmp/libmmi-server.z.so.new"
# Do the same for libmmi-server-common.z.so and libmmi-util.z.so
# 2. Restore the baseline
$HDC shell "cp /data/local/tmp/libmmi-server.z.so.bak /system/lib/libmmi-server.z.so"
# Restore the .bak file for each library in the same way
$HDC shell "reboot"
# Wait for the reboot (~60s)...
# 3. Collect baseline RAM data
$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. Restore the modified files
$HDC shell "mount -o rw,remount /"
$HDC shell "cp /data/local/tmp/libmmi-server.z.so.new /system/lib/libmmi-server.z.so"
# Restore the .new file for each library in the same way
$HDC shell "reboot"
RK3568 mount note: The root filesystem is mounted at
, not
. Running
mount -o rw,remount /system
produces a “not in /proc/mounts” error. The correct command is
.
Estimation method (no reboot required):
Estimate RAM changes based on section-level analysis:
- + delta → affects shared clean PSS
- delta → affects anonymous memory
- delta → affects private dirty memory
When the section delta is very small (< 1%) and
is unchanged, the estimation method can be used directly without rebooting.
7. The Functional Path Must Be Executed Before Collection
Critical constraint: Deploying the
files and rebooting is not sufficient. If a change introduces a lazily allocated data structure, the complete functional path must be executed before collection to ensure that all runtime state has actually been allocated:
Deploy .so → Reboot → Wait for system stability (30s) → Execute functional path → Wait for stability (5s) → Collect data
Define the functional path according to the change rather than fixing it to one feature test case. A general template is:
- Initialize the system state required by the change, such as display, window, device, session, and permission state.
- Invoke the new or modified public API, IPC, server handler, callback, or event path.
- Trigger lazy-allocation and cache paths, such as the first event, repeated events, boundary devices, windows, and display groups.
- Cover cleanup paths, including unbinding, destruction, disconnection, window deletion, and state before and after a service restart.
- Record the steps actually executed and explain which affected libraries and processes are covered by the path.
For example, display-group binding can be triggered through
,
,
BindDeviceToDisplayGroupByDisplay
, and event injection. Other features must use their own minimal complete functional path.
Collecting RAM data without executing the functional path willunderestimate actual memory usage because lazily allocated containers will still be empty.
Code-Level Theoretical Analysis Method (Third Pillar)
Section-level estimation can reflect only statically mapped memory and cannot reflect heap allocations (
/
). For feature changes that introduce new data structures, you
must analyze new containers and objects in the code diff.
See
references/theoretical-ram-estimation.md
for detailed estimation rules. The main process only needs to produce the following:
- A list of new containers, objects, caches, and RS resources
- Typical-scenario and upper-bound-scenario estimates for each object type
- Allocation timing, cleanup paths, and leak risks
- A cross-validation conclusion comparing theoretical estimates with measured libmmi PSS, native heap, and Pss_Anon
Output Report Template
markdown
## ROM Impact
### File-Level Comparison
|----|------------|------------|------|------|
| libmmi-server.z.so | X | Y | +/-Z | +/-N% |
| ... | | | | |
| **Total** | | | | |
### Section-Level Analysis of libmmi-server.z.so
|----|------|------|------|
| .text | X | Y | +/-Z |
| .rodata | | | |
| .data | | | |
| .bss | | | |
### Impact Scope Mapping
|----|--------------|----------|--------------|------|
| 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 Impact
### RAM Growth Attribution
#### Process-Level Delta Breakdown
|-----------|----------|----------|----------|----------|------|
| total PSS | X | Y | +/-Z | smaps_rollup / hidumper | Summary only; entry point |
| .so mmap PSS | X | Y | +/-Z | hidumper + aggregated smaps `.so` values | See “Loaded .so Delta” table |
| native heap | X | Y | +/-Z | hidumper | See “Heap/Anonymous Delta” table |
| Pss_Anon | X | Y | +/-Z | smaps_rollup | Heap/anonymous/runtime state |
| Pss_File | X | Y | +/-Z | smaps_rollup | File mappings or changes in loaded libraries |
#### Loaded .so Delta
|------|-----------|--------------|--------------|----------|----------|---------------|
| multimodalinput | /system/lib/libmmi-server.z.so | X | Y | +/-Z | no | Target libmmi code changes |
| <process> | /system/lib/libxxx.z.so | 0 | Y | +Y | yes | Functional path trigger / test framework / environment noise |
#### libmmi PSS Delta
|----|-------------|---------------|------|--------------|------|
| libmmi-server.z.so | X | Y | +/-Z | .text/.rodata/.data/.bss differences | Server code path |
| libmmi-client.z.so | X | Y | +/-Z | .text/.rodata/.data/.bss differences | Client API/proxy path |
#### Heap/Anonymous Delta
|--------|----------|--------------|---------------|----------------|------|
| New map/vector/cache | X KB | native heap / Pss_Anon | File:line number or diff summary | Creation/cleanup function | Target feature cost |
| Test framework objects | X KB | native heap / Pss_Anon | Test entry point or dependency | Test process lifecycle | Environment difference |
#### Unattributed Residual
|----------------|----------------|----------------------|------------|------|
| +X kB | +Y kB | +Z kB | +N kB | If N is too large, mark the conclusion as incomplete and continue collecting smaps/hidumper/functional-path evidence |
### Measured Comparison (libmmi Library PSS)
|----|-------------|----------------|------|------|
| libmmi-server.z.so | X | Y | +/-Z | section/code path evidence |
| ... | | | | |
### Theoretical Analysis
|------|---------|------------------------|
| Unused path | ~X B | ~X B |
| Typical functional path | ~X KB | ~X KB |
| Extreme upper bound | ~X KB/MB | ~X KB/MB |
### Measured vs. Theoretical Comparison
[Explain whether the two are consistent and the reasons for any differences.]
## Conclusion
ROM: +/-X bytes (+/-N%)
RAM: server-process PSS +/-X kB; client-process PSS +/-Y kB (if applicable); theoretical typical scenario ~Z KB
Common Mistakes
| Mistake | Consequence | Fix |
|---|
| Using file size to evaluate RAM | File size includes the ELF header, symbol table, and other parts that are not mapped into memory | Use readelf section-level analysis |
| Ignoring the section | does not consume ROM but affects anonymous RAM | Check separately |
| Using RSS for cross-process comparisons | RSS includes the full amount of shared pages and causes double counting | Use PSS |
| Comparing RAM data from different system versions | Differences in system library versions introduce noise | Ensure that only the target files are replaced and that the system baseline is consistent |
| Inspecting only in the server process | The client is not loaded in the server process, resulting in an incomplete RAM conclusion | Select a client process or test binary that loads the client and collect its data separately |
| Collecting data after reboot without executing the functional path | Lazily allocated data structures are not actually allocated, underestimating RAM | Always execute the complete functional path before collection to trigger all state allocations |
| Performing only section-level estimation without theoretical analysis | Section-level estimation does not reflect heap allocations (/) and misses runtime data structures | Combine it with theoretical memory estimation of new containers in the diff |
| Performing only theoretical analysis without measurement | Theoretical estimates may miss or overestimate actual behavior and cannot confirm actual performance | Cross-validate the three pillars: section-level analysis, measurement, and theoretical analysis |
| Reporting only total PSS or mmap PSS growth | It is impossible to determine whether the growth came from a library, heap objects, or the test environment | Include process-level categories, the PSS delta for every loaded , heap/anonymous growth, and the unattributed residual |
| Observing growth without listing the new files | It is impossible to determine whether the target feature caused the libraries to be loaded | Perform an outer join on the complete baseline/current lists and list newly loaded libraries and PSS changes |
| Attributing files additionally loaded by the test framework to the target feature | This overstates the feature's RAM cost | Classify files that are not proven to be triggered by the target functionality as environment/test-framework differences or unattributed |
| The smaps parser does not reset | Anonymous mapping PSS is incorrectly attributed to the preceding libmmi library, severely overestimating its usage | Reset at every mapping header line |
| The PID changes between multiple HDC commands | Data is read from different processes | Use within a single shell invocation |
mount -o rw,remount /system
| The RK3568 root filesystem is mounted at , resulting in a “not in /proc/mounts” error | Use |
| Ignoring Parcelable/RefBase inheritance | This underestimates the heap allocation size of objects containing a RefCounter | Analyze the complete inheritance chain, including the vtable and RefCounter |
| Ignoring RS SurfaceNode/BufferQueue | This misses the largest individual memory cost, at 50–200 KB per group | Analyze the RS resource creation path and distinguish from actual creation |
| Considering only during theoretical analysis and ignoring heap allocations | This misses heap allocations for long values, vector elements, and map nodes | Analyze the actual memory layout of every field |