System Tracing

"系统跟踪"就是记录一段时间内的设备活动。系统跟踪会生成跟踪文件,该文件可用于生成系统报告。此报告可帮助开发者了解如何最有效地提升应用或游戏的性能。

在Android平台上,目前可以通过如下途径生成系统跟踪文件:

  • Traceur app: 一款用于将设备活动保存到跟踪文件的Android工具。在搭载Android 10或更高版本的设备上,跟踪文件会以Perfetto格式保存(如下所示)。在搭载较低版本Android系统的设备上,跟踪文件会以Systrace格式保存。

  • Systrace: 平台提供的旧版命令行工具,可记录短时间内的设备活动,并保存在压缩的文本文件中。该工具会生成一份报告,其中汇总了 Android 内核中的数据,例如 CPU 调度程序、磁盘活动和应用线程。

Perfetto是什么

Perfetto是Android 10中引入的全新下一代平台级跟踪工具。

适用于Android、Linux和Chrome的更加通用和复杂的用于性能检测和跟踪分析的生产级开源项目。其核心是引入了一种全新的用户空间到用户空间的跟踪协议,该协议可以直接将protobuf序列化到共享内存缓冲区。且既可用于平台内部的内置数据源(例如ftrace、atrace、logcat),也可通过项目提供的SDK和库暴露给C++应用程序。

同时该协议允许通过一个可扩展的基于protobuf的数据源配置机制对其进行动态配置。不同的数据源可以复用到用户定义的缓冲区的不同子集上,也可以将任意长的跟踪流导出到文件系统中。

它提供了用于记录系统级和应用级活动的服务和库、低开销的native+java内存分析工具,可供SQL分析跟踪文件的库,以及一个基于Web用于将追踪文件可视化方便分析的Perfetto UI[1]

Perfetto
Systrace

为什么使用它

对于习惯了使用systrace的开发者,起初使用perfetto可能会不习惯,虽然目前该工具并不能完全替代systrace,但这只是时间问题(目前该工具代码更新非常频繁),趋势即是如此,而且也的确是很好用的.

相比systrace的优势:

  • 其可记录任意长度的跟踪记录并导出到文件系统中.

  • 更合理的可视化分析标记功能.

  • 内建SQLite数据库,SQL查询的支持,数据后期处理非常灵活.

  • 特定操作方便程度碾压systrace(笑~)

  • 更强的拓展能力,某些特定数据的解析可以通过解析器版本更新支持 比如一开始不支持的syscall,现在支持了.

目前的缺点:

  • 个别操作还是没systrace方便(比如目前为止还不能多选点并自动计算时间差)

  • 暂时没发现了(笑~)

如何使用它

抓取Perfetto跟踪文件:

  • 你可以像atrace一样使用它

但我觉得有点脱裤子放屁的嫌疑,基本不会去这样用.

使用配置文件指导抓取(推荐)

配置文件是最灵活的方式,所有配置项都可按照你的喜好进行配置.

某些android 10的设备上可能要先启动traced:

adb shell setprop persist.traced.enable 1

首先我们需要生成配置文件,这个操作建议在UI页面生成,简单的配置文件如下:

buffers: {
    size_kb: 522240
    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: "android.log"
        android_log_config {
            log_ids: LID_DEFAULT
            log_ids: LID_SYSTEM
        }
    }
}
data_sources: {
    config {
        name: "linux.sys_stats"
        sys_stats_config {
            stat_period_ms: 250
            stat_counters: STAT_CPU_TIMES
            stat_counters: STAT_FORK_COUNT
        }
    }
}
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: "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: "gfx"
            atrace_categories: "input"
            atrace_categories: "view"
            atrace_categories: "wm"
            atrace_categories: "am"
            atrace_categories: "hal"
            atrace_categories: "res"
            atrace_categories: "dalvik"
            atrace_categories: "bionic"
            atrace_categories: "pm"
            atrace_categories: "ss"
            atrace_categories: "database"
            atrace_categories: "aidl"
            atrace_categories: "binder_driver"
            atrace_categories: "binder_lock"
            atrace_apps: "*"
        }
    }
}
duration_ms: 30000

这其中包含:

  • 首先是512M的Buffer配置,当Buffer满了会自动结束抓取.

  • 数据源配置了gpu、logcat、以及ftrace、atrace.

  • 配置此次抓取最长持续时间为30s. 接下来执行如下命令:

adb push perfetto.pbtxt /data/local/tmp/perfetto.pbtxt
adb shell 'cat /data/local/tmp/perfetto.pbtxt | perfetto --txt -c - -o /data/misc/perfetto-traces/trace'

#或者抓取长trace时候可以使用(配置文件也要修改为长trace):
adb shell 'cat /data/local/tmp/perfetto.pbtxt | perfetto --txt -c - -o /data/misc/perfetto-traces/trace  --detach=perf_debug'
#结束抓取:
adb shell 'perfetto --attach=perf_debug --stop'

等待30s后导出/data/misc/perfetto-traces/trace文件即可.

使用Traceur app抓取

这种方式的优点在于使用方便,无需依赖PC只需要界面上点几下即可完成抓取,但是目前应用并不是那么完善,相对的灵活性也没那么高,无法抓取大部分ftrace event.

1.开启开发者选项.

2.进入开发者选项页面.

3.找到"系统跟踪",然后选择打开"显示快捷图块"开关.

3.1(可选) 选择你需要记录的tag:

4.下拉状态栏点击"系统跟踪"图块,然后即可开始抓取.

5.再次点击接口停止抓取并导出文件.

adb pull /data/local/traces .
  • Perfetto UI抓取

很简单,这里就略过了

解析Trace

perfetto抓取的trace文件,只能通过项目内提供的Trace Processor来解析到内置的SQLITE中,针对不同大小的文件解析方法会有差异:

  • 通过官方提供的python api解析

这里就跳过了,感兴趣的建议去官网看下详细的文档.

小文件解析

PerfettoUI这个网站实际上通过WebAssembly技术运行了一个trace_processor,只是浏览器存在内存限制,所以通过WASM只能解析加载较小的文件,目前四大浏览器均已支持该技术:

WASM
PerfettoUI

所以当你尝试加载超大的trace时,就会遇到如下类似的错误:

ERROR

这种情况下就不可以用浏览器直接解析了.

超大文件如何解析

WASM的这个内存限制来自于浏览器本身,所以加载超大文件的方法也很简单了,不要使用WASM去运行trace_processor解析文件就好,这种情况下能解析多大的trace文件取决于你机器的内存大小:

官方已经提供了python脚本自动下载当前操作系统对应的trace_processor(目前仅支持Linux、Macos、Windows后续可能会支持,可以先使用WSL)

# Download prebuilts (Linux and Mac only)
curl -LO https://get.perfetto.dev/trace_processor
chmod +x ./trace_processor

# Start the interactive shell
./trace_processor trace.pftrace

# Start a local trace processor instance to replace wasm module in the UI
./trace_processor -D trace.pftrace 

######################################################################################################################################################################################################################################################### 100.0%
[494.172] processor_shell.cc:1130 Trace loaded: 1137.13 MB (29.6 MB/s)
Error stats for this trace:
                                    name                                      idx                                   source                                    value
---------------------------------------- ---------------------------------------- ---------------------------------------- ----------------------------------------
misplaced_end_event                      [NULL]                                   analysis                                                                    79854
task_state_invalid                       [NULL]                                   analysis                                                                   130057
[494.174] httpd.cc:136            [HTTP] Starting RPC server on 127.0.0.1:9001 and [::1]:9001

此时我们刷新打开perfettoUI会自动检测到9001端口的RPC server:

检测到RPC server

点击"use loaded trace"即可打开9001端口上加载好的trace文件.

tips:如果本地机器内存较小,也可以挂到大内存的工作站上然后把9001端口转发过去即可.

分析Trace

PerfettoUI

  • 如何查看线程唤醒端:

在systrace上,我们一般是通过查看Runnable状态中的wakeup from,然后自己滑到对应的线程区域,如果线程之间唤醒关系比较长,那寻找起来未免太繁琐.

Perfetto的操作方式不同,我们只需要点击Runnable后的Running状态,然后点击下方的跳转按钮,:这会自动跳转到线程调度区域中的对应轨道中,并且下方会显示当前线程的唤醒端(♦) 以及此次唤醒的调度延迟时间:选中唤醒端线程对应的slice,然后同样点击跳转按钮:即可跳转回对应的进程区域的轨道中:

  • 添加标记:

