响应式网站制作方法,泰安明航网络科技有限公司,wordpress标题添加连载中,成都高薪seoandroid 如何分析应用的内存#xff08;十三#xff09;
本篇文章是native内存的最后一篇文章——perfetto
perfetto简介
从2018年始#xff0c;android开发者峰会正式推出perfetto工具。从此perfetto成为安卓最重要的工具之一。在2018年以前#xff0c;android使用syst…android 如何分析应用的内存十三
本篇文章是native内存的最后一篇文章——perfetto
perfetto简介
从2018年始android开发者峰会正式推出perfetto工具。从此perfetto成为安卓最重要的工具之一。在2018年以前android使用systrace工具进行同样的工作。
perfetto结构
perfetto分成三部分 第一部分: 录制。这部分将录制不同的数据来源如内存cpu调度网络等等。然后将其存储在一个共享内存中。
第二部分: 处理。这部分将共享内存中的数据使用trace_processor库处理成易于理解和分析的格式。同时暴露一个SQL的查询接口。使用者可以使用SQL查询语句进行查询
第三部分: 可视化。这部分使用第二部分trace_processor处理之后的格式进行UI显示。如:Perfetto UI 再如Android Studio和Android GPU:Inspector 注意1perfetto的数据来源丰富多样除了常见的系统提供的以外还可以通过perfetto提供的SDK自定义数据来源 注意2trace_processor是库的名字也是一个可执行文件的名字这个可执行文件以shell的方式进行交互 perfetto的使用
perfetto工程。中包含各种各样的脚本用于帮助使用perfetto。在介绍各种脚本之前我们先看看怎么手动启动perfetto。
perfetto命令行选项
perfetto是Android的一个内置命令。它有两种运行模式
轻量模式所有配置选项都作为命令行参数提供但可用的数据源仅限于ftrace和atrace。这种模式类似于systrace。常规模式:使用配置它可以收集所有的数据来源包括自定义的数据来源。 注意ftrace是linuix内核提供的跟踪框架。atrace是Android提供的跟踪框架它基于ftrace开发并提供了Android特有的功能如跟踪amwm等 不管是轻量模式还是常规模式他们都有如下的共同的参数
-d, --background:后台运行
-o, --out OUT_FILE:输出文件
--dropbox TAG通过DropBoxManager的API上传跟踪的数据
--no-guardrails启用 --dropbox时将禁用对过度资源使用的保护措施
--query查询服务状态并打印
--query-raw跟--query一样但是打印的数据为proto格式
-h, --help打印帮助信息轻量模式
下面给出一个轻量模式的例子
adb shell perfetto --out /sdcard/wm.txt wm该命令表示跟踪wm同时将结果输出到out指定的文件中。当然ftrace事件也支持跟踪。
轻量模式支持的参数有
-t, --time TIME[s|m|h]:运行的时间
-b, --buffer SIZE[mb|gb]指定buffer的大小
-s, --size SIZE[mb|gb]指定最大文件大小单位为兆字节MB或千兆字节GB。默认情况下perfetto仅使用内存中的环形缓冲区。常规模式
下面给出一个常规模式的例子。
adb shell perfetto --txt --config /sdcard/record.txt告诉perfetto使用record.txt文件中的配置进行录制。 其中--txt表示使用的是protocol buffer格式简写为pbtxt 常规模式支持的选项有
-c, --config CONFIG_FILE:配置文件
--txt表示使用pbtxt格式的配置其中record.txt内容如下各行解释见其后
duration_ms: 10000buffers: {size_kb: 8960fill_policy: DISCARD
}
buffers: {size_kb: 1280fill_policy: DISCARD
}
data_sources: {config {name: linux.ftraceftrace_config {ftrace_events: sched/sched_switchftrace_events: power/suspend_resumeftrace_events: sched/sched_process_exitftrace_events: sched/sched_process_freeftrace_events: task/task_newtaskftrace_events: task/task_renameftrace_events: ftrace/printatrace_categories: gfxatrace_categories: viewatrace_categories: webviewatrace_categories: cameraatrace_categories: dalvikatrace_categories: power}}
}
data_sources: {config {name: linux.process_statstarget_buffer: 1process_stats_config {scan_all_processes_on_start: true}}
}其中各个字段的解释如下因为本文章主要是内存分析。所以不会解释所有的配置项仅仅解释上文出现的配置。
duration_ms:perfetto运行的时间单位毫秒
buffers定义buffer的大小和buffer满了之后的行为size_kb即为定义的大小fill_policy即buffer满了之后的行为默认是一个环形缓冲buffer此处定义为直接丢弃buffers可以定义多个buffer。通常情况下定义一个buffer是没有问题的。但是当写入数据的速率不同时可能需要定义多个buffer.如果定义了多个buffer就将data_sources的target_buffer指定为对应的buffer即可。如上列第二个data_sourcesdata_sources:定义数据源config:配置指定的数据源。name:数据源名字如linux.ftracetarget_buffer:将录制的数据写入哪一个bufferftrace_config详细指定linux.ftrace的来源。如上面的ftrace事件和atrace的categoriesprocess_stats_config:详细指定linux.process_stats的状态来源。如上面的scan_all_processes_on_start表示在启动的时候所有进程都会被扫描并dumpperfetto手动配置内存分析
正如前面介绍的一样我们修改data_sources配置来录制内存。在前面的章节中使用了test_malloc应用进行内存分析。同样本文也将使用这个应用进行perfetto的实验。
下面从一个简单的例子开始
运行命令
键入下面的命令如下
adb shell perfetto \-c - --txt \-o /data/misc/perfetto-traces/trace \
EOFbuffers: {size_kb: 63488fill_policy: DISCARD
}
buffers: {size_kb: 2048fill_policy: DISCARD
}
data_sources: {config {name: android.packages_listtarget_buffer: 1}
}
data_sources: {config {name: android.heapprofdtarget_buffer: 0heapprofd_config {sampling_interval_bytes: 4096process_cmdline: com.example.test_mallocshmem_size_bytes: 8388608block_client: true}}
}
duration_ms: 10000EOF我们先介绍上文出现的配置项同样的不会介绍所有的配置项而是介绍上文出现的配置项。 因为perfetto提供了一个heap_profile的python脚本帮助我们完成这项工作。后文将会 详细介绍这个脚本的参数使用。
1. 首先定义了两个buffer。一个大小2048kb一个大小63488kb。
2. 数据源1来自于android.packages_list将其数据写入buffer 1中
3. 数据源2来自于android.heapprofd。将其写入buffer 0中
4. 其中android.pacages_list数据源用于获取包的详细信息。
5. android.heapprofd数据源用于获取android堆的详细信息。这是我们分析native内存的主要数据来源。
6. heapprofd_config:分别配置了采样间隔又称为采样率为4096字节。
7. heapprofd配置应用为com.example.test_malloc
8. heapprofd配置自己的共享内存的大小为8388608heapprofd的内存见后文
9. heapprofd配置当buffer满的时候要等待buffer腾出空间。即block_client为true注意上面关于heapprofd的配置在后文会做详细介绍。 键入命令完成之后按下enter键即开始录制
查看
在手机上触发内存泄漏然后10s后会生成一个/data/misc/perfetto-traces/trace文件将其拖入Perfetto UI中如下图
UI界面说明和分析
从上面截图可以看到每一个进程下面有一个heapp rofile.同一行中有一个棱形图标这个菱形图标表示从开始分析到这个棱形图标结束,这段时间内的所有heap的一个snapshot.
点击这个棱形图标将会出现下面的调用栈。
其中
Unreleased malloc size表示还未释放的内存大小有多少Total malloc size总共分配的内存是多少包括已经释放了的Unreleased malloc count未调用free的分配的次数Total malloc count总共分配的次数
为了定位内存泄漏最简单的方法是查看Unreleased malloc count。即还有多少次分配未调用free
过程如下
可以看到总共有9次未被释放 然后一层一层往下找即可找到我们测试程序中的例子如下
从中我们可以看到在Java_com_example_test_1malloc_MainActivity_stringFromJNI函数中 有4次未释放。
切换到Unreleased malloc size标签下能看看到总计为16kB
然后查看代码如下
extern C JNIEXPORT jstring JNICALL
Java_com_example_test_1malloc_MainActivity_stringFromJNI(JNIEnv* env,jobject /* this */) {std::string hello Hello from C;//未释放的地方volatile int * p new int[1024];*p 123456;return env-NewStringUTF(hello.c_str());
}上面的代码中指针p未进行释放操作。 注意因为heapprofd只会记录开始运行到结束这段时间的内存分配因此在heapprofd开始之前的应用的内存分配不会被记录下来。 perfetto脚本配置内存分析
上面介绍了手动配置prefetto进行内存的抓取。接下来我们将使用perfetto工程中的脚本工具heap_profile。
下载heap_profile工具
curl -LO https://raw.githubusercontent.com/google/perfetto/master/tools/heap_profile
chmod x heap_profile使用headp_profile录制内存使用情况
## 录制com.example.test_malloc的内存情况
./heap_profile -n com.example.test_malloc会出现如下的结果
Profiling active. Press CtrlC to terminate.
You may disconnect your device.如果出现Profiling active这表示正在运行中。
此时在手机中进行相应的操作触发内存泄漏。
然后按下ctrlc结束录制。
则会输出如下的结果
^CWaiting for profiler shutdown...
Wrote profiles to /var/folders/fs/mg80v00d5yj67pmqp_qh57_c0000gn/T/629a10 (symlink /var/folders/fs/mg80v00d5yj67pmqp_qh57_c0000gn/T/heap_profile-latest)
The raw-trace file can be viewed using https://ui.perfetto.dev.
The heap_dump.* files can be viewed using pprof/ (Googlers only) or https://www.speedscope.app/.
The two above are equivalent. The raw-trace contains the union of all the heap dumps注意heap_profile只能录制在它运行期间的内存情况不能录制heap_profile运行之前的情况 查看结果
打开Perfetto UI。然后将上面路径中的raw-trace拖入刚才打开的网页。
结果查看同上面手动配置perfetto是一模一样的。
手动配置和脚本配置的比较
手动配置可以同时配置多个数据源因此它比脚本配置更适合同时录制多个数据源。
这属于perfetto的高级用法不过有前面关于perfetto的简单介绍这种所谓的高级也不过是参数的不同而已。
heapprofd守护进程
前面在进行内存分析的时候指定的数据源为heapprofd.接下来看看heapprofd是什么东西。
一句话概括如下heapprofd是android设备中运行的一个守护进程它将android中的进程heap数据解析成可以被Perfetto UI解释的格式。 注意heapprofd需要Android10或者更高版本 内存分析工程的组成部分
我们将heapprofd,heap_profile脚本以及其他的工具和库统称为内存分析工程这里简写为heap profile
heap profile主要分成三个部分 第一部分hprofd.so。应用在需要进行内存分析时会自动加载这个库。并使用这个库将heap中的数据导出到一个共享内存中。例如在上面配置的shmem_size_bytes: 8388608即为这部分共享内存的大小
第二部分heapprofd 一个运行在android 设备内部的守护进程。它负责处理这个共享内存中的数据并解析这些数据成合适的格式
第三部分将录制结果使用图形表示出来。如Android Studio在Android profiler中显示出来 再比如https://ui.perfetto.dev/将结果显示在网页中。
heapprofd为什么性能好
如果heapprofd跟踪所有的内存那么将会陷入malloc debug一样的窘境——变得异常缓慢。 而实际上heaprofd对内存进行采样分析在采样上同样有限制不会对每个字节都采样而是使用了统计学上的概率分布。
这里使用了统计学上一个比较著名的函数P(x)1-e^(-λ)。其中λ分配的内存大小/采样率sampling rate。 这个函数是对指数分布的一个积分。P(x)则表示分配x字节大小的内存至少被采样一次的概率。采样率默认为4096字节。P(x)函数图形如下 至于为什么要采用这个函数则不是本篇文章的讨论范围推荐阅读马同学关于泊松分布和指数分布的文章如下 指数分布 泊松分布
从上图可见部分较小的内存分配可能不会被采样到这样就省去了很多开销。需要注意的是如果有多个内存较小的分配那么它被采样的概率等同于一次分配同等大小的概率
有时候为了增加某个分配被采样的概率则需要调整采样率。比如上文手动配置中:sampling_interval_bytes: 4096。 至于heap_profile脚本如何调整见下文
heap_profile 参数讲解
heap_profile脚本参数如下想来大家应该能看懂这里面的所有配置项了
-n,--name 名字需要进行分析的进程的名字可以是多个进程各个进程用逗号分开-p, --pid PIDS需要进行分析的进程的PID可以是多个进程各个pid用逗号分开-i, --interval采样率的大小单位字节默认4096字节-o, --output DIRECTORY输出的目录文件--all-heaps收集所有的堆--block-client当buffer满的时候等待heapprofd腾出buffer里面的空间--block-client-timeout等待heapprofd腾出buffer空间的最长时间-c, --continuous-dump:连续dump的间隔时间单位毫秒0表示禁止连续dump-d, --durationheap_profile的运行时间如果没有设置则一直运行直到用户输入ctrlc--disable-fork-teardown:在fork时不要终止客户端。这对使用vfork的程序可能会有用。仅适用于Android 11及以上版本。--disable-selinux运行期间disable selinux--dump-at-max记录最大的内存使用量而不是在heap_profile运行时刻的最大内存使用量。这对于分析LMK(Low memory killer)有一定帮助--heaps HEAPS需要收集的堆列表逗号分开。android 12以上。常见的堆列表有malloc,art--idle-allocations:跟踪自上次dump以后每个调用栈有多少字节未被使用--no-android-tree-symbolization不要进行符号解析--no-block-client当buffer满时,提前停止性能分析--no-running不要分析已经运行的程序。需要android 11以上--no-start不要启动heapprofd--no-startup不要收集在性能分析阶段中启动的进程--no-versions不要获取apk的版本信息--print-config:要打印log--shmem-size客户端和 heapprofd 之间的缓冲区大小。默认为 8MiB。必须是 4096 的二次幂且至少为 8192。--simpleperf:获取 heapprofd 的 simpleperf 性能分析。仅用于 heapprofd 开发。--traceconv-binarytraceconv工具的路径。仅用于调试
使用连续模式
默认情况下heap profile将所有的dump都存储在一个snapshot中。在UI界面上就是只有一个菱形图标。
另外一个可行的方式是告诉heap profile周期性的将dump数据存储在不同的snapshot中这样将会在UI界面上看到多个菱形图标。配置如下
continuous_dump_config {dump_interval_ms: 5000
}或者通过heap_profile的-c选项。 其中每一个棱形都表示从开始时间到菱形结束时间的所有dump
只采样java的堆
如果我们只想采样java的堆该如何操作呢可以通过heap_profile的选项参数--heaps com.android.art 或者在配置文件中使用heaps: “com.android.art” 注意java堆采样只有在Android 12 及以上才能使用 注意java堆采样和java堆转储不能混淆 如下图 从图中可以看到java堆采样只有两个tab分别为
总分配大小在这个调用栈中有多少个字节被分配了。这些字节可能已经被释放了总的分配次数在这个调用栈中有多少次分配。
java堆采样对于分析大对象分配导致的内存抖动是有巨大帮助的。同时也有助于分析art的分配类型new,array,class,largeobject
手动触发snapshot
如果我们想要手动触发堆的snapshot可以使用如下的方法
db shell killall -USR1 heapprofd调用栈的符号化
有时候调用栈没有名字此时可以进行离线的符号化或者调用栈有名字但是还想知道出问题的点在第几行。这时就可以进行符号化的操作了。 注意在进行符号化之前需要将llvm-symblozier放入PATH路径中android的llvm-symbolizer在何处找到见android 如何分析应用的内存十一——ASan下面命令将ndk中的必要工具加入PATH中 export PATH/Users/biaowan/Library/Android/sdk/ndk/25.2.9519653/toolchains/llvm/prebuilt/darwin-x86_64/bin/:$PATH具体操作如下。
配置PERFETTO_BINARY_PATH环境变量
## 配置为当前目录
export PERFETTO_BINARY_PATH$(pwd)进行数据抓取详细见上文
./heap_profile -n com.example.test_malloc对raw-trace 进行转换
## 准备对应的so库,放在PERFETTO_BINARY_PATH目录中
## 具体的路径查找见下文
cp -a cp -a ./app/build/intermediates/cxx/Debug/2j414f1k/obj/arm64-v8a/libtest_malloc.so $PERFETTO_BINARY_PATH## 使用traceconv工具同heap_profile脚本一样在同一工程的同一目录下
## 下载命令curl -LO https://raw.githubusercontent.com/google/perfetto/master/tools/traceconv
## traceconv将使用llvm-symbolizer工具对raw-trace文件进行符号化并存储在symbols文件中
./traceconv symbolize raw-trace symbols将符号信息写入trace文件中
cat raw-trace symbols symbolized-trace查看符号化之后的调用栈 如果使用perfetto ui直接查看调用栈可能不会显示对应的行号此时需要使用SQL语句。 所有的符号化都存储在stack_profile_symbol表格中。因此可以使用下面的语句将该表格中的所有行输出。
SELECT * FROM stack_profile_symbol;从上图可以看到对应的行号。
符号化的路径查找
可是从上面的操作过程中似乎漏掉了一个非常重要的信息。那就是traceconv如何知道对应的so库在什么位置。 在上面的例子中只是简单的将so库移动到PERFETTO_BINARY_PATH目录下。
事实上traceconv将会依据下面的路径进行查找。以/system/lib/base.apk!foo.so为例它的build id为abcd1234
$PERFETTO_BINARY_PATH/system/lib/base.apk!foo.so$PERFETTO_BINARY_PATH/system/lib/foo.so$PERFETTO_BINARY_PATH/base.apk!foo.so$PERFETTO_BINARY_PATH/foo.so$PERFETTO_BINARY_PATH/.build-id/ab/cd1234.debug 注意第五点.build-id后面的路径规则为使用前两字字母为子目录如abcd1234的ab。然后使用后面的所有字母如abcd1234的cd1234再加上.debug即cd1234.debug作为目录。 符号化常见问题
问题1
[855.670] subprocess_posix.cc:47 Failed to exec llvm-symbolizer (errno: 2, No such file or directory)表示没有配置llvm-symbolizer在PATH路径中.将其加入路径中即可解决
问题2
[855.680] local_symbolizer.cc:344 Could not find /data/app/~~gx5t-n8HJXchIN64wh1QNg/com.example.test_malloc-iRcQ3mTPeH-1Q84dFo81GQ/lib/arm64/libtest_malloc.so (Build ID: 1807c1edf56cb4a2c27e21e533ea0445a857b100).表示没有找到libtest_malloc.so。此时按照上面介绍的符号化路径的5个规则挨个检查是否存在对应的so库。如果存在so库但是依然出现这个问题则需要比较build id是否相同。读取 build id通过如下的命令
llvm-readelf -n ./app/build/intermediates/cxx/Debug/2j414f1k/obj/arm64-v8a/libtest_malloc.so如果build id不同则说明没有放置正确版本的so库。如果build id相同则将so库放在根目录下再试
调用栈的反混淆
在java代码中经常出现混淆之后的代码对于debug来说非常不舒服。可以使用下面的步骤进行反混淆
使用PERFETTO_PROGUARD_MAP环境变量提供混淆映射如下
PERFETTO_PROGUARD_MAPcom.example.pkg1foo.txt:com.example.pkg2bar.txt使用工具traceconv进行反混淆
##将raw-trace进行反混淆输出到deobfuscation_map中
traceconv deobfuscate raw-trace deobfuscation_map将反混淆结果加入raw-trace中
cat raw-trace deobfuscation_map deobfuscated_trace最后使用perfetto ui打开deobfuscated_trace即可。
因为跟符号化的过程和步骤极度相似所以没有做实验。
故障处理
Buffer overrun
如果分配内存的速度太快而heap_profd无法跟上则出现一个buffer overrun这会导致heapprofd提前结束
如果overrrun被一个短暂的内存尖刺触发则增加共享内存的大小可以解决这个问题。传递–shmem-size参数 或者传递–interval16000或更高的值来增加采样间隔牺牲准确度详见上文heapprofd为什么性能好
性能分析结果为空
在user版本中只有进行了如下配置的才能进行性能分析否则会出现分析结果为空的情况
profilable
manifest ...applicationprofileable android:shelltrue/.../application
/manifestdebugable
manifest ...applicationandroid:debuggabletrue.../application
/manifest自此android的内存的native部分已经全部介绍完毕现在针对前面介绍的所有方法做一个小结
内存方法的总结
第零个工具xdd:只能查看任意内存第一个工具gdb:它可以查看寄存器和任意位置的内存分析coredump能查看栈情况不能查看堆情况第二个工具lldb它可以查看寄存器和任意位置的内存分析coredump能查看栈情况不能查看堆情况第三个工具自定义malloc:只能查看堆情况且查看的范围较小几乎只有自己编译的代码第四个工具malloc hook:能查看所有的堆分配情况第五个工具malloc统计和libmemunreachable可以查看所有堆分配情况第六个工具malloc debug和libc回调:能查看所有堆分配情况第七个工具ASan/HWASan:只能查看linux的堆分配情况无法查找android的分配情况列在此处只是为了知识的完整性第八个工具perfetto:只能查看堆内存分配情况
其中gdb,lldb,ASan/HWAsan也可以作为调试工具出现。
下一个大主题就是android应用的java部分了这部分应该怎么进行内存的分析该怎么分析敬请期待