文件页内存调试分析

名称:filemap 地址:https://kpan.mioffice.cn/webfolder/ext/9uIEwyskjiT%24uVm31GQvyw%40%40?n=0.9258364536417749 密码:Uc26

Filemap 文件页堆栈抓取

adb push filemap_catch /data/local/tmp
adb shell
su # 非 root 手机使用
cd /data/local/tmp/
chmod +x filemap_catch
./filemap_catch -p com.android.systemui -s -d
... # 非自启动应用在执行期间当打印到 drop cache done... 后手动启动应用
# 执行完成后
exit  # su 执行两次
adb pull /data/local/tmp/filemap.perf.data .
adb pull /data/local/tmp/filemap_inode.json .

如果有文件被多个进程使用,请考虑停止这些进程,例如:

pm disable com.miui.weather2
pm disable com.miui.miwallpaper
lsof | grep libllvm-qgl.so  # 这里查看就剩一个 systemui 后再执行命令抓取
./filemap_catch -p com.android.systemui -s -d

如果通过 kill 的方式抓当前进程采样较少,可以考虑抓其父进程

如果进程在 stop android 后仍然存在,一般父进程是 init 进程,那 -s stop android 方式抓是无效的,可以用如下方式抓取

# mediaserver64 父进程为 init, -f 指定父进程,-b 指定在 kill 之前就启动 simpleperf
./filemap_catch -p mediaserver64 -k -f 1 -b -d

对比机 stop android 抓取或抓取 zyogote64 方式

# 使用最新的 filemap_catch
setsid ./filemap_catch -p zygote64 -s -d &
# android 重启后会断掉 shell,但会在后台抓取,等待一段时间(默认抓 10s)后 pull 即可
# 抓取完成可确认下文件时间是否正常
ls -lah /data/local/tmp/filemap.perf.data

可能出现的问题

  1. 采样数据为 0 或较少

抓取完成后可通过 Samples recorded: 确认采样数量,数据量越大越好,如果为 0 就是没有采集到,可检查进程是否正常启动

另外,如果进程自身启动较快或者 drop cache 不干净都有可能导致采样点较少或者为 0,可以尝试抓取父进程

simpleperf I cmd_record.cpp:840] Recorded for 10.0996 seconds. Start post processing.
simpleperf I cmd_record.cpp:941] Samples recorded: 0. Samples lost: 0.
  1. filemap_catch 执行卡住一直不动

大概率是进程没有启动,filemap_catch 会遍历 /proc 去搜索进程,如果一直没匹配到就会卡住

  1. 解析后 so 库内存分配较小或没有

公有的 so 库可能被其它进程先分配内存导致采样不到数据,可抓取 代码执行情况(通过抓 -e cpu-cycles 类型)去对比 so 库函数调用差异

filemap_catch 命令说明

  • -p 进程名
  • -t perf 抓取时间
  • -m 内核态内存大小,如果内核态丢失率高可提高该参数,大小需 2 的 n 次方,如果有 out of memory 错误需降低该参数
  • -u 用户态内存大小,如果用户态丢失率高可提高该参数
  • -s 通过停止 Android 的方式去抓取,android 系统自启动进程使用,stop android 后仍然存在的进程不可用,默认是 stop 进程抓取
  • -d dump inode 文件
  • -k 通过杀进程的方式去抓取,自启应用一般会丢失一些数据,可抓多次取采样点(Samples recorded)最大的
  • -f 指定父进程名或 pid
  • -b 在进程 stop 或 kill 之前执行 simpleperf,与 -f 同时使用
  • -c 指定 simpleperf 进程 cpu mask,会绑核和调频,丢失率高时可酌情使用
/data/local/tmp/ # ./filemap_catch -h
Usage: filemap_catch -p <target_process> [-t <duration>] [-m <mem_map>] [-e <events>]
                     [-f <parent_pid_or_name>] [-b] [-o <outfile>] [-s | -k] [-c <cpu_config>] [-d]
Options:
  -p <target_process>  Target process name
  -t <duration>        Duration of perf data collection (default: 10)
  -m <mem_map>         Memory map size (default: 65536)
  -u <user_mem>        User buffer size (default: 1024M)
  -e <events>          Simpleperf events to record (default: filemap:mm_filemap_add_to_page_cache)
  -o <outfile>         Output perf data file (default: /data/local/tmp/filemap.perf.data)
  -d                   Dump inode file (path: /data/local/tmp/filemap_inode.json)
  -s                   Stop Android before catch (default: stop process)
  -k                   Kill target process before catch
  -f <parent_process>  Parent process name or pid
  -b                   Run simpleperf before stop or kill, use with -f
  -c <cpu_config>      Simpleperf CPU affinity config: 4,5_6,7
                       - Main threads to cpu4,5, worker threads to cpu6,7
  -h                   Print this help message

Filemap 文件页堆栈解析

抓取到文件页 perf.data 后可以通过 report_filemap.exe 解析,如下命令:

simpleperf_report.exe -i filemap.perf.data -o filemap_all.html --inode_file filemap_inode.json

解析出来的 html 文件是所有文件集合的火焰图

1 为整个进程文件页申请的大小

点击 2 Sample Table 后可以进入函数列表

点击 3 处可以切换 event count 显示,event count 值即为 内存占用大小

点击 4 处切换到 Function 界面

