Perfetto工具集之traced

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

1.Perf工具概览

linux中包含了众多性能分析工具perf特指linux-tools perf工具是2009年在linux内核2.6.31中引入的一个工具。它的主要功能是可以跟踪hardware performance counter(PMU)、tracepoints、software performance counter(hrtimer)、dynamic probes等信息。linux内核将这些信息进行封装通过syscallperf_event_open等的形式提供使之抽象为events的概念可以供userspace的进程使用。perf作为一个linux下的命令行工具可以读取这些events并结合性能分析的场景提供了诸如stats、top、record、report等子工具命令适配更细化的分析需求。

Android中一般使用的并不是老牌的linux-tools perf工具而是使用经过Android客制化的perf工具用于支持Android中拓展的一些feature。

  • simpleperf:

Android最早于Android 6.0(2015年)中引入距今2022年已经有7年的历史。其主要作用就是实现Linux中perf工具的基本功能。

  • traced_perf:

Google于2019[在2019年开始开发][修改了一下]年开始开发其作为perfetto的一个consumer而不是单独的一个项目去开发的。其开发目的是能够

a.利用perfetto的成熟平台提供profiling、unwinding、UI等各方面的能力

b.伴随着Android权限管控的愈发严格和MAC的要求原Simpleperf的独立selinux domain完成所有功能的方式已经无法满足sandbox的需求需要进行严格的domain隔离

本文着重讲一下traced_perf。

2.traced_perf的结构

2.1.代码结构

traced_perf的代码位于AOSP的external/perfetto/src/profiling/perf/ 目录下可以看出traced_perf的代码实际上是perfetto项目的一个子目录。

此目录下的代码如下图

c692aac8dad9f5280059e1be7aa41ef8.png

可以看出代码分为三类

  • 编译脚本相关: BUILD.gn

  • 单元测试相关:X_unittest.cc

  • 主要代码逻辑

除了上述的代码目录外在perfetto的主目录下还存在文件external/perfetto/traced_perf.rc

此文件是traced_perf可执行文件的启动脚本。

2.2运行时结构

根据external/perfetto/Android.bp的编译脚本可以看出traced_perf最终会被编译为一个可执行文件并且被install到/system/bin/traced_perf。此可执行文件以daemon的形式存在其启动和结束受它对应的rc启动脚本的控制。

2c463cf967b40c94849bc51206cc37d3.png

38649ef06ee1b8b54be7001e51c7b385.png

其运行时的生命周期可以通过traced_perf.rc文件去分析

7f6a01d7e6dd0428bb368c3fbca7a0ae.png

88bde971d3e3a42a108c8f84e5a4bab9.png

  • traced_perf的权限配置

▫traced_perf的用户设置为了nobody可以确保权限不会影响到其他用户避免被恶意破解后获得提权

▫traced_perf的组包含nobody、readproc、readtracefs三个readproc是为了使之被赋予可以读取/proc/PID目录的权限readtracefs是为了赋予其读取tracefs mount的目录的权限这两个权限是traced_perf能够正常运行所必须的权限。

▫traced_perf赋予了相应的capability分别为KILLDAC_READ_SEARCH。KILL是为了使traced_perf能够给其他进程发送信号。DAC_READ_SEARCH是为了使之能够至少能够获取一些文件的权限而不至于甚至不能够探测某些文件的存在。这两个权限都是traced_perf正常运行所必需的。

▫task_profiles是为了给traced_perf设置为高capacityunwinding的一类cgroup从而使得调度器可以给予其更合理的资源分配。

  • traced_perf的资源

traced_perf申请了一个名为traced_perf的unix_socket此unix_socket是traced_perf与待profiling的进程间通信的通道后续章节有涉及。

  • traced_perf的生命周期管控

traced_perf的生命周期管控通过property trigger来完成。当设置persist.traced_perf.enable 为true的时候会自动启动traced_perf。同时它还会受到sys.init.perf_lsm_hooks和traced.lazy.traced_perf的管控。

3.traced_perf的架构

3.1perfetto的框架

traced_perf作为perfetto工具集的一个组成部分其遵循perfetto的service model的。perfetto的service model如下图所示

7f064e73b6d31678d129d614fbebed45.png

