Android Perfetto Trace性能分析

avatar
作者
筋斗云
阅读量:0

版权声明:转载必须注明本文转自严振杰的博客:http://blog.yanzhenjie.com

本人用 Perfetto 快 2 年了,公司一些同事和网友都来咨询过使用姿势,本人只能把自己一个略显粗糙点的笔记发给同事,有点不够细节和不够深度,于是趁着清明假期,我把经验总结成一篇博客,结合 Perfetto 官网资料取长补短尽量做到深入浅出。

Perfetto 支持多个平台,包括 Linux、Android 和 Chrome,并提供了用于记录系统级和应用级活动的服务和库、低开销的针对 Native 和 Java 的内存分析工具、可供 SQL 分析跟踪文件的 C++库和 Python 库(Python 基于 C++库),以及基于 Web 可视化方便分析 Trace 文件的 Perfetto UI。

聪明的同学从上面的信息中可以了解到很多信息了,下面咱们就展开细节聊一聊。

1、生成 Perfetto 抓 Trace 的配置

做 App 性能分析,一般关注这几类信息:

  1. Memory。
  2. CPU。
  3. GPU。
  4. Power。
  5. 系统层消耗。

我们在抓 Trace 的时候可以指定应用、内存、CPU、GPU、电源和系统事件等,使用 Perfetto 时可以使用 PerfettoUI 来生成配置:https://ui.perfetto.dev/#!/record

第一步,选择目标 Android 设备系统版本。打开 Perfetto UI,选择要抓 Trace 的目标设备,有 2 种方式,如图一所示:

  • 方式一,选择目标系统版本。
  • 方式二,选择 ADB 连接的设备。

(图一)

第二步,调整 Trace 文件记录设置。主要是为了控制 Trace 文件大小或者 Trace 记录时长,如果文件太大则加载缓慢操作也不太顺畅,如图二所示:


(图二)

第三步,配置要分析的探针信息。

收集 CPU 相关信息,需要点击在 Probes 下的 CPU,然后根据需要打开对应开关并做配置,如图三所示:


(图三)

收集 GPU 相关信息,需要点击在 Probes 下的 GPU,然后根据需要打开对应开关,如图四所示:


(图四)

收集电源信息和内存信息同上,不再赘述。

这里需要强调收集 atrace 的配置,选中 Probes 下的 Android apps & avcs,如图五所示:


(图五)

按照以下操作步骤和注意项:

  1. 打开 atrace 开关。
  2. 默认是抓取所有 app trace,可以关闭Record events from Android apps and services,在下面添加目标 App 包名。
  3. 在 Categories 下选择你关注的 trace 类型。

第四步,选择【Recording command】保存配置,一共有 3 种方式。

方式一,保存在本地,打开【Recording command】的页面后,点击按钮复制配置内容,如图六所示:


(图六)

方式二,在 Perfetto UI 持久化保存,如图七所示:


(图七)

方式三,生成远端链接分享给别人下载,如图八所示:


(图八)

我们可以多尝试下配置会更加熟悉和了解细节,保存后可以根据文档手动增删改一些配置(见下文),比如我们主要关注 CPU 信息时,做好对应设置生成的配置如下:

buffers: {     size_kb: 63488     fill_policy: DISCARD } buffers: {     size_kb: 2048     fill_policy: DISCARD } data_sources: {     config {         name: "android.packages_list"         target_buffer: 1     } } data_sources: {     config {         name: "android.gpu.memory"     } } data_sources: {     config {         name: "linux.process_stats"         target_buffer: 1         process_stats_config {             scan_all_processes_on_start: true         }     } } data_sources: {     config {         name: "linux.sys_stats"         sys_stats_config {             stat_period_ms: 1000             stat_counters: STAT_CPU_TIMES             stat_counters: STAT_FORK_COUNT             #cpufreq_period_ms: 1000         }     } } data_sources: {     config {         name: "linux.ftrace"         ftrace_config {             ftrace_events: "sched/sched_switch"             ftrace_events: "power/suspend_resume"             ftrace_events: "sched/sched_wakeup"             ftrace_events: "sched/sched_wakeup_new"             ftrace_events: "sched/sched_waking"             ftrace_events: "power/cpu_frequency"             ftrace_events: "power/cpu_idle"             ftrace_events: "power/gpu_frequency"             ftrace_events: "gpu_mem/gpu_mem_total"             ftrace_events: "power/gpu_work_period"             ftrace_events: "raw_syscalls/sys_enter"             ftrace_events: "raw_syscalls/sys_exit"             ftrace_events: "sched/sched_process_exit"             ftrace_events: "sched/sched_process_free"             ftrace_events: "task/task_newtask"             ftrace_events: "task/task_rename"             ftrace_events: "ftrace/print"             atrace_categories: "am"             atrace_categories: "aidl"             atrace_categories: "dalvik"             atrace_categories: "binder_lock"             atrace_categories: "camera"             atrace_categories: "database"             atrace_categories: "gfx"             atrace_categories: "network"             atrace_categories: "sm"             atrace_categories: "ss"             atrace_categories: "view"             atrace_categories: "webview"             atrace_apps: "com.taobao.android"         }     } } duration_ms: 40000 

实际运行时,因为手机系统版本不一致原因,可能遇到以下错误:

-:36:13 error: No field named "cpufreq_period_ms" in proto SysStatsConfig             cpufreq_period_ms: 1000 

如上面给出的配置示例,注释对应配置行后再尝试。

在实际操作中,会根据不同的场景和需求对抓 Trace 的配置文件做修改,如何修改可以参考官网文档(https://perfetto.dev/docs/)数据源配置,文档位置如图九所示:


(图九)

生成后好放在电脑的本地(比如桌面),可以把它命名为 perfetto.pbtx。

2、按照配置抓 Perfetto Trace

让手机抓 Perfetto Trace 有 3 种方式:

  1. 使用手机的 Traceur app 抓取,很多国产手机没有,本文不做介绍。
  2. 使用 adb 命令抓取。
  3. 使用 Perfetto UI 抓取。
  4. 使用 Python 脚本抓取(推荐)。

值得注意的是 Perfetto 从 Android 9(P)开始集成,从 Android 11(R)开始默认开启。在 Android 9(P)和 Android 10(Q)上需要先确保开启 Trace 服务:

adb shell setprop persist.traced.enable 1 

如果你要在 Android 9(P)之前的设备上使用 Perfetto,请参考官网文档:
https://perfetto.dev/docs/quickstart/android-tracing#recording-a-trace-through-the-cmdline

如果执行了抓取后,没有抓到对应 App 的 Trace,启用systrace VERBOSE再试试:

adb shell setprop log.tag.enableSystrace VERBOSE 

2.1、使用 adb 命令抓取

  1. 通过 adb 把配置推送到手机:
    • adb push ~/Desktop/perfetto.pbtx /data/local/tmp/perfetto.pbtx
  2. 使用 adb 让手机以指定配置抓 Perfetto Trace:
    • adb shell 'cat /data/local/tmp/perfetto.pbtx | perfetto --txt -c - -o /data/misc/perfetto-traces/trace'
  3. 结束抓取:
    • adb shell 'perfetto --attach=perf_debug --stop'

抓取完成后,使用 adb 命令导出 Trace 文件到电脑,进行分析即可。该方式操作步骤多略显繁琐,根据实际情况作为备用方式。

2.2、使用 Perfetto UI 抓取

使用 Perfetto UI 抓取 Trace 时,选择目标 Android 设备系统版本时,需要选择 ADB 连接的设备,在 Perfetto UI 上配置完成后,不用保存到本地,直接点击【Start Recording】开始录制,如图十所示:


(图十)

在 Perfetto UI 抓取 Trace 时不能手动结束,需要等在【Recording Setting】中设置的【Max Duration】倒计时结束,当我们点击了录制后会看到如下提示:

Recording in progress for 40000 ms... 

录制结束后,Perfetto UI 会直接在 Web 界面打开 Trace 文件,我们可以直接进行分析。该方式比较方便,配置可以在 Perfetto UI 持久化保存,美中不足的是 Perfetto UI 抓到的 Trace 文件不能下载发给别人。

2.3、使用 python 脚本抓取

使用 python 脚本抓取到 Trace 后,会把 Trace 文件保存到本地,也会自动在浏览器通过 Perfetto UI 直接打开 Trace 文件,我们直接进行分析。

使用 python 脚本抓取时需要满足以下几个条件:

  1. Android 设备通过 adb 连接到电脑。
  2. 把 python 脚本保存在本地,在本地运行 python 脚本。
  3. 把抓 Trace 的配置保存在本地,运行 python 脚本时需要指定配置文件。

python 脚本在 GitHub 上的开源地址,可以通过浏览器看源码:
https://github.com/google/perfetto/blob/main/tools/record_android_trace
raw.githubusercontent.com 下载链接:
https://raw.githubusercontent.com/google/perfetto/master/tools/record_android_trace

现在我们把 python 脚本和抓 Trace 的配置放在桌面,命名和目录结构如下:

~/Desktop$ ├── perfetto.py ├── perfetto.pbtx 

此时我们手机与电脑通过 adb 连接,然后运行以下命令抓取 Trace:

python3 perfetto.py -c perfetto.pbtx -o trace_file.perfetto-trace 

上述命令中,-c是指定配置文件位置,-o是指定 trace 文件保存位置。

运行命令后,我们开始操作 App,然后觉得抓取到目标 Trace 了,按下ctrl + c结束即可,此时 Trace 文件会被放在-o指定的位置,且 Perfetto UI 会被自动打开,我们直接进行分析即可。

而且我们不用担心抓的 Trace 太大网站内存绷不住,因为 python 脚本会自动下载当前系统的 trace_processor 来解析,细节可以从官网了解。
https://perfetto.dev/docs/visualization/large-traces

3、Trace 分析

先大概了解下 Trace 的基本概念,一个 Trace 段在 Perfetto UI 中用 Slice 表示,它并不代表一个方法,而是一个 Trace 的开始到结束,需要在代码中标记。

Trace.beginSection("Choreographer#doFrame"); ... Trace.endSection(); 

如上述代码所示,一个 Trace 段必须是一个Trace#beginSection(String)对应一个Trace#endSection()

3.1、使用图形界面分析

正式操作之前先掌握一些基本操作技巧。

基本操作 1,打开 Trace 文件后,可以整体上观察哪些线程有问题,比如耗时久Uninterruptiable Sleep (non-IO)等,定位到有问题的线程或者 Trace 段后,我们就可以对线程和 Trace 段进行更详细的分析了,如图十一所示:


(图十一)

基本操作 2,在对线程和 Trace 段进行详细分析之前,对要分析的进程和线程做下钉操作(如图十二所示):

  1. 在列表中点击要分析的 App 进程,点击后该进程会展开子列表,展示所有线程。
  2. 找到RenderThread(渲染线程)并点击名称右侧的钉图标,把该线程钉在列表顶部。

(图十二)

这样做的目的是方便观察主线程渲染线程发生了什么,找到性能瓶颈所在。

下面我们一起看 3 个例子。

3.1.1、Uninterruptible Sleep (non-IO)

我们看一个最经典的方法耗时分析,看过 Framework 源码的同学都知道系统层Choreographer#doFrame()是执行一帧绘制(包括layoutmeasuredraw),如果我们页面操作时(比如浏览信息流)略显卡顿,那么必定存在某一帧或者更多帧耗时久的情况。用 Perfetto UI 打开 Trace,一眼就能看到有些些 Trace 段比较长,选中该段 Trace(这个 Trace 是系统层加的),如图十三所示:


(图十三)

观察 Slice Details 后会很一目了然的看到该 Trace 的耗时:

  1. 看到这一帧耗时 177ms,很明显这么久的一帧会肉眼可见的卡顿一下。
  2. 查看 Duration 列表,可以看到有意外,存在耗时较久的SleepingUninterruptible Sleep (non-IO)
  3. Running 比较久,可以点击箭头展开查看细节。

很明显,主线程存在不符合预期的Uninterruptible Sleep (non-IO)状态,这个 case 中比较明显的棕色段就是Uninterruptible Sleep的信息(不明显的放大横轴段就能看到)。

重点来了,如何定位是什么原因引起的 Uninterruptible Sleep?我们可以这样操作,以Uninterruptible Sleep为中心放大横轴,到足够大了可以看到 App 主线程行慢慢露出RunnableRunning,如图十四所示:


(图十四)
  1. Runnable不表示线程,而是表示当前线程可运行了。
  2. Running表示当前线程正在运行。

此时我们选中距离Uninterruptible Sleep最近的Runnable,如图十五所示:


(图十五)

Selection Details中可以看到Waker信息,它表示唤起当前线程(这里是主线程)的线程,可以看到这里是dp2ndk线程阻塞了主线程。同时,我们在 App 进程子列表中找到dp2ndk线程,并钉住。此时dp2ndk、渲染线程、主线程等就会同时钉在顶部,方便我们做对比确认信息。

从图十五可以看到,dp2ndk线程占用了 CPU,这里需要做优化。

3.1.2、monitor contention with owner

在 Perfetto UI 打开 Trace,放大缩小横轴到适当大小,横向滑动 Trace 段,可以看到类似monitor contention with owner [xxx]的 Trace 段,它会阻塞主线程或者渲染线程,点击主线程或者渲染线程后面的Runnable观察Waker,可以看到是哪个线程阻塞的,如图十六所示:


(图十六)

此时我们选中距离Uninterruptible Sleep最近的Runnable,从图十六的Slice Details中可以看到,NetworkKit-GRS_线程占用了 CPU,这里需要做优化。

3.1.3、Lock contention on InternTable lock

Lock contention on [xxx]的类型比较多,都属于锁操作,在 App 启动过程中或者页面操作过程中非常常见,如图十七所示,发现某个 Trace 耗时较长时,单独选中该段 Trace,在 Slices 中会列出子 Trace 段列表,可以更好的看出耗时 Trace。

类似Lock contention on InternTable lock的渲染帧会影响页面流畅性,造成 App 卡顿,如图十七所示:


(图十七)

3.2、使用 SQL 聚合分析

官网文档:

使用图形界面只能一个个 Case 具体分析,当我们梳理出具体的多个 Cas 时,有时候需要拿到聚合数据给对应同事做优化,可以使用 SQL 查询。

例如,统计名为renderConvertView[smart_ui_jiangliu]的 Trace 的平均耗时:

SELECT name, AVG(dur)/1000 AS dur FROM slice where name = 'renderConvertView[smart_ui_jiangliu]' GROUP BY name 

结果如图十八所示:


(图十八)

例如,统计名为Lock contention on thread list lock (owner tid: 4054)的 Trace,并按照耗时降序排序:

SELECT ts, dur, name FROM slice where name = 'read from memory' ORDER BY dur DESC 

结果如图十九所示:


(图十九)

4、结语

本文介绍了 Perfetto 配置生成、抓取 Trace、使用 Perfetto UI 分析 Trace 三块内容。在 Trace 分析时主要介绍了基于 CPU 的分析,没有介绍 GPU、内存、电量分析的细节,因为日常工作中本人用的不是很多,所以暂时写不了就不误导读者了,有机会再补充一篇文章。

本文介绍的内容是不到官网的 1/3 的,但是一些细节也是官网没有的,很多人在实操时就卡在这些细节上,这也是我写这篇文章的原因。

感兴趣的读者可以加我的 QQ 交流群:46523908

本文完,感谢阅读。

版权声明:转载必须注明本文转自严振杰的博客:http://blog.yanzhenjie.com

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!