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项目的一个子目录。
此目录下的代码如下图
可以看出代码分为三类
编译脚本相关: 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启动脚本的控制。
其运行时的生命周期可以通过traced_perf.rc文件去分析
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如下图所示
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内存这部分内存是没有进行进程间共享的从而可以保持数据的隔离。
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++类的声明与函数实现而不要刚开始就陷入到调用流程的跟踪中避免陷入到多层嵌套的复杂逻辑中。当把几个关键类的功能和对外关系理清楚之后再通过调用关系依次跟踪调用流程。
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.启动消息循环
3.2.3IPC通道框架
IPC通道的框架相对来说比较复杂本小节进行一个原理剖析。
TaskRunner: 是一个Looper interfacePerfProducer使用的实例是基于unix domain socket实例化的TaskRunner。此task_runner_在各个结构间传递承担了各类消息的派发和处理。
ProducerEndPoint: 是Tracing service的producer端的接口类通过ProducerIPCClientImpl得以实例化。
为了能够将PerfProducer类注册为Tracing Service的producer需要执行如下操作
其中ProducerIPCClient::Connect是一个静态方法其实例化了ProducerIPCClientImpl并将其以unique_ptr的形式返回。
上述流程走完之后实际上就建立了PerfProducer的事件处理流程。
关注到ConnectService中ProducerIPCClient::Connect中的第二个参数是this指针实际上是把PerfProducer的对象指针传递给了ProducerEndpoint对象它是通过ProducerIPCClientImpl构造函数中的第三个参数producer传递过去的。
3.2.3.1相关概念
DataSource
顾名思义这个是数据源的意思。根据Perfetto的框架图consumer端需要指明从哪个“数据源”收集数据而Producer可以提供数据源。数据源在perfetto中的定义以proto的形式进行了规定在PerfProducer中它对数据源的定义进行了抽象通过DataSourceState进行描述。
与DataSource相对应的一个数据结构是traced_perf里面的DataSourceState的结构可以看到DataSourceState中维护了一个TraceWriter指针此TraceWriter提供了写入Trace数据的相关方法。
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定义的协议格式发送对应的指令。消息协议如下
上述消息会被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需要实现这函数从而完成整个流程。
图中方框里面的是PerfProducer的事件处理状态连接线上的字是traced_perf中发生的事件或者通过IPC接收到的命令。
4.1onConnect的实现
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
通过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
首先将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
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
4.7通知Unwinder启动了DataSource
4.8启动周期性读取任务
周期性的读取任务主要是从内核的共享内存中获取perf event的数据。在后续的章节中我们会着重讲述获取的数据。
在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了。
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的共享内存地址的分布如下
metadata页对应的数据结构如下
data_head: 指向数据区的首地址这个地址是持续自增加的在使用它的时候需要将其地址与mmap buffer的大小进行一个wrap操作。
data_tail: 此数据是需要由userspace写入指明userspace最后读取的数据的位置从而使得内核不会降未读取的数据覆盖。
data_offset: perf_sample的起始位置由此述职来确定。
data_size: 包含了perf_sample区域大小信息
由Linux内核提供的perf sample也包含固定的格式每个perf sample的数据原型如下
注意到上述结构右边的if注释假如对应的选项没有在sample_type中配置则不包含对应的字段在解析sample的时候值的注意。
perf_event_header是每个sample的头信息它的定义如下
size: 本perf sample的大小
misc包含本sample的一些额外的信息
type: 不同的sample类型只有类型为PERF_RECORD_SAMPLE时才有上面的perf sample的数据结构。比如当其类型为PERF_RECORD_LOST时对应的perf event的数据结构为
5.2EventReader之Sample的解析
5.2.1perf sample的读取
perf sample的获取实际上是在对环形缓冲区的读取环形缓冲区的包含一个读取偏移量以及一个写入偏移量。其中写入偏移量是由内核负责写入的只要读取偏移量小于写入偏移量则说明环形缓冲区中仍有数据未读取。
这里注意到环形缓冲区实际上是可以出现回卷操作的假如出现了回卷操作需要将数据进行重组。
5.2.2Perf sample的解析
perf sample本身的解析工作是通过EventReader::ParseSampleRecord进行的。解析后的数据结构为ParsedSample其定义为
可以看出traced_perf关注的信息包含
CommonSampleData: cpu_mode, cpu, pid, tid, timestamp, timebase_count等信息
regs: 用作unwinding的userspace寄存器信息
stack: userspace栈信息
kernel_ips: 内核instruction pointer信息
下面是Sample解析的具体流程
上述函数会返回解析好的Perf sample即ParsedSample。进行一系列的筛选逻辑后此sample会被发送到unwindwing_worker提供的queue中以便于进行后续的unwinding操作。
至此所有从内核中所需要的perf event信息已经收集并解析完毕下一步是将之转化为可读的callstack信息的流程这离不开unwinding 操作。
6.Unwinding操作
unwinding操作发生在解析完perf event sample之后其发起动作的调用为
其主要处理逻辑位于Unwinder::ConsumeAndUnwindReadySamples函数中。
当unwind成功后调用到PerfProducer中的PostEmitSample中将unwinding之后的数据写入TraceWriter。
6.1内核栈的解析
内核栈的解析相对简单其主要操作函数再Unwinder::SynbolizeKernelCallchain中。其主要原理是解析"/proc/kallsyms" 中的内核地址与符号之间的对应关系。根据对应关系将sample中的kernel态的instruction pointer翻译成地址信息。kernel 态的地址信息介绍见之前章节。
6.2用户栈的解析
用户栈的解析相对复杂用户栈的解析首先要获取几个必要的信息
Userspace寄存器信息
Userspace栈信息
/proc//mem信息
/proc//maps信息
其中前两个信息已经通过之前的Sample解析操作成功获取了那么第3、4个信息怎么获取呢
在之前的Android版本中特权进程是可以直接访问到对应pid的这两个信息的随着Android对安全隐私的越来越重视对不同进程的敏感信息进行了比较强的隔离。因此traced_perf为了获取此信息必须按照符合Android安全设计的机制以相对复杂的方式进行实现。
6.2.1traced_perf如何请求目标进程的maps和mem信息
在AndroidRemoteDescriptorGetter类中实现了获取/proc//mem以及/proc//maps操作的动作获取操作是通过发起signal操作来完成的signal的目标是目标进程
而信息的接收是通过socket来完成的即traced_perf进程刚启动时创建的socket:
在此socket的数据收取操作中获取到上述两个文件的文件描述符文件描述符已经经过内核态转换可以在traced_perf进程中正常使用。
上述代码中的delegate实际上指向的是PerfProducer对象因此delegate_->onProcDescriptors会将两个文件描述符发送给PerfProducer对象。而PerfProducer进而将此文件描述符发送给了UnwinderHandle对象。
6.2.6目标进程如何将maps和mem信息发送给traced_perf
如前面所述traced perf通过signal通知的目标进程让目标进程将文件描述符进行了发送那么目标进程为什么都会响应这类信息呢目标进程可能非常多样包括daemon、系统apk、三方apk等答案在C库中。
当目标进程接收到信号后通过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库的一部分并被大多数进程所加载。
到此为止用户栈的所有所需信息均已准备完毕。
6.2.3用户栈的解析
将所有信息准备好后真正解析用户栈可以直接通过libunwindstack提供的方法Unwinder::Unwind即可了反而流程显得很直接。
7.Sample的写入
Sample数据写入到trace中的操作也是比较直接的将前面流程中获取到的信息通过TraceWriter返回的TrackPacket protobuf结构进行写入即可。
至此整个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
长按关注内核工匠微信
Linux内核黑科技| 技术文章 | 精选教程