3.1.1producer

traced_perf作为Tracing service的producer其和tracing service的交互由两条通道分别是IPC channel和shared_memory其中IPC channel为unix socket后面有详细描述。

shared_memory是指与tracing service之间建立的共享内存通道此共享内存通道有两个作用

1.进行高效的进程间数据传递这里传递的主要是结构化的采样点数据。

2.与控制流进行隔离避免被恶意破解后造成安全隐私风险。

traced_perf本身作为producer端提供了Data Source每个producer可以提供多种Data Source。traced_perf本身对外提供的Data Source包含linux.perf 和一个metadata的Data Source。后续我们对此Data Souce展开详细的描述。

3.1.2Tracing Service

Tracing Service作为perfetto在手机端的核心服务其承担了主控的作用。Tracing Service在手机端主要表现为traced进程它一方面接收consumer的配置文件的控制另外一方面将配置文件转化为对Producer的控制同时还承担了producer端与consumer端的桥梁。producer端与consumer的数据通道采用了trace buffer内存这部分内存是没有进行进程间共享的从而可以保持数据的隔离。

cb2aa73a01b01f3030f3cbdba7a9faa3.png

3.1.3Consumer

consumer端是指对perfetto trace类数据的消耗端比如perfetto ui、shell command、traceur等consumer端还可以进行自定义在Android中添加客制化的consumer从而对Data Source进行客制化的处理。

consumer端和Tracing Service的IPC通道主要也是通过unix socket进行连接的。

3.2traced_perf与perfetto的交互

3.2.1整体流程

整体流程示意图如下读者可以根据此流程理解。交互流程涉及到Perfetto内部的众多类的实现建议读者优先理解涉及到的C++类的声明与函数实现而不要刚开始就陷入到调用流程的跟踪中避免陷入到多层嵌套的复杂逻辑中。当把几个关键类的功能和对外关系理清楚之后再通过调用关系依次跟踪调用流程。

626ef7fb28ecef689b14306c56dc4ca9.png

  • PerfProducer:

调用ConnectService建立连接流程

实现OnConnect流程

实现OnTracingSetup、StartDataSource等函数

  • ProducerEndPoint

创建建立进程间通信的必要对象

实现OnConnect

  • ClientImpl

建立Socket连接

实现onConnect、onDataAvaliable等

通过上述流程拆分可以看出每个类的职责都是非常清晰的。

3.2.2IPC通道建立

对于traced_perf来讲建立IPC通道由下面几个重要流程

1.实例化task_runner和AndroidRemoteDescriptorGetter。task_runner是traced_perf中使用的一个Looper工具类实例AndroidRemoteDescriptorGetter是traced_perf为了获取想要trace的应用的私有进程数据而建立的一个类。后续章节有相关描述。

2.与Tracing Service建立连接

3.启动消息循环

16bb8f7117381b8322654a6174b8b8a5.png

3.2.3IPC通道框架

IPC通道的框架相对来说比较复杂本小节进行一个原理剖析。

  • TaskRunner: 是一个Looper interfacePerfProducer使用的实例是基于unix domain socket实例化的TaskRunner。此task_runner_在各个结构间传递承担了各类消息的派发和处理。

  • ProducerEndPoint: 是Tracing service的producer端的接口类通过ProducerIPCClientImpl得以实例化。

为了能够将PerfProducer类注册为Tracing Service的producer需要执行如下操作

08d4adab88d6d6f4de8c8e14e87dffd5.png

其中ProducerIPCClient::Connect是一个静态方法其实例化了ProducerIPCClientImpl并将其以unique_ptr的形式返回。

902590daffe1e9a6797f6c534514da7a.png

d473bd0e01154954001a8c41e28c8d1d.png

上述流程走完之后实际上就建立了PerfProducer的事件处理流程。

关注到ConnectService中ProducerIPCClient::Connect中的第二个参数是this指针实际上是把PerfProducer的对象指针传递给了ProducerEndpoint对象它是通过ProducerIPCClientImpl构造函数中的第三个参数producer传递过去的。

3.2.3.1相关概念

  • DataSource

