Android上启动perfetto跟踪

目的

该篇主要介绍如果在Android下面启动perfetto的跟踪,方便后面能快速使用perfetto工具。

启动跟踪服务

启动跟踪服务在Android11(R)之后是默认开启的,在Android9(P)和Android10(Q)上,需要执行以下操作,确保已启用跟踪服务:

1
2
# Needed only on Android 9 (P) and 10 (Q) on non-Pixel phones.
adb shell setprop persist.traced.enable 1

如果是在Android9之前的版本,能通过Perfetto 脚本捕获跟踪记录record_android_trace。

记录跟踪

命令行跟踪

record_android_trace脚本跟踪
  • 首先将record_android_trace脚本下载下来:
    1
    2
    3
    4
    
    //下载脚本
    curl -O https://raw.githubusercontent.com/google/perfetto/main/tools/record_android_trace
    //给脚本分配执行权限
    chmod u+x record_android_trace
    
  • 开始跟踪
    1
    2
    
    ./record_android_trace -o trace_file.perfetto-trace -t 30s -b 64mb \
    sched freq idle am wm gfx view binder_driver hal dalvik camera input res memory
    
  • 命令解释:
    • 上面的脚本中是通过record_android_trace这个脚本收集,接着-o后面跟的是输出的perfetto能识别的文件,-t是跟踪多久的时间,-b后面是最大的文件大小,最后是一堆的category分类。收集完后,它会自动在ui.perfetto.dev上打开该trace文件。上面拼接的分类有如下解释:
      • sched:进程调度(Scheduler)
      • freq:CPU 频率变化(CPU Frequency)
      • idle:CPU 空闲状态(CPU Idle)
      • am:应用生命周期、进程调度等(Activity Manager)
      • wm:窗口管理、Activity 启动等(Window Manager)
      • gfx:图形渲染、SurfaceFlinger、GPU 活动等(Graphics)
      • view:视图绘制、布局、测量等(View System)
      • binder_driver:进程间通信(Binder IPC)
      • hal:硬件抽象层(Hardware Abstraction Layer)
      • dalvik:ART 运行时、GC、JNI 调用等(ART Runtime)
      • camera:摄像头操作(Camera)
      • input:输入事件处理(Input)
      • res:资源加载(Resource)
      • memory:内存分配和使用(Memory)

想获取更多的类别,可以通过如下的指令获取:

1
2
adb shell
atrace --list_categories

atrace –list_categories 列出的主要是 Atrace 类别。这些类别是 Perfetto 中最常用的类别之一。
一些厂商可能会添加自己的自定义类别,这些也会在这里列出。

使用设备上的 /system/bin/perfetto 命令

首先我得设备是Android13的,所以具备通过adb的命令来抓取跟踪文件,需要在开发者选项中选择调试应用为你要调试的应用

接着通过如下命令来跟踪:

1
2
adb shell perfetto -o /data/misc/perfetto-traces/trace_file.perfetto-trace -t 20s \
sched freq idle am wm gfx view binder_driver hal dalvik camera input res memory

此处的命令和上面record_android_trace脚本跟踪基本一致的,跟踪完后,导出该文件到电脑上:

1
adb pull /data/misc/perfetto-traces/trace_file.perfetto-trace ~/trace.perfetto-trace

最后将导出的trace文件拖拽到ui.perfetto.dev中。

通过配置文件来启动跟踪

首先来看下官网给的事例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
cat<<EOF>config.pbtx
duration_ms: 10000

buffers: {
    size_kb: 8960
    fill_policy: DISCARD
}
buffers: {
    size_kb: 1280
    fill_policy: DISCARD
}
data_sources: {
    config {
        name: "linux.ftrace"
        ftrace_config {
            ftrace_events: "sched/sched_switch"
            ftrace_events: "power/suspend_resume"
            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: "gfx"
            atrace_categories: "view"
            atrace_categories: "webview"
            atrace_categories: "camera"
            atrace_categories: "dalvik"
            atrace_categories: "power"
        }
    }
}
data_sources: {
    config {
        name: "linux.process_stats"
        target_buffer: 1
        process_stats_config {
            scan_all_processes_on_start: true
        }
    }
}
EOF

./record_android_trace -c config.pbtx -o trace_file.perfetto-trace 

