perfetto 官方文档翻译

Perfetto 翻译第十篇-案例学习-分析内存使用

2023-10-07  本文已影响0人  David_zhou

前言:虽然有翻译软件,虽然有chatgpt,毕竟语言隔阂,对这个工具还是一知半解,因此想通过翻译的方式和大家来一起学习下Perfetto这个强大的工具

目录

#####################以下分割线#####################
英文原文在这里

前提条件

ADB已安装。

运行Android 10+的设备。

可评测或可调试的应用程序。如果你运行的是Android的“用户”版本(而不是“userdebug”或“eng”),你的应用程序需要在manifest中标记为可评测或可调试。有关更多详细信息,请参阅文档

dumpsys meminfo

dumpsys meminfo是开始研究进程内存使用情况的一个较好的方式,它提供了一个进程正在使用的各种类型内存的概况。

$ adb shell dumpsys meminfo com.android.systemui

Applications Memory Usage (in Kilobytes):
Uptime: 2030149 Realtime: 2030149

** MEMINFO in pid 1974 [com.android.systemui] **
                  Pss  Private  Private  SwapPss      Rss     Heap     Heap     Heap
                Total    Dirty    Clean    Dirty    Total     Size    Alloc     Free
               ------   ------   ------   ------   ------   ------   ------   ------
 Native Heap    16840    16804        0     6764    19428    34024    25037     5553
 Dalvik Heap     9110     9032        0      136    13164    36444     9111    27333

[more stuff...]

查看Dalvik堆(即Java堆)和Native堆的“Private Dirty”列,我们可以看到SystemUI在Java堆上的内存使用量是9M,在Native堆上是17M。

linux内存管理

那上面其他的clean, dirty, Rss, Pss, Swap 代表什么意思,为了回答这个问题,我们需要深入研究下linux内存管理。

从内核的角度来看,内存被划分为大小相等的块,称为页。页的大小通常是4KiB。

页被组织在称为VMA(虚拟内存区域)的虚拟连续范围中。

当进程通过mmap())系统调用请求新的内存页池时,就会创建VMA。应用程序很少直接调用mmap(),而是通过由本地进程的分配器malloc()/operator new()或Java应用程序的Android RunTime来间接调用。

VMA可以有两种类型:有后备文件的映射和匿名的映射。

有后备文件 VMAs: 有后备文件的VMA是内存中文件的映射。它们是通过向mmap()传递文件描述符而获得的。内核在VMA上通过缺页中断的方式来传输文件,因此读取指向VMA的指针相当于文件上的read()。例如,动态链接器(ld)在执行新进程或动态加载库时使用文件支持的VMA,或者Android framework在加载新的.dex库或访问APK中的资源时使用的都是有后备文件的的VMA。

匿名VMA:是没有任何文件映射的内存区域。这是内核请求内存时分配的方式。匿名VMA是通过调用mmap(…MAP_ANONMOUS…)获得的。

只有当应用程序尝试从VMA读取/写入时,才会以页粒度分配物理内存。如果你分配了32 MiB的页,但如果只改动了一个字节,你的进程的内存使用量只会增加4KiB。进程的虚拟内存将增加32 MiB,但其驻留物理内存只增加4 KiB。

在优化程序的内存使用时,我们感兴趣的是减少它们在物理内存中的占用。在现代操作上,虚拟内存使用率高通常不值得担心(除非地址空间用完,这在64位系统上极少发生)。

我们将驻留在物理内存中的进程内存大小称为RSS(resident Set Size)。但常驻内存是分类型的。

从内存消耗的角度来看,VMA中的各个页面可以具有以下状态:

随时间变化的内存

dumpsys meminfo可以很好地获取当前内存使用情况的快照,但即使是很短的内存峰值也会导致内存不足,从而导致LMK。我们有两种工具来调查这种情况

RSS高水位线。

内存跟踪点。

RSS高水位线

我们可以从/proc/[pid]/status文件中获得很多信息,包括内存信息。VmHWM 显示进程自启动以来的最大RSS使用量。该值由内核保持更新。

$ adb shell cat '/proc/$(pidof com.android.systemui)/status'[...]
VmHWM:    256972 kB
VmRSS:    195272 kB
RssAnon:  30184 kB
RssFile:  164420 kB
RssShmem: 668 kB
VmSwap:   43960 kB
[...]
内存跟踪点

注意:有关内存跟踪点的详细说明,请参阅{数据源>内存>计数器和事件](https://perfetto.dev/docs/data-sources/memory-counters)页面。

我们可以使用Perfetto从内核获取有关内存管理的信息。

$ adb shell perfetto \
  -c - --txt \
  -o /data/misc/perfetto-traces/trace \
<<EOF

buffers: {
    size_kb: 8960
    fill_policy: DISCARD
}
buffers: {
    size_kb: 1280
    fill_policy: DISCARD
}
data_sources: {
    config {
        name: "linux.process_stats"
        target_buffer: 1
        process_stats_config {
            scan_all_processes_on_start: true
        }
    }
}
data_sources: {
    config {
        name: "linux.ftrace"
        ftrace_config {
            ftrace_events: "mm_event/mm_event_record"
            ftrace_events: "kmem/rss_stat"
            ftrace_events: "kmem/ion_heap_grow"
            ftrace_events: "kmem/ion_heap_shrink"
        }
    }
}
duration_ms: 30000

EOF

当执行命命令时,会不断收集内存的使用情况。

使用adb Pull/data/misc/perfetto-traces/trace~/mem-trace提取文件并上传到perfetto UI。这将显示有关系统ION使用情况的总体统计数据,以及每个进程的统计数据。向下滚动(或按Ctrl-F键)到com.google.android.GoogleCamera并展开。这将显示相机的各种内存统计数据的时间线。

camera_memory.png
我们可以看到,大约2/3的位置,内存飙升(在mem.rss.anon track中)。这就是拍照的地方。这是了解应用程序的内存不同情况使用内存的好方法。
工具的选择

如果想深入研究由Java代码分配的匿名内存(由dumpsys meminfo标记为Dalvik Heap),请参阅分析Java堆部分。

如果想深入研究由native代码分配的匿名内存,该内存由dumpsys meminfo标记为native堆,请参阅分析native堆一节。请注意,即使您的应用程序没有任何C/C++代码,也经常会使用native内存。这是因为某些框架API(例如Regex)的实现是通过native代码在内部实现的。

如果想深入到有文件映射的内存中,最好的选择是使用adb shell showmap PID(在Android上)或检查/proc/PID/smaps。

Low-memory kills

当Android设备的内存不足时,名为lmkd的守护程序将开始终止进程,以释放内存。不同设备的策略不同,但通常进程将按oom_score_adj分数的降序被终止(即首先被终止的是后台应用程序和进程,最后是前台进程)。

Android上的应用程序在切换后不会被终止。相反,即使用户不再使用app,它们也会保持缓存状态。这是为了使应用程序的后续启动更快。这样的应用程序通常会首先被杀死(因为它们具有更高的oom_score_adj)。

我们可以使用Perfetto收集有关LMK和oom_score_adj的信息。

$ adb shell perfetto \
  -c - --txt \
  -o /data/misc/perfetto-traces/trace \
<<EOF

buffers: {
    size_kb: 8960
    fill_policy: DISCARD
}
buffers: {
    size_kb: 1280
    fill_policy: DISCARD
}
data_sources: {
    config {
        name: "linux.process_stats"
        target_buffer: 1
        process_stats_config {
            scan_all_processes_on_start: true
        }
    }
}
data_sources: {
    config {
        name: "linux.ftrace"
        ftrace_config {
            ftrace_events: "lowmemorykiller/lowmemory_kill"
            ftrace_events: "oom/oom_score_adj_update"
            ftrace_events: "ftrace/print"
            atrace_apps: "lmkd"
        }
    }
}
duration_ms: 60000

EOF

使用命令`adb pull /data/misc/perfetto-traces/trace ~/oom-trace提取文件,并上传到 Perfetto UI.

memory_lmk.png
我们可以看到,相机的OOM分数在打开时减少(使其不太可能被杀死),在关闭后再次增加。
分析native内存使用

Native Heap Profiles 需要 Android 10.

注意:有关native堆分析器和疑难解答的详细说明,请参阅数据源>堆分析器页面。

应用程序通常通过malloc或C++的new获得内存,而不是直接从内核获得内存。分配器确保更有效地处理内存(即不会太浪费内存),并且请求内核的开销保持较低。

我们可以记录native分配,并使用heapprofd释放进程所做的操作。生成的profile文件可以用于将内存使用情况归因于特定的函数调用堆栈,支持native代码和Java代码的混合。profile文件仅显示其运行时完成的分配,不会显示之前完成的任何分配。

抓取profile

使用 tools/heap_profile 脚本profile 进程。如果遇到问题,请确保使用的是最新版本。使用tools/heap_profile-h查看所有参数,或使用默认值并profile 一个进程(例如,system_server):

$ tools/heap_profile -n system_server

Profiling active. Press Ctrl+C to terminate.
You may disconnect your device.

Wrote profiles to /tmp/profile-1283e247-2170-4f92-8181-683763e17445 (symlink /tmp/heap_profile-latest)
These can be viewed using pprof. Googlers: head to pprof/ and upload them.

当Profiling运行时,稍稍把玩下手机。之后按Ctrl-C结束profile。对于本教程,我打开了几个应用程序。

查看数据

然后将原始跟踪文件从输出目录上传到Perfetto UI,并单击显示的菱形标记。

profile-diamond.png

可用的选项卡包括

默认视图将显示在profile时完成但未释放的所有分配(空格选项卡)。


memory_profile.png

我们可以看到,通过AssetManager.applyStyle在调用过程中分配了大量内存。要获得调用分配的总内存,我们可以在Focus文本框中输入“applyStyle”。这将仅展示与“applyStyle”匹配的调用堆栈。


native-heap-prof-focus.png
从这里我们清晰的知道我们想要查找的的代码。从代码中,我们可以看到内存是如何使用的,以及我们是否确实需要所有的内存。
分析 Java Heap

Java Heap Dumps 需要系统在Android 11及以上

注意:有关捕获Java堆转储和故障排除的详细说明,请参阅Data sources>Java heap dumps页面。

Dumping the java heap

我们可以获得构成Java堆的所有Java对象的图的快照。我们使用tools/java_heap_dump脚本。如果遇到问题,请使用的最新版本。

$ tools/java_heap_dump -n com.android.systemui

Dumping Java Heap.
Wrote profile to /tmp/tmpup3QrQprofile
This can be viewed using https://ui.perfetto.dev.
查看数据

将文件上传到 Perfetto UI ,然后点击菱形标记。

profile-diamond.png

这将显示对象到GC root的最短路径的内存的火焰图。通常,对象可以通过许多路径到达,我们只显示最短的,因为这降低了所显示数据的复杂性,并且通常是高可信.。最右边的[合并]堆栈是所有太小而无法显示的对象的总和。


java-heap-graph.png

可用的选项卡包括

如果我们只想看到具有包含某些字符串的帧的调用堆栈,则可以使用Focus功能。比如如果我们想知道与notification相关的所有分配,我们可以将“notification”放在“焦点”框中。

与navtive堆配置文件一样,如果我们希望关注图的某些特定方面,则可以按类的名称进行过滤。比如如果我们想查看notification可能关联的内存使用,我们可以将“notification”放在“焦点”框中。


java-heap-graph-focus.png

我们聚合每个类名的路径,因此如果java.lang.Object[]还有很多存活的对象,我们将显示一个元素作为其子元素,正如您在上面最左侧的堆栈中看到的那样。

#####################以上分割线#####################

后记:
1 本次主要使用百度翻译,虽然被骂,但至少翻译这个工具降低了门槛。
2 英文文档中的长难句真的是又长又难,基于百度的翻译,然后自己再调整下,水平实在有限。
3 技术背景知识不够,有些专有名词不知道怎么翻译,也不知道百度翻译的是否准确,功夫在诗外。
4 万事开头难,中间难不难,还不知道。中间的事后面再说,正确一天翻译一篇。
5 虽然可能会有人不屑,但总要有人去做不起眼的小事。
6 google 厉害,这个perfetto 工具也很厉害。君子善假于物也。
7 工具的使用是最简单的入门,背后还有更多的东西值得学习。
8 水平实在有限,闻过则喜,希望有更多的人反馈,期待更好的建议

上一篇下一篇

猜你喜欢

热点阅读