5 当把鼠标放在某个函数上时显示出来的提示框中有该函数所占用的内存大小

在 Function 页面拉到最底下即为当前线程该函数调用栈中申请的文件大小及文件 6

7 处为对应文件的 dev 和 inode 值,可以根据该值单独解析处单个文件的火焰图分析

为方便线程火焰图文件查看,将上述的 Function 页面显示的 Filemap 集成到了悬浮页中,如下图

鼠标放置在 1 中函数处弹出悬浮窗,2 显示的是该处函数申请的内存大小

3该线程所有该函数的文件申请内存大小

解析单个文件栈命令

simpleperf_report.exe -i filemap.perf.data -o filemap_266338359_34078.html --dev_inode 266338359_34078

解析完后打开 html 文件可以看到该进程的单个文件的所分配的内存大小

点击 Flamegraph 切换到火焰图界面即可查看该文件内存分配的堆栈信息

如上是文件映射内存的过程,堆栈反应的是某些文件加载到内存中的情况,但针对一些可执行文件,可能需要查看它具体调用了哪些接口导致映射内存过大,可采用如下方式抓取进程回栈搜索相应的可执行文件查看接口使用。

文件代码执行抓取解析

adb shell
cd /data/local/tmp
# 使用 -e cpu-cycles 抓取
./filemap_catch -p com.android.systemui -e cpu-cycles -s -o /data/local/tmp/cpu-cycles.perf.data
...
adb pull /data/local/tmp/cpu-cycles.perf.data
# init 子进程参考命令
./filemap_catch -p audioserver -e cpu-cycles -f 1 -b -o /data/local/tmp/cpu-cycles.perf.data

新版本增加了 -n 参数,如果采样点较少可通过增加 -n c10000 抓取,如果丢失率较高可调整采样频率比如使用 -n c20000,-n 参数 c 值越小采样频率越高,f 值越大采样频率越高

解析

simpleperf_report.exe -i cpu-cycles.perf.data -o cpu-cycles.html

可以在 cpu-cycles.html 中搜索该可执行文件调用了哪些函数接口,注意,cpu-cycles.html 文件解析不包含内存信息,仅有函数调用栈信息

可以利用手机中的 simpleperf 导出某个 so 库中所有执行函数为 csv 文件,这样可以在抓取对比机后利用 AI 去对比函数调用差异

adb shell
simpleperf report -i /data/local/tmp/cpu-cycles.perf.data --csv --children --sort comm,pid,tid,dso,symbol,vaddr_in_file --dsos /system/lib64/libaudiohal@aidl.so -o /data/local/tmp/cpu-cycles.csv
adb pull /data/local/tmp/cpu-cycles.csv .

文件页释放抓取

文件页的释放并不是完全由本进程去释放的,所以没有办法抓到完整的回栈信息,但可以通过 filemap/mm_filemap_delete_from_page_cache ftrace 去获取释放了多少

可以在抓取 perfetto 中的 config 中增加如下配置,也可以直接使用网盘中的 android_perfetto_config.pbtx 文件,配置默认是抓取 10s,如果需要更改就调整 duration_ms 大小

data_sources: {
    config {
        name: "linux.ftrace"
        target_buffer: 0
        ftrace_config {
            ftrace_events: "kmem/rss_stat"
            ftrace_events: "filemap/mm_filemap_add_to_page_cache"
            ftrace_events: "filemap/mm_filemap_delete_from_page_cache"
            ...
        }
    }
}
duration_ms: 10000 # 配置抓取时间,单位 ms

执行如下命令抓取 perfetto trace

# windows cmd
type android_perfetto_config.pbtx | adb shell perfetto --txt -o /data/misc/perfetto-traces/trace_file.perfetto-trace -c -
adb pull /data/misc/perfetto-traces/trace_file.perfetto-trace
# shell
cat android_perfetto_config.pbtx | adb shell perfetto --txt -o /data/misc/perfetto-traces/trace_file.perfetto-trace -c -
adb pull /data/misc/perfetto-traces/trace_file.perfetto-trace

抓取到的 perfetto trace 使用 perfetto 网站 打开查看,找到关心的进程,点开进程后找到 3 处 mem_rss:file 就是进程的文件页大小变化

1 处可以点击选择 Ftrace Events 查看 ftrace 日志,点击 2 处 Filter 可以过滤出 mm_filemap 相关的 ftrace 信息查看具体 inode,点击 4 处可以导出为 systrace 文件,这样可以文本打开搜索过滤

其它

文件 dev 和 inode 获取

adb shell stat /vendor/lib64/libllvm-qgl.so
File: /vendor/lib64/libllvm-qgl.so
  Size: 23688512         Blocks: 25304   IO Blocks: 512  regular file
Device: fe0ch/65036d     Inode: 25269504         Links: 1        Device type: 0,0
Access: (0644/-rw-r--r--)       Uid: (    0/    root)   Gid: (    0/    root)
Access: 2009-01-01 08:00:00.000000000 +0800
Modify: 2009-01-01 08:00:00.000000000 +0800
Change: 2009-01-01 08:00:00.000000000 +0800

dev 值需要移位操作,如上 fe0c 变更为 fe0000c(fe << 20 + 0c),转化成十进制就是 266338316,在与 inode 组合,266338316_25269504 然后在使用 report_filemap.exe 去过滤,如果过滤出来没有采样点的话就是没有抓到该文件回栈