在EOF之前的命令会把配置写到config.pbtx文件中,在EOF之后,和上面的一样,只不过区别是增加了-c后面跟配置文件的路径,该方式也会自动在ui.perfetto.dev上打开该trace。
下面再来解释下上面的配置文件都配置了些什么:

  • duration_ms: 10000: 跟踪将持续 10 秒
  • buffers: 定义了两个缓冲区。
    • size_kb: 8960, fill_policy: DISCARD(主缓冲区,其中填充策略是丢弃模式)
    • size_kb: 1280, fill_policy: DISCARD(次缓冲区,用来存储其它少量的数据源)
  • data_sources
    • linux.ftrace:ftrace和atrace事件的跟踪
      • ftrace相关的跟踪
        • sched/sched_switch:这是显示 CPU 调度和构建线程 CPU 轨道的最关键事件。 有了这个事件,Perfetto UI 就能知道哪些线程在何时运行在哪个 CPU 上。
        • power/suspend_resume:与电源管理相关。
        • sched/sched_process_exit, sched/sched_process_free, task/task_newtask, task/task_rename:这些事件有助于 Perfetto 跟踪进程和线程的创建、销毁和重命名,从而在 UI 中正确地显示它们的生命周期和名称。
        • ftrace/print: 收集 printk 等内核打印信息,在某些调试场景有用。
      • atrace相关的跟踪
        • gfx,view,webview:图形和ui渲染相关的atrace。
        • camera:摄像头相关的atrace
        • dalvik:ART运行时相关的atrace。
        • power:电源相关的atrace。
    • linux.process_stats:所有进程的状态跟踪,如果没有该配置,生成的perfetto跟踪信息中,都不会显示具体的进程名字,如果想知道
      • target_buffer: 1: 指示此数据源将数据写入第二个(较小的)缓冲区。
      • process_stats_config:
        • scan_all_processes_on_start: true:这将会在跟踪开始时扫描所有进程信息,确保 Perfetto 了解所有活动的进程和线程的初始状态。 再来看一个之前通过perfetto可视化页面获取的一个配置:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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.log"
        android_log_config {
            log_ids: LID_EVENTS
            log_ids: LID_CRASH
            log_ids: LID_KERNEL
            log_ids: LID_DEFAULT
            log_ids: LID_RADIO
            log_ids: LID_SECURITY
            log_ids: LID_STATS
            log_ids: LID_SYSTEM
        }
    }
}
data_sources: {
    config {
        name: "android.surfaceflinger.frametimeline"
    }
}
data_sources: {
    config {
        name: "linux.ftrace"
        ftrace_config {
            ftrace_events: "ftrace/print"
            atrace_categories: "am"
            atrace_categories: "adb"
            atrace_categories: "aidl"
            atrace_categories: "dalvik"
            atrace_categories: "audio"
            atrace_categories: "binder_lock"
            atrace_categories: "binder_driver"
            atrace_categories: "bionic"
            atrace_categories: "camera"
            atrace_categories: "database"
            atrace_categories: "gfx"
            atrace_categories: "hal"
            atrace_categories: "input"
            atrace_categories: "network"
            atrace_categories: "nnapi"
            atrace_categories: "pm"
            atrace_categories: "power"
            atrace_categories: "rs"
            atrace_categories: "res"
            atrace_categories: "rro"
            atrace_categories: "sm"
            atrace_categories: "ss"
            atrace_categories: "vibrator"
            atrace_categories: "video"
            atrace_categories: "view"
            atrace_categories: "webview"
            atrace_categories: "wm"
            atrace_apps: "com.example.coroutinescopedemo"
        }
    }
}
duration_ms: 10000

主要来说下和上面官网的配置区别。主要在data_sources部分:

  • android.packages_list:收集设备上安装的应用程序包的列表。这对于在 Perfetto UI 中将跟踪事件与特定的应用程序关联起来非常有用。
  • android.log:收集android logcat日志。这对于调试应用程序行为、查看系统事件、崩溃信息等非常重要。
    • android_log_config: 详细配置了要收集的日志缓冲区:
      • LID_EVENTS: 事件日志(二进制格式的系统事件)。
      • LID_CRASH: 崩溃日志。
      • LID_KERNEL: 内核日志。
      • LID_DEFAULT: 主日志缓冲区(应用程序和大部分系统日志)。
      • LID_RADIO: 无线电/电话相关的日志。
      • LID_SECURITY: 安全相关的日志。
      • LID_STATS: 统计信息日志。
      • LID_SYSTEM: 系统日志。
    • 这几乎涵盖了所有主要的 Logcat 日志缓冲区,会收集大量的日志信息。
  • android.surfaceflinger.frametimeline:收集 SurfaceFlinger 的帧时间线数据。这对于分析 UI 渲染性能、卡顿、Jank 等问题非常关键。它能显示帧的生命周期,从应用提交到最终显示在屏幕上的各个阶段。
  • linux.ftrace:和上面对比,增加了atrace_apps信息,这是非常重要的。它会启用针对特定应用程序 com.example.coroutinescopedemo 的应用层自定义 app 类别跟踪。如果该应用使用了 android.os.Trace 或类似的 Tracing SDK 来添加自定义事件,那么这些事件将被捕获。