perfetto目前提供两种标记类型,标记的方式分别为: 点击最上方的时间轨道即可添加时间点标记. 而通过按住鼠标左键选中一块区域然后点击"shift+m"即可添加常驻区域标记:通过选中已经添加的标记,我们可以选择为其添加标记名,或者更改其颜色,以及执行移除操作:而如果只是点击"m"添加的是临时区域标记,当你再次选中另外一块区域添加临时区域时,上一个临时区域会自动移除.

指标子系统

当我第一眼看到SQL支持的时候我就在想,是否可以直接使用一段语句提取到我们常见场景的关键性能指标,帮助我们更方便的提取到我们需要的基本性能信息,果然这一点官方也早就想到了,这就是内置的"指标"系统. 项目内置的指标大概有如下这些:

指标名说明
heap_profile_callsites没用过,暂时未知
android_cpu统计出每个进程的CPU情况,包括以线程为粒度的每个核心的占用时间以及核心频率情况.
java_heap_stats没用过,暂时未知
android_lmk统计所有LMK事件指标,并会标记在UI中
android_mem没用过,暂时未知
android_ionion内存指标
android_surfaceflinger统计掉帧指标,并标记到UI中
android_package_listtrace中的package列表
android_gpuGPU内存指标
android_mem_unagg不知道怎么分类的内存指标,暂时没什么用
display_metrics重复帧性能指标,似乎仅限google自家机器使用
android_powrails没用过,暂时未知
trace_metadata打印trace文件的一些基本信息
android_startupandroid应用冷启动或热启动性能指标
android_thread_time_in_state这个有BUG暂时不能使用

除了以上这些,你也可以在系统关键点自行打点,并且通过自定义"指标"的方式,设计仅针对自家设备的性能指标衡量文件.

执行指标:PerfettoUI可以执行内置指标

执行指标分析

如果你想执行自己的指标文件,那么就需要使用如下命令:

./trace_processor --run-metrics <你的指标文件> <trace>

你将得到如下类似的指标分析结果:

output

Sql分析

Sql分析可以直接在PerfettoUI页面执行:

Query

通过SQL分析其可以更高效的过滤trace文件,并且可以完成一些通过图形界面无法很方便完成的统计,

数据库完整的ER图如下:

ER

一共55个table,9个view...关系比较复杂,看着比较吓人而已,实际上我们没必要全部记下来,因为常用的就那几个,并且其结构虽然复杂但是设计的却很合理,下面先解释一下常用的各个"名词",sql表名也是同名的

slice:

slice

简单讲就是你通过: Trace.beginSection/ATRACE_BEGIN记录的事件

counter:

counter

这个顾名思义...

sched:

sched

CPU调度信息就查询这张表,但是这张表只包含Running的任务.其他状态需要自己结合end_state和ts计算或者直接用下面的表.

thread_state:

thread_state

记录线程的完整状态,相当于扩展了的sched表.

tips:

  1. 当你查询slice时发现缺少一些需要的上下文信息,此时请通过track_id去JOIN查询对应的上下文: 例如你想知道你查询的slice其属于哪个线程,此时你就可以通过JOIN thread_track找到线程的utid,然后通过utid JOIN到thread表中找到这条线程的详细信息.

  2. utid 对应 thread表,upid 对应 process表.

简单案例

例如我们想查看某应用在bindApplication阶段的核心分配情况,SQL只需要一句即可:

SELECT 
slice.name,TOTAL(sta.dur)/1e6 as cpu_dur,cpu from slice 
JOIN thread_track ON slice.track_id=thread_track.id
JOIN thread USING(utid)
JOIN thread_state sta ON (sta.utid=thread_track.utid AND (sta.ts >= slice.ts AND sta.ts+sta.dur <= slice.ts+slice.dur ))
WHERE slice.name="bindApplication" AND thread.name LIKE "%news" AND sta.state = "Running"
GROUP BY cpu
ORDER BY cpu_dur DESC

然后你就可以得到如下的输出:

namecpu_durcpu
bindApplication40.1282267
bindApplication38.1367715
bindApplication28.7897396
bindApplication27.3389644
bindApplication2.4630720
bindApplication1.5319792
bindApplication0.6128121
bindApplication0.4027613

工具灵活度反正是很够了~具体怎么活用就看各位大佬发动聪明的脑袋瓜了(认真)~

参考资料

[1]

Perfetto UI: https://ui.perfetto.dev

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