顾名思义这个是数据源的意思。根据Perfetto的框架图consumer端需要指明从哪个“数据源”收集数据而Producer可以提供数据源。数据源在perfetto中的定义以proto的形式进行了规定在PerfProducer中它对数据源的定义进行了抽象通过DataSourceState进行描述。

dfc863f18ba08daaf27f48e89cb9e63e.png

5db27af76dcc189185ed7dbe5591f887.png

fdc5d6850fa18bb6da16b0df3a69efb8.png

865c4dd0b6ccc74bdb08d2a88ba83892.png

b02d2d199c895fa8eab5991c4e3e4303.png

与DataSource相对应的一个数据结构是traced_perf里面的DataSourceState的结构可以看到DataSourceState中维护了一个TraceWriter指针此TraceWriter提供了写入Trace数据的相关方法。

b504b3c40239a248ea13c78676888200.png

1fce7bc5ea42a563cdb39ca11f01fddf.png

3.2.3.2TraceWriter

TraceWriter类是为了让使用者可以在perfetto的共享内存中以零拷贝的形式写入Trace数据方便使用者高效写入Trace数据。

  • NewTracePacket

创建一个TracePacket并返回一个handle

  • FinishTracePacket

完成之前创建的TracePacket

  • Flush

将TracePacket刷入到service端

3.2.3.3IPC消息的接收

ProducerEndPoint对象会通过PerfProducer对象提供的service_sock_name与PerfProducer进行通信当连接建立之后就进入了IPC流程服务端会将消息按照perfetto定义的协议格式发送对应的指令。消息协议如下

5e110bb624c88a99160c16d60ff2dff0.png

23479d3fb1c82b2eb77e956a20f02797.png

e9101a8f513047b81ea261f2d5fb9e23.png

上述消息会被ProducerEndPoint解析并最终转化为Producer接口类的虚函数调用注意到ProducerEndPoint维护了一个Producer(PerfProducer)实例的指针。

Producer的实例需要实现如下接口

  • OnConnect

当与Tracing Service建立Socket连接后会被调用

  • OnDisconnect

当与Tracing Service断开Socket连接后会被调用。此时可以销毁PerfProducer对象了。

  • OnStartupTracingSetup

当第一个DataSource被创建之前被调用可以做一些初始化的工作。

  • SetupDataSource

设置DataSource时被调用其传递的参数包含DataSourceInstanceId以及DataSourceConfig

  • StartDataSource

启动DataSource

  • StopDataSource

停止DataSource

  • Flush

Tracing Service要求Producer将数据写入到共享内存中。

  • ClearIncrementalState

Producer端应该在此调用后停止引用之前写入到共享内存的数据。

3.2.3.4IPC消息的发送

ProducerEndPoint提供如下接口

  • Disconnect:

用于与ProducerEndPoint断开连接此时不再能收到来自于Service端的回调消息。

  • RegisterDataSource

注册DataSource

  • UpdateDataSource

更新DataSource

  • RegisterTraceWriter

注册TraceWriter

  • CommitData

通知Tracing Service shared memory中的数据已经更新。

  • CreateTraceWriter

创建TraceWriter

  • 其他同步方法

4.traced_perf的事件处理

上一章节中我们讨论了traced_perf与perfetto的框架的关系本章节中着重阐述traced_perf在perfetto producer框架下如何实现其作为perfetto的一个producer达到profiling进线程counter信息、获取调用栈、分析性能问题的目的的。

上一章节中描述了trace_perf 通过IPC通道从tracing service进行事件接收这些事件最终转化为了Producer的重写函数那么traced_perf作为tracing service的producer需要实现这函数从而完成整个流程。

15663d605dcbb359b15110345d14a458.png

图中方框里面的是PerfProducer的事件处理状态连接线上的字是traced_perf中发生的事件或者通过IPC接收到的命令。

4.1onConnect的实现

948dc7d77cc99dc291e385bc3607f284.png

onConnect的实现非常简单首先设置连接状态的状态机为“kConnected”状态其次实例化了两个名字分别为“linux.perf”与“perfetto.metatrace”的DataSourceDescriptor然后通过endpoint_指针的RegisterDataSource方法进行DataSource注册其中endpoint_即为上一章节中提到的ProducerEndPoint对象的指针。

4.2StartDataSource的实现

StartDataSource的参数有两个分别是DataSourceInstanceID和DataSourceConfig其中DataSourceInstanceID是一个唯一的无符号64位的id用来标识DataSource的实例DataSourceConfig是data_source_config.proto生成的protobuf类其原型可以参考https://cs.android.com/android/platform/superproject/+/master:external/perfetto/protos/perfetto/config/data_source_config.proto;l=1;bpv=1;bpt=0

4.3启动MetaTraceSource

ebff3693b662b08cddb190c1c434c72d.png

通过endpoint_智能指针调用CreateTraceWriter方法创建一个TraceWriter对象。同时将此metatrace进行使能并保存到metatrace_writers_维护的一个map结构中。

4.4tracepoint与id的mapping的lookup操作

tracepoint一般是以名字的方式提供给配置文件的但是linux kernel中一般使用其对应的id进行API访问控制因此这里需要一个映射的提取。一般来说此id可以通过位于tracefs的events/GROUP/NAME/id文件中可以提取出来。

4.5打开perf event对应的eventfd

3f01c1bb4e3b199c039adcb24bff8165.png

首先将pb格式封装的配置文件转化为perf_event_attr数据结构之后调用linux kernel提供的系统调用向操作系统注册。

打开perf event所必须的linux syscall为perf_event_open此API的参数比较复杂详细介绍可以参考官方文档https://man7.org/linux/man-pages/man2/perf_event_open.2.html

这里着重讲解一下关键的配置信息

▫perf_event_attr

91cae745240487c5b0fa169d94d6fbf1.png

perf_event_attr是一个比较大的结构体包含了对perf_event配置的各种属性信息以比较简单的tracepoint事件为例一般来说需要设置以下必须的字段

type: 设置为PERF_TYPE_TRACEPOINT类型

size: 设置为sizeof(perf_event_attr)

config设置为上一步中获取到的mapping的id信息

sample_type: 设置sample中包含的数据类型

read_format设置read返回的数值中包含的数据类型

开关bitmask配置包含是否包含mmap的数据是否包含comm等近30个配置项

pid

获取哪个pid的perf event事件

cpu

获取哪个cpu的perf event事件

groud_fd

可以将多个events通过同一个event fd进行返回可以将其中一个事件传入-1作为group leader后续事件可以将返回的fd传入此参数中。

4.6创建TraceWriter并使能perf event

7b86c2580a45f56d81447013c8221a2b.png

4.7通知Unwinder启动了DataSource

bed34054116859507ff07d36a2481483.png

4.8启动周期性读取任务

fb71dc60be653c2f9c488ed6584887a0.png

周期性的读取任务主要是从内核的共享内存中获取perf event的数据。在后续的章节中我们会着重讲述获取的数据。

aa9eb0d7f4a01d801799bc6a6603f15a.png

在TickDataSourceRead函数中的ReadAndParsePerCpuBuffer中会将从内核的共享内存中读取的sample数据推送到unwinding_worker的queue中。当调用PostProcessQueue时会唤醒unwinding_worker对应的线程并执行unwind操作直到所有sample都unwind完毕。

若DataSource的状态未停止则需要继续抓取更多的samples因此在这个task中又继续调用了延迟任务继续让task_runner调度本任务。

5.Sample的获取

Sample事件的获取是从Linux内核中提供的ring buffer共享内存中获取的这部分操作位于PerfProducer::ReadAndParsePerCpuBuffer中进行的这部分操作相对来说比较繁琐下图中截取了一部分。其基本的流程是

循环通过EventReader的ReadUntilSample获取解析好的Sample如果DataSource的config中有配置一些filter项则筛选掉不感兴趣的Sample直到没有Sample产生了或者已经获取到足够的Sample了。

cb44f2504ea99c4c45bf11bd1a77bf8f.png

5.1PerfRingBuffer之环形缓冲区数据的获取

回顾一下perf_event_open函数的原型

int syscall(SYS_perf_event_open, struct perf_event_attr *attr,                   pid_t pid, int cpu, int group_fd, unsigned long flags);                  

其中perf_event_attr结构中包含了众多的配置参数。跟通过ring buffer获取sample相关的参数有以下几个配置

  • sample_period/sample_freq: 指明多久获取一次sample。

  • sample_type: 指明什么类型的数据会包含在sample中比如Instruction pointer、TID、Sample的时间、地址信息等

通过perf_event_open返回的文件描述符可以进而通过mmap系统调用返回一个Kernel与Userspace共享的内存地址空间此内存地址中的数据一般由Kernel写入Userspace的程序负责对其进行解析。mmap的共享内存地址的分布如下

f393ec8c2940cd4bac2bf920f87fe6c2.png

metadata页对应的数据结构如下

d9aca5799a6abdc5950fb810c65bb81b.png

9922f8c7dbcdffafa65803ab115830d2.png

  • data_head: 指向数据区的首地址这个地址是持续自增加的在使用它的时候需要将其地址与mmap buffer的大小进行一个wrap操作。

  • data_tail: 此数据是需要由userspace写入指明userspace最后读取的数据的位置从而使得内核不会降未读取的数据覆盖。

  • data_offset: perf_sample的起始位置由此述职来确定。

  • data_size: 包含了perf_sample区域大小信息

由Linux内核提供的perf sample也包含固定的格式每个perf sample的数据原型如下

8762ffaefcf39cab8ad61089ea99326a.png

06fe2aa58dfd36d047f8f7b950961374.png

注意到上述结构右边的if注释假如对应的选项没有在sample_type中配置则不包含对应的字段在解析sample的时候值的注意。

perf_event_header是每个sample的头信息它的定义如下

8306087d3fba32a6c47c8d83177b3a6d.png

  • size: 本perf sample的大小

  • misc包含本sample的一些额外的信息

  • type: 不同的sample类型只有类型为PERF_RECORD_SAMPLE时才有上面的perf sample的数据结构。比如当其类型为PERF_RECORD_LOST时对应的perf event的数据结构为

1a285c548a45f2690de22d5ab02e1c31.png

5.2EventReader之Sample的解析

5.2.1perf sample的读取

a6d0a1307966a296392149497aa1d9b9.png

perf sample的获取实际上是在对环形缓冲区的读取环形缓冲区的包含一个读取偏移量以及一个写入偏移量。其中写入偏移量是由内核负责写入的只要读取偏移量小于写入偏移量则说明环形缓冲区中仍有数据未读取。

这里注意到环形缓冲区实际上是可以出现回卷操作的假如出现了回卷操作需要将数据进行重组。

5.2.2Perf sample的解析

perf sample本身的解析工作是通过EventReader::ParseSampleRecord进行的。解析后的数据结构为ParsedSample其定义为

1103ac1c4d9d93f4be2b0b49e4ac829c.png

13cca1d716acff05b587a09cb923c25f.png

可以看出traced_perf关注的信息包含

  • CommonSampleData: cpu_mode, cpu, pid, tid, timestamp, timebase_count等信息

  • regs: 用作unwinding的userspace寄存器信息

  • stack: userspace栈信息

  • kernel_ips: 内核instruction pointer信息

下面是Sample解析的具体流程

6aea7ddf8b77978f267ab41052bcfc0e.png

上述函数会返回解析好的Perf sample即ParsedSample。进行一系列的筛选逻辑后此sample会被发送到unwindwing_worker提供的queue中以便于进行后续的unwinding操作。

cc5449a4dbecb0aad18849cc4524d0f1.png

至此所有从内核中所需要的perf event信息已经收集并解析完毕下一步是将之转化为可读的callstack信息的流程这离不开unwinding 操作。

6.Unwinding操作

unwinding操作发生在解析完perf event sample之后其发起动作的调用为

1bbeee6fee428643a7e7d7bfafc9aa2a.png

其主要处理逻辑位于Unwinder::ConsumeAndUnwindReadySamples函数中。

a3317f6ec1027ed2f2a5cfcb4d2f0f94.png

a0b398ec618afde5055de0c8db7cc15c.png

当unwind成功后调用到PerfProducer中的PostEmitSample中将unwinding之后的数据写入TraceWriter。

6.1内核栈的解析

内核栈的解析相对简单其主要操作函数再Unwinder::SynbolizeKernelCallchain中。其主要原理是解析"/proc/kallsyms" 中的内核地址与符号之间的对应关系。根据对应关系将sample中的kernel态的instruction pointer翻译成地址信息。kernel 态的地址信息介绍见之前章节。

26b3dcc3d6e5789767524e2d1905b895.png

6.2用户栈的解析

用户栈的解析相对复杂用户栈的解析首先要获取几个必要的信息

  1. Userspace寄存器信息

  2. Userspace栈信息

  3. /proc//mem信息

  4. /proc//maps信息

其中前两个信息已经通过之前的Sample解析操作成功获取了那么第3、4个信息怎么获取呢

在之前的Android版本中特权进程是可以直接访问到对应pid的这两个信息的随着Android对安全隐私的越来越重视对不同进程的敏感信息进行了比较强的隔离。因此traced_perf为了获取此信息必须按照符合Android安全设计的机制以相对复杂的方式进行实现。

6.2.1traced_perf如何请求目标进程的maps和mem信息

在AndroidRemoteDescriptorGetter类中实现了获取/proc//mem以及/proc//maps操作的动作获取操作是通过发起signal操作来完成的signal的目标是目标进程

7aff45e884f4b39aad1c073658b03e52.png

而信息的接收是通过socket来完成的即traced_perf进程刚启动时创建的socket:

5a46a4d3ab4d850561b1b5c5ceb35f12.png

在此socket的数据收取操作中获取到上述两个文件的文件描述符文件描述符已经经过内核态转换可以在traced_perf进程中正常使用。

e40df4ee196165f2aef7a01c56fa2352.png

上述代码中的delegate实际上指向的是PerfProducer对象因此delegate_->onProcDescriptors会将两个文件描述符发送给PerfProducer对象。而PerfProducer进而将此文件描述符发送给了UnwinderHandle对象。

6.2.6目标进程如何将maps和mem信息发送给traced_perf

如前面所述traced perf通过signal通知的目标进程让目标进程将文件描述符进行了发送那么目标进程为什么都会响应这类信息呢目标进程可能非常多样包括daemon、系统apk、三方apk等答案在C库中。

254ad5a008572b837b50fd9e6c07fd55.png

当目标进程接收到信号后通过unix socket与traced_perf进程建立连接然后打开两个文件maps和mem通过unix socket的sendmsg进行发送。关于通过unix socket发送文件描述符可以参考文档https://man7.org/linux/man-pages/man7/unix.7.html这里不做详细描述。

上述代码在android_profiling_dynamic.cpp中会编译成C库的一部分并被大多数进程所加载。

228627562da3f9d19d4ad33669c38af7.png

到此为止用户栈的所有所需信息均已准备完毕。

6.2.3用户栈的解析

将所有信息准备好后真正解析用户栈可以直接通过libunwindstack提供的方法Unwinder::Unwind即可了反而流程显得很直接。

ebddcef51aef7e52204d51af981cb207.png

7.Sample的写入

Sample数据写入到trace中的操作也是比较直接的将前面流程中获取到的信息通过TraceWriter返回的TrackPacket protobuf结构进行写入即可。

95a57880d51a324501498e6ea81b9c75.png

至此整个traced_perf的获取sample的执行流程大概完成了。

8.总结

traced_perf的工作流程主要部分包括

  • perfetto流程的嵌入traced_perf是perfetto的producer

  • Sample的获取

  • Unwinding操作

这三项主要内容看似复杂实际上整体结构也是比较清晰的。Perfetto已经将tracing、profiling的框架打通Tracing producer要接入perfetto也是一件按部就班的事情而已。

9.参考链接

TracedPerf源码: https://cs.android.com/

traced_perf相关文档: https://perfetto.dev/docs

perf历史: https://en.wikipedia.org/wiki/Perf_(Linux)

simpleperf相关文档: https://android.googlesource.com/platform/prebuilts/simpleperf/+/782cdf2ea6e33f2414b53884742d59fe11f01ebe/README.md

perf_event_open: https://man7.org/linux/man-pages/man2

bfc9e8f737c7852b0470ebb7085ed043.gif

长按关注内核工匠微信

Linux内核黑科技| 技术文章 | 精选教程

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6