关于更多的配置可以参考repo上的test/configs

上面是通过record_android_trace脚本,然后配置config.pbtx来跟踪的,我们也可以不借助record_android_trace脚本,直接使用设备上的命令:

1
cat config.pbtx | adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/trace.perfetto-trace

或者先把config.pbtx给推送到设备上,然后再跟踪:

1
2
adb push config.pbtx /data/local/tmp/config.pbtx
adb shell 'cat /data/local/tmp/config.pbtx | perfetto --txt -c - -o /data/misc/perfetto-traces/trace.perfetto-trace'

通过 Perfetto 界面记录跟踪

点开ui.perfetto,dev,然后点击左侧菜单的Record new trace,然后连接上设备:

来到Buffers and duration,这里默认的缓冲区是64M,duration是10s,我们可以选择不动:

接着配置前面说的ftrace和atrace信息,它是在Android apps & svcs的tab中,但是此处我只看到atrace的配置:

可以看到上面我把所有的atrace的分类都给勾上了。在最后一栏中我填上了进程的名字,这个跟上面config.pbtx的atrace_apps配置是一样的,用来跟踪自定义的trace信息。

接着来看下android log的数据源配置,它也是在Android apps & svcs的tab中:

可以看到我这里把所有的log级别都给勾上了。

在Event log下面还有个Frame timeline的跟踪,它是我们卡顿分析中很重要的一项,它会把每一帧的期望时间和实际时间给列出来,方便分析是否是卡顿帧:

它会在config.pbtx中生成如下:

1
2
3
4
5
data_sources: {
    config {
        name: "android.surfaceflinger.frametimeline"
    }
}

这个在前面的config中有介绍。所有的配置完事后,可以在Cmdline instructions的tab中浏览生成的配置信息。最终生成的也是前面介绍的config.pbtx: alt text

通用操作

alt text

导航操作

w:放大操作
s:缩小操作
a:轨道左移
d:轨道右移

鼠标操作

鼠标左键点击:选中区域或片段
control+滚轮:放大或缩小
鼠标左键长按+拖拽:选中区域
shift+左键长按+拖拽:轨道左移或右移

命令行操作

在头部的搜索框中输入「>」,进入快捷操作,点击delete退出快捷操作
在头部的搜索框中输入「:」,进入sql模式,点击delete退出sql模式
enter:在上面输入完sql后,就执行sql
control+enter:在上面输入完sql后,就执行sql,每次的sql都不会被覆盖

sql页面操作

control+enter:执行sql,也可以通过鼠标选中某段sql来执行结果

快捷键操作

shift+m:基于当前选中的区域或片段添加一个新的笔记片段
R:对当前选中的区域或片段转为区域选择
esc:取消当前选择的区域或片段
command+p:通过名字找轨道
f:将画面聚焦到当前选中的区域或片段
回车:对页面上的搜索结果(command+f)进行定位到下一个
shift+回车:对页面上的搜索结果(command+f)进行定位到上一个
command+shift+p:打开快捷操作,跟上面在搜索框中输入「>」一样的效果
command+o:打开trace文件
command+t:置顶轨道
delete:移除掉选中的笔记片段
/:聚焦到搜索框
.:定位到下一个轨道的片段
,:定位到上一个轨道的片段
m:根据当前选中的区域或片段,添加临时的笔记片段,和上面的shift+m的区别是,此处如果再添加另外的笔记片段的时候,此时这种添加的笔记片段会被覆盖
q:切换底部的面板
command+b:切换左边的菜单面板
command+option+s:多个trace文件在浏览器中打开的时候,能同步多个trace的操作,方便对比

