diff --git a/Runner/suites/Performance/Memory_Map/Memory_Map.yaml b/Runner/suites/Performance/Memory_Map/Memory_Map.yaml new file mode 100755 index 00000000..addedacf --- /dev/null +++ b/Runner/suites/Performance/Memory_Map/Memory_Map.yaml @@ -0,0 +1,23 @@ +metadata: + name: memory-map + format: "Lava-Test Test Definition 1.0" + description: "Collect memory-map, per-process memory, reserved memory, DMA-BUF, KGSL, zram, slab, vmalloc and VM debug data, and print a final memory component summary without Android procrank/dmabuf_dump." + os: + - linux + scope: + - performance + - memory + +params: + OUT_DIR: "./logs_Memory_Map" + COLLECT_DELAY_SECS: "0" + TOP_PROCESS_COUNT: "20" + MOUNT_DEBUGFS: "1" + VERBOSE: "0" + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Performance/Memory_Map/ + - OUT_DIR="${OUT_DIR}" COLLECT_DELAY_SECS="${COLLECT_DELAY_SECS}" TOP_PROCESS_COUNT="${TOP_PROCESS_COUNT}" MOUNT_DEBUGFS="${MOUNT_DEBUGFS}" VERBOSE="${VERBOSE}" ./run.sh || true + - $REPO_PATH/Runner/utils/send-to-lava.sh Memory_Map.res diff --git a/Runner/suites/Performance/Memory_Map/Memory_Map_README.md b/Runner/suites/Performance/Memory_Map/Memory_Map_README.md new file mode 100644 index 00000000..41b9c2f2 --- /dev/null +++ b/Runner/suites/Performance/Memory_Map/Memory_Map_README.md @@ -0,0 +1,640 @@ +# Memory_Map Performance Test + +`Memory_Map` collects a Yocto/Linux memory snapshot from the target and prints a final memory component report to stdout. It is designed to run directly from the target serial shell or from an automation environment such as LAVA without requiring Ubuntu-only tools like `procrank` or `dmabuf_dump`. + +The test uses only standard Linux interfaces where possible: + +- `/proc/meminfo` +- `/proc/iomem` +- `/proc/*/smaps_rollup` +- `/proc/vmstat`, `/proc/zoneinfo`, `/proc/slabinfo`, `/proc/vmallocinfo` +- `/sys/kernel/debug/*` when debugfs is available +- `/sys/class/kgsl/*` when KGSL is available +- `/sys/block/zram*/mm_stat` when zram is available +- Device tree reserved-memory nodes, when present + +--- + +## Quick start + +From the Memory_Map suite directory on the target: + +```sh +cd /tmp/Runner/suites/Performance/Memory_Map +./run.sh +``` + +A normal run prints progress messages while collection is happening, dumps selected debug summaries to stdout, and ends with a compact memory component table plus PASS/FAIL status. + +Example final stdout table: + +```text +------------------------------------------------------------ +Mem Component (in MB) Qualcomm Technologies, Inc. Robotics RB3gen2 +------------------------------------------------------------ +NHLOS 593.96 +Kernel Static 214.01 +Apps + Framework 776.95 +Free Mem 4559.09 +------------------------------------------------------------ +MemTotal 5336.04 +System RAM 5550.04 +Slab 131.32 +PageTables 7.82 +KernelStack 6.15 +VmallocUsed 51.35 +CMA Used 0.00 +Swap Used 0.00 +------------------------------------------------------------ +``` + +Generated artifacts are stored under: + +```text +./logs_Memory_Map/ +``` + +The run is expected to end with: + +```text +[PASS] ... Memory_Map: PASS +``` + +If the test cannot collect required memory information such as `/proc/meminfo`, it exits FAIL. + +--- + +## Current run behavior + +The current `run.sh` flow performs the following high-level steps: + +1. Creates the output directory, usually `./logs_Memory_Map`. +2. Prints run configuration such as output directory, collection delay, and top process count. +3. Optionally prepares debugfs access. +4. Collects platform, boot, `/proc`, `/sys`, and debugfs memory artifacts. +5. Builds `memory_summary.txt` from `/proc/meminfo`. +6. Scans `/proc/*/smaps_rollup` for process memory accounting. +7. Generates summaries for reserved memory, DMA-BUF, zram, and KGSL. +8. Writes `memory_component_summary.txt`. +9. Prints selected artifacts and the final memory component summary to stdout. +10. Emits PASS/FAIL result. + +The progress messages are intentionally printed before slower steps, especially the process memory scan, so users can see that the test is still active. + +--- + +## Common usage + +### Default run + +```sh +./run.sh +``` + +Use this for normal collection and stdout reporting. + +### Capture stdout to a file + +```sh +./run.sh | tee Memory_Map_stdout.log +``` + +This is useful for local debugging or attaching the run output to a bug report. + +### Inspect generated logs + +```sh +ls -lh logs_Memory_Map +cat logs_Memory_Map/memory_component_summary.txt +cat logs_Memory_Map/memory_summary.txt +cat logs_Memory_Map/process_top_pss.txt +``` + +### Re-run after clearing old logs + +```sh +rm -rf logs_Memory_Map Memory_Map_stdout_*.log +./run.sh +``` + +### Check command-line help + +If the suite wrapper exposes command-line options, use: + +```sh +./run.sh --help +``` + +Typical parameters controlled by the wrapper are: + +- output directory +- collection delay +- top process count +- debugfs mount behavior +- verbose artifact printing + +The library function behind the wrapper is: + +```sh +perf_mem_collect_all +``` + +--- + +## What is collected + +### Platform and boot context + +| File | Purpose | +|---|---| +| `uname.txt` | Kernel and machine information from `uname -a` | +| `date.txt` | UTC timestamp at collection time | +| `proc_version.txt` | Kernel version string from `/proc/version` | +| `cmdline.txt` | Kernel boot command line | +| `cpuinfo.txt` | CPU information | +| `config.txt` | Kernel config from `/proc/config.gz`, if available | +| `uptime.txt` | Current uptime | + +### Core memory snapshots + +| File | Purpose | +|---|---| +| `meminfo.txt` | Raw `/proc/meminfo` | +| `memory_summary.txt` | Parsed memory key/value summary | +| `free.txt` | Output of `free` | +| `vmstat.txt` | Raw `/proc/vmstat` | +| `vmstat_cmd.txt` | Output of `vmstat` | +| `zoneinfo.txt` | Raw `/proc/zoneinfo` | +| `pagetypeinfo.txt` | Raw `/proc/pagetypeinfo` | +| `buddyinfo.txt` | Raw `/proc/buddyinfo` | +| `slabinfo.txt` | Raw `/proc/slabinfo` | +| `vmallocinfo.txt` | Raw `/proc/vmallocinfo` | +| `iomem.txt` | Raw `/proc/iomem`; used for System RAM calculation | +| `modules.txt` | Raw `/proc/modules` | +| `lsmod.txt` | Output of `lsmod`, if available | + +### Process memory + +| File | Purpose | +|---|---| +| `process_smaps_availability.txt` | Counts processes with readable `smaps_rollup` | +| `process_mem.tsv` | Per-process PSS/RSS/swap/thread summary | +| `process_top_pss.txt` | Top processes sorted by PSS | +| `ps_A.txt` | Output of `ps -A` | +| `ps_eT.txt` | Output of `ps -eT` | + +### Reserved memory, DMA-BUF, zram, KGSL, debug + +| File | Purpose | +|---|---| +| `reserved_memory_dt.tsv` | Device tree reserved-memory nodes | +| `memblock_reserved.txt` | Kernel debugfs memblock reserved regions, if available | +| `dma_buf_bufinfo.txt` | Raw DMA-BUF debugfs bufinfo, if available | +| `dmabuf_fd_owners.tsv` | Best-effort DMA-BUF file descriptor owners from `/proc/*/fd` | +| `dmabuf_summary.txt` | DMA-BUF availability and owner summary | +| `zram_summary.tsv` | zram accounting from `/sys/block/zram*/mm_stat` | +| `kgsl_summary.txt` | KGSL memory/debug information, if available | +| `ion_heap_system.txt` | Legacy ION heap debugfs information, if available | +| `tracing_buffer_total_size_kb.txt` | ftrace buffer size, if available | +| `mem_bank_state.txt` | Memory bank online/offline states | +| `dmesg.txt` | Kernel log captured during the run | +| `manifest.tsv` | Artifact name, size, and path index | + +--- + +## Final report files + +### `memory_component_summary.txt` + +This is the main human-readable report. It is also printed to stdout at the end of the run. + +It contains: + +- NHLOS +- Kernel Static +- Apps + Framework +- Free Mem +- MemTotal +- System RAM +- Slab +- PageTables +- KernelStack +- VmallocUsed +- CMA Used +- Swap Used + +### `memory_summary.txt` + +This is a parsed key/value summary from `/proc/meminfo`. Example keys: + +```text +MemTotal_kB=5464100 +MemFree_kB=3824288 +MemAvailable_kB=4668504 +UsedApprox_kB=795596 +Slab_kB=134476 +PageTables_kB=8008 +KernelStack_kB=6296 +VmallocUsed_kB=52584 +CmaUsed_kB=0 +SwapUsed_kB=0 +``` + +### `manifest.tsv` + +This file lists every generated artifact, its byte size, and its path. It is useful when checking whether a debug file was collected or empty. + +Example: + +```text +meminfo 1448 ./logs_Memory_Map/meminfo.txt +iomem 10916 ./logs_Memory_Map/iomem.txt +memory_component_summary 848 ./logs_Memory_Map/memory_component_summary.txt +``` + +--- + +## Memory formulas used in the report + +All final report values are printed in MB. Raw Linux counters are normally collected in kB and converted as: + +```text +MB = kB / 1024 +``` + +### 1. Total Linux memory, shown as `System RAM` + +`System RAM` is calculated from `/proc/iomem` by summing every address range named `System RAM`. + +Formula per range: + +```text +range_size_bytes = end_address - start_address + 1 +range_size_kB = range_size_bytes / 1024 +``` + +Total: + +```text +System RAM = sum(all /proc/iomem "System RAM" ranges) +``` + +Example: + +```text +83600000-839fffff : System RAM +``` + +```text +0x839fffff - 0x83600000 + 1 = 4194304 bytes = 4096 kB = 4 MB +``` + +### 2. Installed Total RAM + +For the NHLOS calculation, the test resolves installed RAM by rounding the observed Linux memory up to a standard DRAM bucket. This is needed because Linux-visible memory is lower than the physical installed DRAM. + +For example, when the platform has approximately 6 GB installed RAM: + +```text +Installed Total RAM = 6144 MB +``` + +The installed total may differ by platform and memory configuration. + +### 3. NHLOS / Non-Linux memory + +NHLOS is calculated using the documented memory-map method: + +```text +NHLOS = Installed Total RAM - System RAM +``` + +Where: + +- `Installed Total RAM` is the physical DRAM size used for the platform. +- `System RAM` is the total Linux memory from `/proc/iomem`. + +Example from the current run: + +```text +Installed Total RAM = 6144.00 MB +System RAM = 5550.04 MB +NHLOS = 6144.00 - 5550.04 = 593.96 MB +``` + +NHLOS can vary between builds because the Linux System RAM ranges in `/proc/iomem` can change with DTB, kernel, firmware, bootloader, or memory reservation changes. + +### 4. Kernel Static + +Kernel Static is calculated using: + +```text +Kernel Static = System RAM - MemTotal +``` + +Where: + +- `System RAM` comes from `/proc/iomem`. +- `MemTotal` comes from `/proc/meminfo`. + +Example from the current run: + +```text +System RAM = 5550.04 MB +MemTotal = 5336.04 MB +Kernel Static = 5550.04 - 5336.04 = 214.00 MB +``` + +This represents memory visible to Linux as System RAM but not available as normal allocatable memory in `MemTotal`. + +### 5. Apps + Framework + +Apps + Framework is calculated as the approximate used Linux memory: + +```text +Apps + Framework = MemTotal - MemAvailable +``` + +The same value is written in `memory_summary.txt` as: + +```text +UsedApprox_kB = MemTotal_kB - MemAvailable_kB +``` + +Example: + +```text +MemTotal = 5336.04 MB +MemAvailable = 4559.09 MB +Apps + Framework = 776.95 MB +``` + +This is a practical after-boot used-memory value. It includes userspace and framework memory plus other used Linux memory that is not currently available. + +### 6. Free Mem + +Free Mem uses `MemAvailable` rather than `MemFree`: + +```text +Free Mem = MemAvailable +``` + +`MemAvailable` is preferred because it estimates memory available for new workloads without swapping, including reclaimable cache. + +### 7. Slab + +```text +Slab = Slab_kB from /proc/meminfo +``` + +### 8. PageTables + +```text +PageTables = PageTables_kB from /proc/meminfo +``` + +### 9. KernelStack + +```text +KernelStack = KernelStack_kB from /proc/meminfo +``` + +### 10. VmallocUsed + +```text +VmallocUsed = VmallocUsed_kB from /proc/meminfo +``` + +### 11. CMA Used + +```text +CMA Used = CmaTotal - CmaFree +``` + +If the kernel does not expose CMA counters, this may print `0.00`. + +### 12. Swap Used + +```text +Swap Used = SwapTotal - SwapFree +``` + +--- + +## Why NHLOS may not match older internal snapshots + +NHLOS is calculated from the live board memory map. If `/proc/iomem` changes, NHLOS changes. + +For example: + +```text +NHLOS = Installed Total RAM - System RAM +``` + +If installed RAM is 6144 MB: + +```text +System RAM = 5550.04 MB -> NHLOS = 593.96 MB +System RAM = 5520.12 MB -> NHLOS = 623.88 MB +``` + +So a difference of about 30 MB in `/proc/iomem` System RAM directly creates a 30 MB difference in NHLOS. + +Common causes: + +- Different DTB or reserved-memory layout +- Different kernel configuration +- Firmware or bootloader changes +- Different memory carveouts +- Different platform SKU or RAM population +- Updated boot image or board support package + +To compare against an internal report, compare these files first: + +```sh +cat logs_Memory_Map/iomem.txt | grep "System RAM" +cat logs_Memory_Map/meminfo.txt | grep MemTotal +cat logs_Memory_Map/memory_component_summary.txt +``` + +--- + +## Debugging guide + +### NHLOS looks different from expected + +Check: + +```sh +grep "System RAM" logs_Memory_Map/iomem.txt +cat logs_Memory_Map/memory_component_summary.txt +``` + +Then calculate manually: + +```text +NHLOS = Installed Total RAM - summed System RAM from /proc/iomem +``` + +### Kernel Static is zero or wrong + +Kernel Static depends on `/proc/iomem` parsing. If `System RAM` is `0.00`, inspect: + +```sh +cat logs_Memory_Map/iomem.txt +grep "System RAM" logs_Memory_Map/iomem.txt +``` + +Expected formula: + +```text +Kernel Static = System RAM - MemTotal +``` + +If `/proc/iomem` is missing, hidden, unreadable, or format-changed, Kernel Static cannot be calculated correctly. + +### Apps + Framework is higher than top process sum + +This is expected. The final report uses: + +```text +Apps + Framework = MemTotal - MemAvailable +``` + +The top process list is only a debug view of process PSS and does not necessarily equal the full used-memory approximation. + +### DMA-BUF summary is empty + +Check whether debugfs is mounted and whether the kernel exposes DMA-BUF bufinfo: + +```sh +mount | grep debugfs +ls -l /sys/kernel/debug/dma_buf/bufinfo +cat logs_Memory_Map/dmabuf_summary.txt +``` + +Even if `dma_buf_bufinfo.txt` is unavailable, the test still tries to collect best-effort DMA-BUF FD owners from `/proc/*/fd`. + +### KGSL summary is unavailable + +KGSL is platform and kernel dependent. Check: + +```sh +ls -l /sys/class/kgsl/kgsl +cat logs_Memory_Map/kgsl_summary.txt +``` + +If KGSL is not present, the test records `kgsl_available=no` and continues. + +--- + +## PASS / FAIL behavior + +The test is primarily a collection and reporting test. + +Current expected behavior: + +- PASS when required artifacts such as `/proc/meminfo` are collected and summary generation completes. +- FAIL when required memory collection fails, for example `meminfo.txt` is missing or empty. +- Optional artifacts can be missing without failing the test. + +Examples of optional artifacts: + +- debugfs memblock reserved data +- DMA-BUF debugfs data +- ION heap data +- KGSL data +- zram data + +--- + +## Using results in automation + +For automation systems, the most useful outputs are: + +```text +stdout final memory component summary +logs_Memory_Map/memory_component_summary.txt +logs_Memory_Map/memory_summary.txt +logs_Memory_Map/manifest.tsv +logs_Memory_Map/process_top_pss.txt +logs_Memory_Map/iomem.txt +``` + +Recommended parsing target: + +```sh +cat logs_Memory_Map/memory_component_summary.txt +``` + +Recommended debug bundle: + +```sh +tar czf Memory_Map_logs.tgz logs_Memory_Map Memory_Map_stdout_*.log 2>/dev/null || \ +tar czf Memory_Map_logs.tgz logs_Memory_Map +``` + +--- + +## Limitations + +- This test does not require `procrank` or `dmabuf_dump`. +- Per-process memory uses `/proc/*/smaps_rollup`; if access is restricted, process accounting may be partial. +- DMA-BUF, KGSL, ION, and memblock details depend on debugfs/sysfs availability. +- NHLOS is calculated from the current live memory map, so values can differ between builds and DTBs. +- The final report is intended for after-boot snapshot comparison, not continuous memory leak tracking. + +--- + +## Suggested comparison workflow + +Run the test on the baseline build and the candidate build: + +```sh +./run.sh +cp -r logs_Memory_Map logs_Memory_Map_baseline + +# Boot candidate build, then: +./run.sh +cp -r logs_Memory_Map logs_Memory_Map_candidate +``` + +Compare the final reports: + +```sh +diff -u \ + logs_Memory_Map_baseline/memory_component_summary.txt \ + logs_Memory_Map_candidate/memory_component_summary.txt +``` + +Compare raw memory maps if NHLOS or Kernel Static changed: + +```sh +diff -u \ + logs_Memory_Map_baseline/iomem.txt \ + logs_Memory_Map_candidate/iomem.txt +``` + +Compare process memory if Apps + Framework changed: + +```sh +diff -u \ + logs_Memory_Map_baseline/process_top_pss.txt \ + logs_Memory_Map_candidate/process_top_pss.txt +``` + +--- + +## Summary + +`Memory_Map` provides a lightweight target-side memory report for Qualcomm Linux platforms. It collects raw evidence, prints a concise debug-friendly stdout report, and uses documented memory formulas: + +```text +NHLOS = Installed Total RAM - System RAM from /proc/iomem +Kernel Static = System RAM from /proc/iomem - MemTotal from /proc/meminfo +Apps+Framework = MemTotal - MemAvailable +Free Mem = MemAvailable +``` + +These formulas make the report easy to validate from the generated logs and easy to compare across builds. + diff --git a/Runner/suites/Performance/Memory_Map/run.sh b/Runner/suites/Performance/Memory_Map/run.sh new file mode 100755 index 00000000..32ca3e6c --- /dev/null +++ b/Runner/suites/Performance/Memory_Map/run.sh @@ -0,0 +1,192 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause + +TESTNAME="Memory_Map" +RES_FILE="./${TESTNAME}.res" + +SCRIPT_DIR="$( + cd "$(dirname "$0")" || exit 1 + pwd +)" + +OUT_DIR="${OUT_DIR:-./logs_${TESTNAME}}" +COLLECT_DELAY_SECS="${COLLECT_DELAY_SECS:-0}" +TOP_PROCESS_COUNT="${TOP_PROCESS_COUNT:-20}" +MOUNT_DEBUGFS="${MOUNT_DEBUGFS:-1}" +VERBOSE="${VERBOSE:-0}" + +usage() { + cat <&2 + exit 0 + ;; +esac + +INIT_ENV="" +SEARCH="$SCRIPT_DIR" + +while [ "$SEARCH" != "/" ]; do + if [ -f "$SEARCH/init_env" ]; then + INIT_ENV="$SEARCH/init_env" + break + fi + SEARCH="$(dirname "$SEARCH")" +done + +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] Could not find init_env starting from $SCRIPT_DIR" >&2 + echo "$TESTNAME FAIL" >"$RES_FILE" + exit 1 +fi + +# shellcheck disable=SC1090 +. "$INIT_ENV" + +# shellcheck disable=SC1091 +. "$TOOLS/functestlib.sh" + +# shellcheck disable=SC1091 +. "$TOOLS/lib_performance.sh" + +while [ "$#" -gt 0 ]; do + case "$1" in + --out) + if [ "$#" -lt 2 ]; then + log_fail "--out requires a directory" + usage >&2 + echo "$TESTNAME FAIL" >"$RES_FILE" + exit 1 + fi + shift + if [ -z "${1:-}" ] || [ "${1#-}" != "$1" ]; then + log_fail "--out requires a directory" + usage >&2 + echo "$TESTNAME FAIL" >"$RES_FILE" + exit 1 + fi + OUT_DIR="$1" + ;; + --delay) + if [ "$#" -lt 2 ]; then + log_fail "--delay requires seconds" + usage >&2 + echo "$TESTNAME FAIL" >"$RES_FILE" + exit 1 + fi + shift + if [ -z "${1:-}" ] || [ "${1#-}" != "$1" ]; then + log_fail "--delay requires seconds" + usage >&2 + echo "$TESTNAME FAIL" >"$RES_FILE" + exit 1 + fi + COLLECT_DELAY_SECS="$1" + ;; + --top-process-count) + if [ "$#" -lt 2 ]; then + log_fail "--top-process-count requires a number" + usage >&2 + echo "$TESTNAME FAIL" >"$RES_FILE" + exit 1 + fi + shift + if [ -z "${1:-}" ] || [ "${1#-}" != "$1" ]; then + log_fail "--top-process-count requires a number" + usage >&2 + echo "$TESTNAME FAIL" >"$RES_FILE" + exit 1 + fi + TOP_PROCESS_COUNT="$1" + ;; + --no-debugfs-mount) + MOUNT_DEBUGFS=0 + ;; + --verbose) + VERBOSE=1 + ;; + -h|--help) + usage >&2 + exit 0 + ;; + *) + log_warn "Unknown option: $1" + usage >&2 + echo "$TESTNAME FAIL" >"$RES_FILE" + exit 1 + ;; + esac + shift +done + +case "$COLLECT_DELAY_SECS" in + ''|*[!0-9]*) + log_warn "Invalid COLLECT_DELAY_SECS='$COLLECT_DELAY_SECS'; using 0" + COLLECT_DELAY_SECS=0 + ;; +esac + +case "$TOP_PROCESS_COUNT" in + ''|*[!0-9]*) + log_warn "Invalid TOP_PROCESS_COUNT='$TOP_PROCESS_COUNT'; using 20" + TOP_PROCESS_COUNT=20 + ;; +esac + +case "$MOUNT_DEBUGFS" in + 0|1) + ;; + *) + log_warn "Invalid MOUNT_DEBUGFS='$MOUNT_DEBUGFS'; using 1" + MOUNT_DEBUGFS=1 + ;; +esac + +case "$VERBOSE" in + 0|1) + ;; + *) + log_warn "Invalid VERBOSE='$VERBOSE'; using 0" + VERBOSE=0 + ;; +esac + +log_info "-----------------------------------------------------------------------------------------" +log_info "-------------------Starting ${TESTNAME} Testcase----------------------------" + +check_dependencies cat awk sed sort head grep wc tr mkdir rm dirname basename || { + log_skip "$TESTNAME SKIP - basic dependencies missing" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +} + +if perf_mem_collect_all "$OUT_DIR" "$COLLECT_DELAY_SECS" "$TOP_PROCESS_COUNT" "$MOUNT_DEBUGFS" "$VERBOSE"; then + log_pass "$TESTNAME: PASS" + echo "$TESTNAME PASS" >"$RES_FILE" + exit 0 +fi + +log_fail "$TESTNAME: FAIL" +echo "$TESTNAME FAIL" >"$RES_FILE" +exit 0 diff --git a/Runner/utils/lib_performance.sh b/Runner/utils/lib_performance.sh index c3c66840..c601ca98 100755 --- a/Runner/utils/lib_performance.sh +++ b/Runner/utils/lib_performance.sh @@ -4127,3 +4127,1577 @@ dump_debug_file() { log_info "===== end ${title} =====" } + +# --------------------------------------------------------------------------- +# Memory map collection helpers +# --------------------------------------------------------------------------- + +perf_mem_hex_bytes_to_dec() { + hex="$1" + + [ -n "$hex" ] || { + printf '\n' + return 0 + } + + awk -v hex="$hex" ' + function hexval(c) { + c = tolower(c) + if (c >= "0" && c <= "9") return c + 0 + if (c == "a") return 10 + if (c == "b") return 11 + if (c == "c") return 12 + if (c == "d") return 13 + if (c == "e") return 14 + if (c == "f") return 15 + return 0 + } + + BEGIN { + total = 0 + for (i = 1; i <= length(hex); i++) { + total = (total * 16) + hexval(substr(hex, i, 1)) + } + printf "%.0f\n", total + } + ' +} + +perf_mem_dt_u32_prop() { + prop="$1" + + [ -r "$prop" ] || { + printf '\n' + return 0 + } + + hex="$( + od -An -tx1 -N 4 -v "$prop" 2>/dev/null \ + | tr -d ' \n' + )" + + perf_mem_hex_bytes_to_dec "$hex" +} + +perf_mem_dt_cells_to_bytes() { + file_path="$1" + start_cell="$2" + cell_count="$3" + + [ -r "$file_path" ] || { + printf '\n' + return 0 + } + + byte_skip=$((start_cell * 4)) + byte_count=$((cell_count * 4)) + + hex="$( + dd if="$file_path" bs=1 skip="$byte_skip" count="$byte_count" 2>/dev/null \ + | od -An -tx1 -v 2>/dev/null \ + | tr -d ' \n' + )" + + perf_mem_hex_bytes_to_dec "$hex" +} + +perf_mem_dt_reg_total_size_kb() { + reg_file="$1" + address_cells="$2" + size_cells="$3" + + [ -r "$reg_file" ] || { + printf '\n' + return 0 + } + + case "$address_cells" in + ''|*[!0-9]*) address_cells=2 ;; + esac + + case "$size_cells" in + ''|*[!0-9]*) size_cells=2 ;; + esac + + tuple_cells=$((address_cells + size_cells)) + total_bytes="$(wc -c <"$reg_file" 2>/dev/null || echo 0)" + total_cells=$((total_bytes / 4)) + + if [ "$tuple_cells" -le 0 ] || [ "$total_cells" -le 0 ]; then + printf '\n' + return 0 + fi + + total="0" + cell_index=0 + + while [ "$cell_index" -lt "$total_cells" ]; do + size_start=$((cell_index + address_cells)) + size_bytes="$(perf_mem_dt_cells_to_bytes "$reg_file" "$size_start" "$size_cells")" + + if [ -n "$size_bytes" ]; then + total="$( + awk -v a="$total" -v b="$size_bytes" ' + BEGIN { printf "%.0f\n", a + b } + ' + )" + fi + + cell_index=$((cell_index + tuple_cells)) + done + + awk -v bytes="$total" ' + BEGIN { printf "%.0f\n", bytes / 1024 } + ' +} + +perf_mem_dt_size_prop_total_kb() { + prop="$1" + size_cells="$2" + + [ -r "$prop" ] || { + printf '\n' + return 0 + } + + case "$size_cells" in + ''|*[!0-9]*) size_cells=2 ;; + esac + + hex="$( + od -An -tx1 -v "$prop" 2>/dev/null \ + | tr -d ' \n' + )" + + [ -n "$hex" ] || { + printf '\n' + return 0 + } + + awk -v hex="$hex" -v size_cells="$size_cells" ' + function hexval(c) { + c = tolower(c) + if (c >= "0" && c <= "9") return c + 0 + if (c == "a") return 10 + if (c == "b") return 11 + if (c == "c") return 12 + if (c == "d") return 13 + if (c == "e") return 14 + if (c == "f") return 15 + return 0 + } + + function hex2dec(s, i, n, v) { + n = 0 + for (i = 1; i <= length(s); i++) { + v = hexval(substr(s, i, 1)) + n = (n * 16) + v + } + return n + } + + BEGIN { + chars = size_cells * 8 + if (length(hex) < chars) { + exit + } + size_hex = substr(hex, 1, chars) + size_bytes = hex2dec(size_hex) + printf "%.0f\n", size_bytes / 1024 + } + ' +} + +perf_mem_total_physical_kb_from_dt_memory() { + base="" + + if [ -d /sys/firmware/devicetree/base ]; then + base="/sys/firmware/devicetree/base" + elif [ -d /proc/device-tree ]; then + base="/proc/device-tree" + fi + + [ -n "$base" ] || { + printf '\n' + return 0 + } + + address_cells="$(perf_mem_dt_u32_prop "$base/#address-cells")" + size_cells="$(perf_mem_dt_u32_prop "$base/#size-cells")" + + [ -n "$address_cells" ] || address_cells=2 + [ -n "$size_cells" ] || size_cells=2 + + total_kb="0" + + for mem_node in "$base"/memory "$base"/memory@*; do + [ -d "$mem_node" ] || continue + [ -r "$mem_node/reg" ] || continue + + node_kb="$(perf_mem_dt_reg_total_size_kb "$mem_node/reg" "$address_cells" "$size_cells")" + + if [ -n "$node_kb" ]; then + total_kb="$( + awk -v a="$total_kb" -v b="$node_kb" ' + BEGIN { printf "%.0f\n", a + b } + ' + )" + fi + done + + if [ "$total_kb" = "0" ]; then + printf '\n' + else + printf '%s\n' "$total_kb" + fi +} + +perf_mem_reserved_nhlos_kb_from_dt_mode() { + out_dir="$1" + mode="$2" + base="" + + case "$mode" in + all) + detail_file="$out_dir/nhlos_reserved_memory_dt_all.tsv" + ;; + non_reusable) + detail_file="$out_dir/nhlos_reserved_memory_dt_non_reusable.tsv" + ;; + *) + detail_file="$out_dir/nhlos_reserved_memory_dt_all.tsv" + mode="all" + ;; + esac + + if [ -d /sys/firmware/devicetree/base/reserved-memory ]; then + base="/sys/firmware/devicetree/base/reserved-memory" + elif [ -d /proc/device-tree/reserved-memory ]; then + base="/proc/device-tree/reserved-memory" + fi + + { + printf 'node\tname\tsource\tsize_kb\tsize_mb\tno_map\treusable\tlinux_cma_default\tincluded\n' + } >"$detail_file" + + [ -n "$base" ] || { + printf '\n' + return 0 + } + + address_cells="$(perf_mem_dt_u32_prop "$base/#address-cells")" + size_cells="$(perf_mem_dt_u32_prop "$base/#size-cells")" + + [ -n "$address_cells" ] || address_cells=2 + [ -n "$size_cells" ] || size_cells=2 + + total_kb="0" + + for node in "$base"/*; do + [ -d "$node" ] || continue + + node_base="$(basename "$node")" + + if [ -r "$node/name" ]; then + node_name="$(tr -d '\0' <"$node/name" 2>/dev/null)" + else + node_name="$node_base" + fi + + if [ -e "$node/no-map" ]; then + no_map="yes" + else + no_map="no" + fi + + if [ -e "$node/reusable" ]; then + reusable="yes" + else + reusable="no" + fi + + if [ -e "$node/linux,cma-default" ]; then + linux_cma_default="yes" + else + linux_cma_default="no" + fi + + size_kb="" + source="" + + if [ -r "$node/reg" ]; then + size_kb="$(perf_mem_dt_reg_total_size_kb "$node/reg" "$address_cells" "$size_cells")" + source="reg" + elif [ -r "$node/size" ]; then + size_kb="$(perf_mem_dt_size_prop_total_kb "$node/size" "$size_cells")" + source="size" + fi + + [ -n "$size_kb" ] || continue + [ "$size_kb" != "0" ] || continue + + included="yes" + if [ "$mode" = "non_reusable" ]; then + if [ "$reusable" = "yes" ] || [ "$linux_cma_default" = "yes" ]; then + included="no" + fi + fi + + size_mb="$( + awk -v kb="$size_kb" ' + BEGIN { printf "%.2f\n", kb / 1024 } + ' + )" + + if [ "$included" = "yes" ]; then + total_kb="$( + awk -v a="$total_kb" -v b="$size_kb" ' + BEGIN { printf "%.0f\n", a + b } + ' + )" + fi + + printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \ + "$node_base" "$node_name" "$source" "$size_kb" "$size_mb" \ + "$no_map" "$reusable" "$linux_cma_default" "$included" >>"$detail_file" + done + + if [ "$total_kb" = "0" ]; then + printf '\n' + else + printf '%s\n' "$total_kb" + fi +} + +perf_mem_reserved_nhlos_kb_from_dt() { + perf_mem_reserved_nhlos_kb_from_dt_mode "$1" "all" +} + +perf_mem_reserved_nhlos_non_reusable_kb_from_dt() { + perf_mem_reserved_nhlos_kb_from_dt_mode "$1" "non_reusable" +} + +perf_mem_dt_gap_nhlos_kb() { + memtotal_kb="$1" + total_physical_kb="$(perf_mem_total_physical_kb_from_dt_memory)" + + if [ -z "$total_physical_kb" ] || [ -z "$memtotal_kb" ]; then + printf '\n' + return 0 + fi + + awk -v total="$total_physical_kb" -v memtotal="$memtotal_kb" ' + BEGIN { + v = total - memtotal + if (v < 0) v = 0 + printf "%.0f\n", v + } + ' +} + +perf_mem_iomem_reserved_regions_all_kb() { + iomem_file="$1" + detail_file="$2" + + [ -r "$iomem_file" ] || { + printf '\n' + return 0 + } + + awk -v detail_file="$detail_file" ' + function hexval(c) { + c = tolower(c) + if (c >= "0" && c <= "9") return c + 0 + if (c == "a") return 10 + if (c == "b") return 11 + if (c == "c") return 12 + if (c == "d") return 13 + if (c == "e") return 14 + if (c == "f") return 15 + return 0 + } + + function hex2dec(s, i, n, v) { + gsub(/^0x/, "", s) + n = 0 + for (i = 1; i <= length(s); i++) { + v = hexval(substr(s, i, 1)) + n = (n * 16) + v + } + return n + } + + function add_top_range(s, e, name, is_system) { + if (e < s) return + + all_count++ + all_starts[all_count] = s + all_ends[all_count] = e + all_names[all_count] = name + all_system[all_count] = is_system + + /* + * DDR span is inferred from top-level System RAM plus common Qualcomm + * firmware/reserved labels. The final NHLOS is span - System RAM, so + * unnamed holes/gaps are counted too. + */ + lname = tolower(name) + if (is_system == 1 || lname ~ /(reserved|nomap|no-map|modem|mpss|adsp|cdsp|gpdsp|wpss|slpi|spss|tz|hyp|pil|rmtfs|venus|vpu|video|camera|cam|ipa|gpu|gmu|qsee|secure|xbl|aop|smem|cpucp|memory)/) { + if (have_span == 0) { + span_start = s + span_end = e + have_span = 1 + } else { + if (s < span_start) span_start = s + if (e > span_end) span_end = e + } + } + } + + BEGIN { + printf "item\trange\tname\tstart_dec\tend_dec\tsize_kb\tsize_mb\tincluded\n" > detail_file + have_span = 0 + all_count = 0 + } + + /* + * Use only top-level /proc/iomem ranges. Child lines are indented and + * would double count Kernel code/data under System RAM. + */ + /^[0-9a-fA-F]+-[0-9a-fA-F]+[[:space:]]*:/ { + split($1, range, "-") + start = hex2dec(range[1]) + end = hex2dec(range[2]) + + name = $0 + sub(/^[0-9a-fA-F]+-[0-9a-fA-F]+[[:space:]]*:[[:space:]]*/, "", name) + + is_system = 0 + if (tolower(name) == "system ram") { + is_system = 1 + } + + add_top_range(start, end, name, is_system) + } + + END { + if (all_count == 0 || have_span == 0) { + exit + } + + sys_count = 0 + + for (i = 1; i <= all_count; i++) { + if (all_system[i] != 1) { + continue + } + + if (all_ends[i] < span_start || all_starts[i] > span_end) { + continue + } + + sys_count++ + sys_starts[sys_count] = all_starts[i] + sys_ends[sys_count] = all_ends[i] + } + + if (sys_count == 0) { + exit + } + + for (i = 1; i <= sys_count; i++) { + for (j = i + 1; j <= sys_count; j++) { + if (sys_starts[j] < sys_starts[i]) { + ts = sys_starts[i] + te = sys_ends[i] + sys_starts[i] = sys_starts[j] + sys_ends[i] = sys_ends[j] + sys_starts[j] = ts + sys_ends[j] = te + } + } + } + + merged_count = 0 + + for (i = 1; i <= sys_count; i++) { + if (merged_count == 0) { + merged_count = 1 + mstarts[merged_count] = sys_starts[i] + mends[merged_count] = sys_ends[i] + continue + } + + if (sys_starts[i] <= mends[merged_count] + 1) { + if (sys_ends[i] > mends[merged_count]) { + mends[merged_count] = sys_ends[i] + } + } else { + merged_count++ + mstarts[merged_count] = sys_starts[i] + mends[merged_count] = sys_ends[i] + } + } + + system_bytes = 0 + for (i = 1; i <= merged_count; i++) { + system_bytes += mends[i] - mstarts[i] + 1 + } + + span_bytes = span_end - span_start + 1 + nhlos_bytes = span_bytes - system_bytes + + if (nhlos_bytes < 0) { + nhlos_bytes = 0 + } + + printf "DDR_SPAN\t%.0f-%.0f\tDDR span inferred from /proc/iomem\t%.0f\t%.0f\t%.0f\t%.2f\tyes\n", \ + span_start, span_end, span_start, span_end, span_bytes / 1024, span_bytes / 1024 / 1024 >> detail_file + + printf "SYSTEM_RAM_TOTAL\t-\tMerged top-level System RAM\t0\t0\t%.0f\t%.2f\tno\n", \ + system_bytes / 1024, system_bytes / 1024 / 1024 >> detail_file + + printf "NHLOS_ALL\t-\tDDR span minus System RAM, includes reserved regions and holes\t0\t0\t%.0f\t%.2f\tyes\n", \ + nhlos_bytes / 1024, nhlos_bytes / 1024 / 1024 >> detail_file + + for (i = 1; i <= all_count; i++) { + if (all_ends[i] < span_start || all_starts[i] > span_end) { + continue + } + + size_bytes = all_ends[i] - all_starts[i] + 1 + included = "debug" + + if (all_system[i] == 1) { + included = "system_ram" + } else { + included = "non_system_or_reserved" + } + + printf "TOP_LEVEL_RANGE\t%.0f-%.0f\t%s\t%.0f\t%.0f\t%.0f\t%.2f\t%s\n", \ + all_starts[i], all_ends[i], all_names[i], all_starts[i], all_ends[i], \ + size_bytes / 1024, size_bytes / 1024 / 1024, included >> detail_file + } + + printf "%.0f\n", nhlos_bytes / 1024 + } + ' "$iomem_file" 2>/dev/null +} + +perf_mem_nhlos_kb_from_iomem_patterns() { + iomem_file="$1" + + [ -r "$iomem_file" ] || { + printf '\n' + return 0 + } + + awk ' + function hexval(c) { + c = tolower(c) + if (c >= "0" && c <= "9") return c + 0 + if (c == "a") return 10 + if (c == "b") return 11 + if (c == "c") return 12 + if (c == "d") return 13 + if (c == "e") return 14 + if (c == "f") return 15 + return 0 + } + + function hex2dec(s, i, n, v) { + gsub(/^0x/, "", s) + n = 0 + for (i = 1; i <= length(s); i++) { + v = hexval(substr(s, i, 1)) + n = (n * 16) + v + } + return n + } + + BEGIN { + pattern = "modem|mpss|adsp|cdsp|gpdsp|wpss|slpi|spss|tz|hyp|pil|rmtfs|venus|vpu|video|camera|cam|ipa|gpu|gmu|qsee|secure" + } + + tolower($0) ~ pattern && $1 ~ /^[0-9a-fA-F]+-[0-9a-fA-F]+$/ { + split($1, range, "-") + start = hex2dec(range[1]) + end = hex2dec(range[2]) + + if (end >= start) { + total += end - start + 1 + } + } + + END { + if (total > 0) { + printf "%.0f\n", total / 1024 + } + } + ' "$iomem_file" 2>/dev/null +} + +perf_mem_roundup_installed_kb() { + memtotal_kb="$1" + + case "$memtotal_kb" in + ''|*[!0-9.]*) + printf '\n' + return 0 + ;; + esac + + awk -v kb="$memtotal_kb" ' + BEGIN { + split("512 1024 1536 2048 3072 4096 6144 8192 12288 16384 24576 32768 49152 65536", sizes, " ") + mb = kb / 1024 + + for (i = 1; i <= length(sizes); i++) { + if (mb <= sizes[i]) { + printf "%.0f\n", sizes[i] * 1024 + exit + } + } + + printf "%.0f\n", mb * 1024 + } + ' +} + +perf_mem_collect_file() { + src="$1" + dst="$2" + + if [ -r "$src" ]; then + cat "$src" >"$dst" 2>/dev/null || true + else + printf 'missing or unreadable: %s\n' "$src" >"$dst" + fi +} + +perf_mem_collect_cmd() { + dst="$1" + shift + + "$@" >"$dst" 2>&1 || true +} + +perf_mem_append_manifest() { + out_dir="$1" + name="$2" + path="$3" + + if [ -e "$path" ]; then + size="$(wc -c <"$path" 2>/dev/null || echo 0)" + printf '%s\t%s\t%s\n' "$name" "$size" "$path" >>"$out_dir/manifest.tsv" + fi +} + +perf_mem_try_mount_debugfs() { + mount_debugfs="$1" + + [ "$mount_debugfs" -eq 1 ] 2>/dev/null || return 0 + [ -d /sys/kernel/debug ] || return 0 + + if grep -q ' debugfs /sys/kernel/debug ' /proc/mounts 2>/dev/null; then + return 0 + fi + + mount -t debugfs debugfs /sys/kernel/debug 2>/dev/null || true +} + +perf_mem_dump_process_mem_tsv() { + out="$1" + tmp="${out}.tmp" + + printf 'pid\tcomm\tuid\tpss_kb\trss_rollup_kb\tswap_pss_kb\tvmrss_kb\trssanon_kb\trssfile_kb\tshmem_kb\tvmdata_kb\tvmpte_kb\tvmswap_kb\tthreads\tcmdline\n' >"$tmp" + + for proc_dir in /proc/[0-9]*; do + [ -d "$proc_dir" ] || continue + + pid="${proc_dir#/proc/}" + [ -r "$proc_dir/status" ] || continue + + comm="$(cat "$proc_dir/comm" 2>/dev/null || echo unknown)" + cmdline="$(tr '\0' ' ' <"$proc_dir/cmdline" 2>/dev/null | sed 's/[[:space:]]*$//')" + [ -n "$cmdline" ] || cmdline="[$comm]" + + status_vals="$( + awk ' + /^Uid:/ { uid=$2 } + /^Threads:/ { threads=$2 } + /^VmRSS:/ { vmrss=$2 } + /^RssAnon:/ { rssanon=$2 } + /^RssFile:/ { rssfile=$2 } + /^RssShmem:/ { shmem=$2 } + /^VmData:/ { vmdata=$2 } + /^VmPTE:/ { vmpte=$2 } + /^VmSwap:/ { vmswap=$2 } + END { + printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s", + uid+0, vmrss+0, rssanon+0, rssfile+0, + shmem+0, vmdata+0, vmpte+0, vmswap+0, threads+0 + } + ' "$proc_dir/status" 2>/dev/null + )" + + if [ -r "$proc_dir/smaps_rollup" ]; then + rollup_vals="$( + awk ' + /^Pss:/ { pss=$2 } + /^Rss:/ { rss=$2 } + /^SwapPss:/ { swappss=$2 } + END { + printf "%s\t%s\t%s", pss+0, rss+0, swappss+0 + } + ' "$proc_dir/smaps_rollup" 2>/dev/null + )" + else + rollup_vals="0 0 0" + fi + + uid="$(printf '%s\n' "$status_vals" | awk -F '\t' '{print $1}')" + vmrss="$(printf '%s\n' "$status_vals" | awk -F '\t' '{print $2}')" + rssanon="$(printf '%s\n' "$status_vals" | awk -F '\t' '{print $3}')" + rssfile="$(printf '%s\n' "$status_vals" | awk -F '\t' '{print $4}')" + shmem="$(printf '%s\n' "$status_vals" | awk -F '\t' '{print $5}')" + vmdata="$(printf '%s\n' "$status_vals" | awk -F '\t' '{print $6}')" + vmpte="$(printf '%s\n' "$status_vals" | awk -F '\t' '{print $7}')" + vmswap="$(printf '%s\n' "$status_vals" | awk -F '\t' '{print $8}')" + threads="$(printf '%s\n' "$status_vals" | awk -F '\t' '{print $9}')" + + pss="$(printf '%s\n' "$rollup_vals" | awk -F '\t' '{print $1}')" + rss_rollup="$(printf '%s\n' "$rollup_vals" | awk -F '\t' '{print $2}')" + swap_pss="$(printf '%s\n' "$rollup_vals" | awk -F '\t' '{print $3}')" + + printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \ + "$pid" "$comm" "$uid" "$pss" "$rss_rollup" "$swap_pss" \ + "$vmrss" "$rssanon" "$rssfile" "$shmem" "$vmdata" "$vmpte" \ + "$vmswap" "$threads" "$cmdline" >>"$tmp" + done + + { + head -n 1 "$tmp" + tail -n +2 "$tmp" 2>/dev/null | sort -k4,4nr + } >"$out" + + rm -f "$tmp" +} + +perf_mem_dump_process_top_pss() { + in_file="$1" + out_file="$2" + count="$3" + + { + printf 'Top %s processes by PSS\n' "$count" + printf 'pid\tpss_kb\trss_kb\tswap_pss_kb\tcomm\tcmdline\n' + awk -F '\t' 'NR > 1 { + printf "%s\t%s\t%s\t%s\t%s\t%s\n", $1, $4, $5, $6, $2, $15 + }' "$in_file" | head -n "$count" + } >"$out_file" +} + +perf_mem_dump_process_smaps_availability() { + out="$1" + total=0 + readable=0 + + for proc_dir in /proc/[0-9]*; do + [ -d "$proc_dir" ] || continue + total=$((total + 1)) + + if [ -r "$proc_dir/smaps_rollup" ]; then + readable=$((readable + 1)) + fi + done + + { + echo "proc_count=$total" + echo "smaps_rollup_readable=$readable" + } >"$out" +} + +perf_mem_dump_memory_summary() { + out="$1" + + awk ' + /^MemTotal:/ { memtotal=$2 } + /^MemFree:/ { memfree=$2 } + /^MemAvailable:/ { memavailable=$2 } + /^Buffers:/ { buffers=$2 } + /^Cached:/ { cached=$2 } + /^SwapCached:/ { swapcached=$2 } + /^Active:/ { active=$2 } + /^Inactive:/ { inactive=$2 } + /^Shmem:/ { shmem=$2 } + /^Slab:/ { slab=$2 } + /^SReclaimable:/ { sreclaimable=$2 } + /^SUnreclaim:/ { sunreclaim=$2 } + /^KernelStack:/ { kernelstack=$2 } + /^PageTables:/ { pagetables=$2 } + /^VmallocUsed:/ { vmallocused=$2 } + /^CmaTotal:/ { cmatotal=$2 } + /^CmaFree:/ { cmafree=$2 } + /^SwapTotal:/ { swaptotal=$2 } + /^SwapFree:/ { swapfree=$2 } + END { + used = memtotal - memavailable + filecache = cached + buffers + sreclaimable - shmem + if (filecache < 0) filecache = 0 + + printf "MemTotal_kB=%d\n", memtotal + printf "MemFree_kB=%d\n", memfree + printf "MemAvailable_kB=%d\n", memavailable + printf "UsedApprox_kB=%d\n", used + printf "Buffers_kB=%d\n", buffers + printf "Cached_kB=%d\n", cached + printf "FileCacheApprox_kB=%d\n", filecache + printf "SwapCached_kB=%d\n", swapcached + printf "Active_kB=%d\n", active + printf "Inactive_kB=%d\n", inactive + printf "Shmem_kB=%d\n", shmem + printf "Slab_kB=%d\n", slab + printf "SReclaimable_kB=%d\n", sreclaimable + printf "SUnreclaim_kB=%d\n", sunreclaim + printf "KernelStack_kB=%d\n", kernelstack + printf "PageTables_kB=%d\n", pagetables + printf "VmallocUsed_kB=%d\n", vmallocused + printf "CmaTotal_kB=%d\n", cmatotal + printf "CmaFree_kB=%d\n", cmafree + printf "CmaUsed_kB=%d\n", cmatotal - cmafree + printf "SwapTotal_kB=%d\n", swaptotal + printf "SwapFree_kB=%d\n", swapfree + printf "SwapUsed_kB=%d\n", swaptotal - swapfree + } + ' /proc/meminfo >"$out" 2>/dev/null || true +} + +perf_mem_dump_reserved_memory_dt() { + out="$1" + base="" + + if [ -d /sys/firmware/devicetree/base/reserved-memory ]; then + base="/sys/firmware/devicetree/base/reserved-memory" + elif [ -d /proc/device-tree/reserved-memory ]; then + base="/proc/device-tree/reserved-memory" + fi + + { + printf 'node\tname\tcompatible\tstatus\treg_hex\tsize_hex\tno_map\n' + + if [ -z "$base" ]; then + return 0 + fi + + for node in "$base"/*; do + [ -d "$node" ] || continue + + node_name="$(basename "$node")" + + if [ -r "$node/name" ]; then + name="$(tr -d '\0' <"$node/name" 2>/dev/null)" + else + name="$node_name" + fi + + if [ -r "$node/compatible" ]; then + compatible="$(tr '\0' ',' <"$node/compatible" 2>/dev/null | sed 's/,$//')" + else + compatible="" + fi + + if [ -r "$node/status" ]; then + status="$(tr -d '\0' <"$node/status" 2>/dev/null)" + else + status="" + fi + + if [ -r "$node/reg" ]; then + reg_hex="$(od -An -tx1 -v "$node/reg" 2>/dev/null | tr -d ' \n')" + else + reg_hex="" + fi + + if [ -r "$node/size" ]; then + size_hex="$(od -An -tx1 -v "$node/size" 2>/dev/null | tr -d ' \n')" + else + size_hex="" + fi + + if [ -e "$node/no-map" ]; then + no_map="yes" + else + no_map="no" + fi + + printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \ + "$node_name" "$name" "$compatible" "$status" "$reg_hex" "$size_hex" "$no_map" + done + } >"$out" 2>/dev/null || true +} + +perf_mem_dump_dmabuf_fd_owners() { + out="$1" + tmp="${out}.tmp" + + printf 'pid\tcomm\tfd\ttarget\n' >"$tmp" + + for fd in /proc/[0-9]*/fd/*; do + [ -e "$fd" ] || continue + + target="$(readlink "$fd" 2>/dev/null || true)" + + case "$target" in + *dmabuf*|*dma-buf*|*dma_buf*) + proc_dir="$(dirname "$(dirname "$fd")")" + pid="${proc_dir#/proc/}" + comm="$(cat "$proc_dir/comm" 2>/dev/null || echo unknown)" + fd_num="$(basename "$fd")" + printf '%s\t%s\t%s\t%s\n' "$pid" "$comm" "$fd_num" "$target" >>"$tmp" + ;; + esac + done + + sort -k1,1n "$tmp" >"$out" 2>/dev/null || cp "$tmp" "$out" + rm -f "$tmp" +} + +perf_mem_dump_dmabuf_summary() { + out="$1" + raw="$2" + owners="$3" + + { + echo "debugfs_dma_buf_bufinfo=$raw" + + if [ -s "$raw" ] && ! grep -q '^missing or unreadable:' "$raw" 2>/dev/null; then + echo "bufinfo_available=yes" + grep -E 'size|exp_name|name|pid|ino|count|refs' "$raw" 2>/dev/null | head -n 80 || true + else + echo "bufinfo_available=no" + fi + + echo + echo "dmabuf fd owner count:" + + if [ -s "$owners" ]; then + awk -F '\t' 'NR > 1 { count[$2]++ } END { for (c in count) print count[c], c }' "$owners" \ + | sort -nr | head -n 20 + else + echo "no dma-buf fd owners found from /proc/*/fd" + fi + } >"$out" 2>/dev/null || true +} + +perf_mem_dump_zram_summary() { + out="$1" + + { + printf 'device\torig_data_size\tcompr_data_size\tmem_used_total\tmem_limit\tmem_used_max\tsame_pages\tpages_compacted\n' + + for stat in /sys/block/zram*/mm_stat; do + [ -r "$stat" ] || continue + dev="$(basename "$(dirname "$stat")")" + + awk -v dev="$dev" '{ + printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", dev, $1, $2, $3, $4, $5, $6, $7 + }' "$stat" + done + } >"$out" 2>/dev/null || true +} + +perf_mem_dump_kgsl_summary() { + out="$1" + + { + echo "KGSL summary" + + if [ ! -d /sys/class/kgsl/kgsl ]; then + echo "kgsl_available=no" + else + echo "kgsl_available=yes" + + for f in \ + /sys/class/kgsl/kgsl/page_alloc \ + /sys/class/kgsl/kgsl/vmalloc \ + /sys/class/kgsl/kgsl/mapped \ + /sys/class/kgsl/kgsl/mapped_max \ + /sys/class/kgsl/kgsl/mapped_memtype; do + if [ -r "$f" ]; then + echo + echo "===== $f =====" + cat "$f" 2>/dev/null || true + fi + done + + if [ -d /sys/class/kgsl/kgsl/proc ]; then + echo + echo "===== /sys/class/kgsl/kgsl/proc =====" + ls -la /sys/class/kgsl/kgsl/proc 2>/dev/null || true + + for p in /sys/class/kgsl/kgsl/proc/*; do + [ -d "$p" ] || continue + + echo + echo "===== $p =====" + + for f in "$p"/*; do + [ -r "$f" ] || continue + printf '%s: ' "$(basename "$f")" + cat "$f" 2>/dev/null || true + done + done + fi + fi + } >"$out" 2>/dev/null || true +} + +perf_mem_dump_console_file() { + title="$1" + file_path="$2" + max_lines="${3:-80}" + + [ -f "$file_path" ] || return 0 + + log_info "===== ${title}: ${file_path} =====" + + awk -v max_lines="$max_lines" ' + NR <= max_lines { + print + next + } + NR == max_lines + 1 { + print "... truncated after " max_lines " lines ..." + exit + } + ' "$file_path" 2>/dev/null || true + + log_info "===== end ${title} =====" +} + +perf_mem_get_machine_name() { + machine="unknown" + + if [ -r /proc/device-tree/model ]; then + machine="$(tr -d '\0' /dev/null)" + elif [ -r /sys/firmware/devicetree/base/model ]; then + machine="$(tr -d '\0' /dev/null)" + elif command -v hostname >/dev/null 2>&1; then + machine="$(hostname 2>/dev/null || echo unknown)" + fi + + [ -n "$machine" ] || machine="unknown" + printf '%s\n' "$machine" +} + +perf_mem_read_summary_kb() { + file_path="$1" + key="$2" + + awk -F '=' -v key="$key" ' + $1 == key { + print $2 + found = 1 + exit + } + END { + if (!found) print "" + } + ' "$file_path" 2>/dev/null +} + +perf_mem_kb_to_mb() { + kb="$1" + + case "$kb" in + ''|*[!0-9.]*) + printf 'unknown\n' + return 0 + ;; + esac + + awk -v kb="$kb" 'BEGIN { printf "%.2f\n", kb / 1024 }' +} + +perf_mem_system_ram_kb_from_iomem() { + iomem_file="$1" + + [ -r "$iomem_file" ] || { + printf '\n' + return 0 + } + + awk ' + function hexval(c) { + c = tolower(c) + if (c >= "0" && c <= "9") return c + 0 + if (c == "a") return 10 + if (c == "b") return 11 + if (c == "c") return 12 + if (c == "d") return 13 + if (c == "e") return 14 + if (c == "f") return 15 + return 0 + } + + function hex2dec(s, i, n, v) { + gsub(/^0x/, "", s) + n = 0 + for (i = 1; i <= length(s); i++) { + v = hexval(substr(s, i, 1)) + n = (n * 16) + v + } + return n + } + + /^[[:space:]]*[0-9a-fA-F]+-[0-9a-fA-F]+[[:space:]]*:[[:space:]]*System RAM([[:space:]]*)$/ { + line = $0 + sub(/^[[:space:]]*/, "", line) + split(line, parts, /[[:space:]]+/) + split(parts[1], range, "-") + + start = hex2dec(range[1]) + end = hex2dec(range[2]) + + if (end > start) { + count++ + starts[count] = start + ends[count] = end + } + } + + END { + if (count == 0) { + exit + } + + for (i = 1; i <= count; i++) { + for (j = i + 1; j <= count; j++) { + if (starts[j] < starts[i]) { + ts = starts[i] + te = ends[i] + starts[i] = starts[j] + ends[i] = ends[j] + starts[j] = ts + ends[j] = te + } + } + } + + merged_count = 0 + + for (i = 1; i <= count; i++) { + if (merged_count == 0) { + merged_count = 1 + mstarts[merged_count] = starts[i] + mends[merged_count] = ends[i] + continue + } + + if (starts[i] <= mends[merged_count] + 1) { + if (ends[i] > mends[merged_count]) { + mends[merged_count] = ends[i] + } + } else { + merged_count++ + mstarts[merged_count] = starts[i] + mends[merged_count] = ends[i] + } + } + + total_bytes = 0 + + for (i = 1; i <= merged_count; i++) { + total_bytes += mends[i] - mstarts[i] + 1 + } + + if (total_bytes <= 1024) { + exit + } + + printf "%.0f\n", total_bytes / 1024 + } + ' "$iomem_file" 2>/dev/null +} + +perf_mem_resolve_nhlos_kb() { + out_dir="$1" + memtotal_kb="$2" + + source_file="$out_dir/nhlos_source.txt" + iomem_file="$out_dir/iomem.txt" + + nhlos_kb="" + installed_kb="" + systemram_kb="" + + installed_kb="$(perf_mem_roundup_installed_kb "$memtotal_kb")" + systemram_kb="$(perf_mem_system_ram_kb_from_iomem "$iomem_file")" + + if [ -n "$installed_kb" ] && [ -n "$systemram_kb" ]; then + nhlos_kb="$( + awk -v installed="$installed_kb" -v linux="$systemram_kb" ' + BEGIN { + v = installed - linux + if (v < 0) { + v = 0 + } + printf "%.0f\n", v + } + ' + )" + source="installed_total_ram_minus_iomem_system_ram" + else + nhlos_kb="" + source="unavailable_iomem_system_ram" + log_warn "Unable to calculate NHLOS: /proc/iomem System RAM is missing/restricted. Run as root." + fi + + { + echo "nhlos_source=$source" + echo "formula=Installed Total RAM - Total Linux System RAM from /proc/iomem" + echo "nhlos_kb=${nhlos_kb:-unknown}" + echo "installed_total_ram_kb=${installed_kb:-unknown}" + echo "systemram_kb=${systemram_kb:-unknown}" + echo "memtotal_kb=${memtotal_kb:-unknown}" + } >"$source_file" + + printf '%s\n' "$nhlos_kb" +} + +perf_mem_write_component_summary() { + out_dir="$1" + + summary_file="$out_dir/memory_component_summary.txt" + machine="$(perf_mem_get_machine_name)" + + mem_summary="$out_dir/memory_summary.txt" + iomem_file="$out_dir/iomem.txt" + + memtotal_kb="$(perf_mem_read_summary_kb "$mem_summary" "MemTotal_kB")" + memavailable_kb="$(perf_mem_read_summary_kb "$mem_summary" "MemAvailable_kB")" + slab_kb="$(perf_mem_read_summary_kb "$mem_summary" "Slab_kB")" + pagetables_kb="$(perf_mem_read_summary_kb "$mem_summary" "PageTables_kB")" + kernelstack_kb="$(perf_mem_read_summary_kb "$mem_summary" "KernelStack_kB")" + vmalloc_kb="$(perf_mem_read_summary_kb "$mem_summary" "VmallocUsed_kB")" + cmaused_kb="$(perf_mem_read_summary_kb "$mem_summary" "CmaUsed_kB")" + swapused_kb="$(perf_mem_read_summary_kb "$mem_summary" "SwapUsed_kB")" + + systemram_kb="$(perf_mem_system_ram_kb_from_iomem "$iomem_file")" + nhlos_kb="$(perf_mem_resolve_nhlos_kb "$out_dir" "$memtotal_kb")" + + total_physical_kb="" + if [ -f "$out_dir/nhlos_source.txt" ]; then + total_physical_kb="$( + sed -n 's/^total_physical_kb=//p' "$out_dir/nhlos_source.txt" 2>/dev/null \ + | head -n 1 + )" + fi + + if [ -n "$systemram_kb" ] && [ -n "$memtotal_kb" ]; then + kernel_static_kb="$( + awk -v linux="$systemram_kb" -v memtotal="$memtotal_kb" ' + BEGIN { + v = linux - memtotal + if (v < 0) v = 0 + printf "%.0f\n", v + } + ' + )" + else + kernel_static_kb="" + log_warn "Unable to calculate Kernel Static: /proc/iomem System RAM is missing/restricted. Run as root." + fi + + if [ -n "$memtotal_kb" ] && [ -n "$memavailable_kb" ]; then + apps_framework_kb="$( + awk -v memtotal="$memtotal_kb" -v free="$memavailable_kb" ' + BEGIN { + v = memtotal - free + if (v < 0) v = 0 + printf "%.0f\n", v + } + ' + )" + else + apps_framework_kb="" + fi + + { + printf '%s\n' "------------------------------------------------------------" + printf '%-36s %s\n' "Mem Component (in MB)" "$machine" + printf '%s\n' "------------------------------------------------------------" + + printf '%-36s %s\n' "NHLOS" "$(perf_mem_kb_to_mb "$nhlos_kb")" + printf '%-36s %s\n' "Kernel Static" "$(perf_mem_kb_to_mb "$kernel_static_kb")" + printf '%-36s %s\n' "Apps + Framework" "$(perf_mem_kb_to_mb "$apps_framework_kb")" + printf '%-36s %s\n' "Free Mem" "$(perf_mem_kb_to_mb "$memavailable_kb")" + + printf '%s\n' "------------------------------------------------------------" + printf '%-36s %s\n' "MemTotal" "$(perf_mem_kb_to_mb "$memtotal_kb")" + printf '%-36s %s\n' "System RAM" "$(perf_mem_kb_to_mb "$systemram_kb")" + + if [ -n "$total_physical_kb" ] && [ "$total_physical_kb" != "unknown" ]; then + printf '%-36s %s\n' "Total Physical" "$(perf_mem_kb_to_mb "$total_physical_kb")" + fi + + printf '%-36s %s\n' "Slab" "$(perf_mem_kb_to_mb "$slab_kb")" + printf '%-36s %s\n' "PageTables" "$(perf_mem_kb_to_mb "$pagetables_kb")" + printf '%-36s %s\n' "KernelStack" "$(perf_mem_kb_to_mb "$kernelstack_kb")" + printf '%-36s %s\n' "VmallocUsed" "$(perf_mem_kb_to_mb "$vmalloc_kb")" + printf '%-36s %s\n' "CMA Used" "$(perf_mem_kb_to_mb "$cmaused_kb")" + printf '%-36s %s\n' "Swap Used" "$(perf_mem_kb_to_mb "$swapused_kb")" + printf '%s\n' "------------------------------------------------------------" + } >"$summary_file" + + printf '%s\n' "$summary_file" +} + +perf_mem_print_component_summary() { + out_dir="$1" + summary_file="$out_dir/memory_component_summary.txt" + + [ -f "$summary_file" ] || return 0 + + printf '\n' + cat "$summary_file" + printf '\n' +} + +perf_mem_collect_all() { + out_dir="$1" + delay_secs="$2" + top_process_count="$3" + mount_debugfs="$4" + verbose="$5" + + mkdir -p "$out_dir" 2>/dev/null || { + log_fail "Failed to create output directory: $out_dir" + return 1 + } + + case "$delay_secs" in ''|*[!0-9]*) delay_secs=0 ;; esac + case "$top_process_count" in ''|*[!0-9]*) top_process_count=20 ;; esac + case "$mount_debugfs" in ''|*[!0-9]*) mount_debugfs=0 ;; esac + case "$verbose" in ''|*[!0-9]*) verbose=0 ;; esac + + log_info "Output directory: $out_dir" + log_info "Collect delay: ${delay_secs}s" + log_info "Top process count: $top_process_count" + + if [ "$delay_secs" -gt 0 ]; then + log_info "Waiting ${delay_secs}s before memory capture" + sleep "$delay_secs" + fi + + : >"$out_dir/manifest.tsv" + + log_info "Preparing optional debugfs access" + perf_mem_try_mount_debugfs "$mount_debugfs" + + log_info "Collecting platform and boot context" + + perf_mem_collect_cmd "$out_dir/uname.txt" uname -a + perf_mem_append_manifest "$out_dir" "uname" "$out_dir/uname.txt" + + perf_mem_collect_cmd "$out_dir/date.txt" date -u + perf_mem_append_manifest "$out_dir" "date" "$out_dir/date.txt" + + perf_mem_collect_file /proc/version "$out_dir/proc_version.txt" + perf_mem_append_manifest "$out_dir" "proc_version" "$out_dir/proc_version.txt" + + perf_mem_collect_file /proc/cmdline "$out_dir/cmdline.txt" + perf_mem_append_manifest "$out_dir" "cmdline" "$out_dir/cmdline.txt" + + perf_mem_collect_file /proc/cpuinfo "$out_dir/cpuinfo.txt" + perf_mem_append_manifest "$out_dir" "cpuinfo" "$out_dir/cpuinfo.txt" + + if [ -r /proc/config.gz ]; then + zcat /proc/config.gz >"$out_dir/config.txt" 2>/dev/null || true + else + printf 'missing: /proc/config.gz\n' >"$out_dir/config.txt" + fi + perf_mem_append_manifest "$out_dir" "config" "$out_dir/config.txt" + + log_info "Collecting core /proc memory snapshots" + + perf_mem_collect_file /proc/meminfo "$out_dir/meminfo.txt" + perf_mem_append_manifest "$out_dir" "meminfo" "$out_dir/meminfo.txt" + + perf_mem_collect_cmd "$out_dir/free.txt" free + perf_mem_append_manifest "$out_dir" "free" "$out_dir/free.txt" + + perf_mem_collect_file /proc/vmstat "$out_dir/vmstat.txt" + perf_mem_append_manifest "$out_dir" "vmstat" "$out_dir/vmstat.txt" + + perf_mem_collect_cmd "$out_dir/vmstat_cmd.txt" vmstat + perf_mem_append_manifest "$out_dir" "vmstat_cmd" "$out_dir/vmstat_cmd.txt" + + perf_mem_collect_file /proc/zoneinfo "$out_dir/zoneinfo.txt" + perf_mem_append_manifest "$out_dir" "zoneinfo" "$out_dir/zoneinfo.txt" + + perf_mem_collect_file /proc/pagetypeinfo "$out_dir/pagetypeinfo.txt" + perf_mem_append_manifest "$out_dir" "pagetypeinfo" "$out_dir/pagetypeinfo.txt" + + perf_mem_collect_file /proc/buddyinfo "$out_dir/buddyinfo.txt" + perf_mem_append_manifest "$out_dir" "buddyinfo" "$out_dir/buddyinfo.txt" + + perf_mem_collect_file /proc/slabinfo "$out_dir/slabinfo.txt" + perf_mem_append_manifest "$out_dir" "slabinfo" "$out_dir/slabinfo.txt" + + perf_mem_collect_file /proc/vmallocinfo "$out_dir/vmallocinfo.txt" + perf_mem_append_manifest "$out_dir" "vmallocinfo" "$out_dir/vmallocinfo.txt" + + perf_mem_collect_file /proc/modules "$out_dir/modules.txt" + perf_mem_append_manifest "$out_dir" "modules" "$out_dir/modules.txt" + + perf_mem_collect_cmd "$out_dir/lsmod.txt" lsmod + perf_mem_append_manifest "$out_dir" "lsmod" "$out_dir/lsmod.txt" + + perf_mem_collect_file /proc/iomem "$out_dir/iomem.txt" + perf_mem_append_manifest "$out_dir" "iomem" "$out_dir/iomem.txt" + + perf_mem_collect_file /proc/uptime "$out_dir/uptime.txt" + perf_mem_append_manifest "$out_dir" "uptime" "$out_dir/uptime.txt" + + perf_mem_collect_file /proc/sys/vm/swappiness "$out_dir/swappiness.txt" + perf_mem_append_manifest "$out_dir" "swappiness" "$out_dir/swappiness.txt" + + perf_mem_collect_cmd "$out_dir/df.txt" df + perf_mem_append_manifest "$out_dir" "df" "$out_dir/df.txt" + + perf_mem_collect_cmd "$out_dir/mount.txt" mount + perf_mem_append_manifest "$out_dir" "mount" "$out_dir/mount.txt" + + perf_mem_collect_cmd "$out_dir/ps_A.txt" ps -A + perf_mem_append_manifest "$out_dir" "ps_A" "$out_dir/ps_A.txt" + + perf_mem_collect_cmd "$out_dir/ps_eT.txt" ps -eT + perf_mem_append_manifest "$out_dir" "ps_eT" "$out_dir/ps_eT.txt" + + log_info "Collecting optional debugfs and sysfs memory artifacts" + + perf_mem_collect_file /sys/kernel/debug/memblock/reserved "$out_dir/memblock_reserved.txt" + perf_mem_append_manifest "$out_dir" "memblock_reserved" "$out_dir/memblock_reserved.txt" + + perf_mem_collect_file /sys/kernel/debug/tracing/buffer_total_size_kb "$out_dir/tracing_buffer_total_size_kb.txt" + perf_mem_append_manifest "$out_dir" "tracing_buffer_total_size_kb" "$out_dir/tracing_buffer_total_size_kb.txt" + + perf_mem_collect_file /sys/kernel/debug/dma_buf/bufinfo "$out_dir/dma_buf_bufinfo.txt" + perf_mem_append_manifest "$out_dir" "dma_buf_bufinfo" "$out_dir/dma_buf_bufinfo.txt" + + perf_mem_collect_file /sys/kernel/debug/ion/heaps/system "$out_dir/ion_heap_system.txt" + perf_mem_append_manifest "$out_dir" "ion_heap_system" "$out_dir/ion_heap_system.txt" + + log_info "Generating memory summary" + + perf_mem_dump_memory_summary "$out_dir/memory_summary.txt" + perf_mem_append_manifest "$out_dir" "memory_summary" "$out_dir/memory_summary.txt" + + perf_mem_dump_process_smaps_availability "$out_dir/process_smaps_availability.txt" + perf_mem_append_manifest "$out_dir" "process_smaps_availability" "$out_dir/process_smaps_availability.txt" + + log_info "Scanning process memory from /proc/*/smaps_rollup" + + perf_mem_dump_process_mem_tsv "$out_dir/process_mem.tsv" + perf_mem_append_manifest "$out_dir" "process_mem" "$out_dir/process_mem.tsv" + + perf_mem_dump_process_top_pss "$out_dir/process_mem.tsv" "$out_dir/process_top_pss.txt" "$top_process_count" + perf_mem_append_manifest "$out_dir" "process_top_pss" "$out_dir/process_top_pss.txt" + + log_info "Generating reserved-memory, DMA-BUF, zram and KGSL summaries" + + perf_mem_dump_reserved_memory_dt "$out_dir/reserved_memory_dt.tsv" + perf_mem_append_manifest "$out_dir" "reserved_memory_dt" "$out_dir/reserved_memory_dt.tsv" + + perf_mem_dump_dmabuf_fd_owners "$out_dir/dmabuf_fd_owners.tsv" + perf_mem_append_manifest "$out_dir" "dmabuf_fd_owners" "$out_dir/dmabuf_fd_owners.tsv" + + perf_mem_dump_dmabuf_summary "$out_dir/dmabuf_summary.txt" "$out_dir/dma_buf_bufinfo.txt" "$out_dir/dmabuf_fd_owners.tsv" + perf_mem_append_manifest "$out_dir" "dmabuf_summary" "$out_dir/dmabuf_summary.txt" + + perf_mem_dump_zram_summary "$out_dir/zram_summary.tsv" + perf_mem_append_manifest "$out_dir" "zram_summary" "$out_dir/zram_summary.tsv" + + perf_mem_dump_kgsl_summary "$out_dir/kgsl_summary.txt" + perf_mem_append_manifest "$out_dir" "kgsl_summary" "$out_dir/kgsl_summary.txt" + + { + for state in /sys/devices/system/memory/memory*/state; do + [ -r "$state" ] || continue + printf '%s\t%s\n' "$state" "$(cat "$state" 2>/dev/null)" + done + } >"$out_dir/mem_bank_state.txt" 2>/dev/null || true + perf_mem_append_manifest "$out_dir" "mem_bank_state" "$out_dir/mem_bank_state.txt" + + perf_mem_collect_cmd "$out_dir/dmesg.txt" dmesg + perf_mem_append_manifest "$out_dir" "dmesg" "$out_dir/dmesg.txt" + + log_info "Generating final memory component summary" + + component_summary_file="$(perf_mem_write_component_summary "$out_dir")" + perf_mem_append_manifest "$out_dir" "memory_component_summary" "$component_summary_file" + + if [ -f "$out_dir/nhlos_source.txt" ]; then + perf_mem_append_manifest "$out_dir" "nhlos_source" "$out_dir/nhlos_source.txt" + fi + + if [ -f "$out_dir/nhlos_iomem_reserved_regions_all.tsv" ]; then + perf_mem_append_manifest "$out_dir" "nhlos_iomem_reserved_regions_all" "$out_dir/nhlos_iomem_reserved_regions_all.tsv" + fi + + if [ -f "$out_dir/nhlos_reserved_memory_dt_all.tsv" ]; then + perf_mem_append_manifest "$out_dir" "nhlos_reserved_memory_dt_all" "$out_dir/nhlos_reserved_memory_dt_all.tsv" + fi + + if [ -f "$out_dir/nhlos_reserved_memory_dt_non_reusable.tsv" ]; then + perf_mem_append_manifest "$out_dir" "nhlos_reserved_memory_dt_non_reusable" "$out_dir/nhlos_reserved_memory_dt_non_reusable.tsv" + fi + + log_info "Collected memory artifacts:" + perf_mem_dump_console_file "manifest" "$out_dir/manifest.tsv" 80 + + log_info "Memory summary:" + perf_mem_dump_console_file "memory summary" "$out_dir/memory_summary.txt" 80 + + log_info "Top process memory summary:" + perf_mem_dump_console_file "top processes by PSS" "$out_dir/process_top_pss.txt" 80 + + log_info "DMA-BUF summary:" + perf_mem_dump_console_file "dma-buf summary" "$out_dir/dmabuf_summary.txt" 80 + + if [ "$verbose" -eq 1 ]; then + perf_mem_dump_console_file "reserved memory from DT" "$out_dir/reserved_memory_dt.tsv" 120 + + if [ -f "$out_dir/nhlos_iomem_reserved_regions_all.tsv" ]; then + perf_mem_dump_console_file "NHLOS iomem non-System-RAM DDR ranges" "$out_dir/nhlos_iomem_reserved_regions_all.tsv" 160 + fi + + if [ -f "$out_dir/nhlos_reserved_memory_dt_all.tsv" ]; then + perf_mem_dump_console_file "NHLOS DT reserved memory all" "$out_dir/nhlos_reserved_memory_dt_all.tsv" 160 + fi + + if [ -f "$out_dir/nhlos_reserved_memory_dt_non_reusable.tsv" ]; then + perf_mem_dump_console_file "NHLOS DT non-reusable reserved memory" "$out_dir/nhlos_reserved_memory_dt_non_reusable.tsv" 160 + fi + + if [ -f "$out_dir/nhlos_source.txt" ]; then + perf_mem_dump_console_file "NHLOS source" "$out_dir/nhlos_source.txt" 40 + fi + + perf_mem_dump_console_file "zram summary" "$out_dir/zram_summary.tsv" 80 + perf_mem_dump_console_file "KGSL summary" "$out_dir/kgsl_summary.txt" 120 + fi + + if [ ! -s "$out_dir/meminfo.txt" ]; then + log_fail "Failed to collect /proc/meminfo" + return 1 + fi + + log_info "Final memory component summary" + perf_mem_print_component_summary "$out_dir" + + return 0 +} +