常见sql

模糊匹配查询:like、glob、regexp

like:一般跟%和_来搭配使用,是不确定数量字符个数,_是匹配单个字符,并且like前面可以加not取反,比如我想查询所有fragment的onCreateView的耗时:

1
SELECT * FROM slice WHERE name LIKE '%onCreateView%';

在like中是不区分大小写。 glob:一般和*、?、[…]搭配使用,*表示任意长度的字符串、?表示单个字符、[…]表示字符集。它是要区分大小写。并且前面也可以用not取反。

1
2
3
4
SELECT *
FROM slice
WHERE name GLOB '*interesting_slice*'
LIMIT 10;

regexp:是一种以正则来匹配的关键字,下面看一个统计slice的耗时sql:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
SELECT
  name,
  COUNT(dur) AS count_slice,
  -- Convert nanoseconds to milliseconds
  AVG(dur) / 1000000 AS avg_dur_ms,
  CAST(MAX(dur) AS DOUBLE) / 1000000 AS max_dur_ms,
  CAST(MIN(dur) AS DOUBLE) / 1000000 AS min_dur_ms,
  PERCENTILE(dur,50) / 1000000 AS P50_dur_ms,
  PERCENTILE(dur,90) / 1000000 AS P90_dur_ms,
  PERCENTILE(dur,99) / 1000000 AS P99_dur_ms
FROM slice
WHERE name REGEXP '.*interesting_slice.*'
GROUP BY name
ORDER BY count_slice DESC
LIMIT 10;

上面的sql通过regexp正则来匹配到感兴趣的slice,并且开头和结尾都是.*来匹配,该.表示匹配单个字符,而*则是匹配前一个字符是0次或多次,所以表示前面interesting_slice前面有任意个数的字符,interesting_slice后面有任意个数的字符。并且按照名字分组,列明有名字、count_slice表示该slice出现的次数、avg_dur_ms该slice的平均耗时,单位是ms、max_dur_ms最大耗时、min_dur_ms最小耗时、P50_dur_ms是p50耗时、P90_dur_ms是p90的耗时、P99_dur_ms是p99的耗时,最后按照出现次数降序排列,查询前10条。

联表查询

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
SELECT
  s.id AS id,
  s.ts AS ts,
  s.track_id AS track_id,
  s.slice_id AS slice_id,
  s.dur AS dur,
  s.name AS slice,
  p.name AS process,
  t.name AS thread
FROM slice s
JOIN thread_track tt ON s.track_id = tt.id
JOIN thread t on tt.utid = t.utid
JOIN process p on t.upid = p.upid
WHERE s.name LIKE '%interesting_slice%'
-- Only look for slices in your app's process
AND p.name = 'com.example.myapp'
-- Only look for slices on your app's main thread
AND t.is_main_thread
ORDER BY dur DESC;

上面thread_track(线程轨道表)的id和slice的trace_id进行关联,thread(线程表)的utid(线程唯一id)和thread_track的utid进行关联,progress(进程表)的upid(进程唯一id)和thread的upid进行关联。

通过sql查找睡眠中断的原因

睡眠中断是性能优化中一个阻塞线程的问题,比如在等待io、锁等都是睡眠中断问题,即调用在不可中断条件下阻塞的内核函数。我们可以通过配置perfetto如下来找出睡眠中断的原因:

1
2
3
4
5
6
7
8
9
data_sources: {
    config {
        name: "linux.ftrace"
        target_buffer: 0
        ftrace_config {
            ftrace_events: "sched/sched_blocked_reason"
        }
    }
}

然后通过sql查找中断的原因:

1
2
3
4
5
6
7
SELECT blocked_function, COUNT(thread_state.id), SUM(dur)
FROM thread_state
JOIN thread USING (utid)
JOIN process USING (upid)
WHERE process.name = "com.google.android.youtube"
GROUP BY blocked_function
ORDER BY SUM(dur) DESC;

blocked_function:它是来自于thread_state表,它是线程状态表,表示线程在某个时刻阻塞时,对应的函数。 COUNT(thread_state.id):统计该阻塞问题出现的次数 SUM(dur):统计这种阻塞出现的总时长 注意:上面通过join thread using(utid)是通过thread_state中的utid和thread表中的utid进行关联,将thread中的upid和process中的upid进行关联,通过threa表进行中间过渡。

参考

Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy