Perfetto工具集之traced_perf
1.Perf工具概覽
linux中包含了眾多性能分析工具,perf(特指linux-tools perf)工具是2009年在linux內(nèi)核2.6.31中引入的一個工具。它的主要功能是可以跟蹤hardware performance counter(PMU)、tracepoints、software performance counter(hrtimer)、dynamic probes等信息。linux內(nèi)核將這些信息進行封裝,通過syscall(perf_event_open等)的形式提供,使之抽象為events的概念,可以供userspace的進程使用。perf作為一個linux下的命令行工具,可以讀取這些events,并結(jié)合性能分析的場景,提供了諸如stats、top、record、report等子工具命令,適配更細化的分析需求。
Android中一般使用的并不是老牌的linux-tools perf工具,而是使用經(jīng)過Android客制化的perf工具,用于支持Android中拓展的一些feature。
simpleperf:
Android最早于Android 6.0(2015年)中引入,距今(2022年)已經(jīng)有7年的歷史。其主要作用就是實現(xiàn)Linux中perf工具的基本功能。
traced_perf:
Google于2019[在2019年開始開發(fā)][修改了一下]年開始開發(fā),其作為perfetto的一個consumer而不是單獨的一個項目去開發(fā)的。其開發(fā)目的是能夠:
a.利用perfetto的成熟平臺,提供profiling、unwinding、UI等各方面的能力
b.伴隨著Android權限管控的愈發(fā)嚴格和MAC的要求,原Simpleperf的獨立selinux domain完成所有功能的方式已經(jīng)無法滿足sandbox的需求,需要進行嚴格的domain隔離
本文著重講一下traced_perf。
2.traced_perf的結(jié)構(gòu)
2.1.代碼結(jié)構(gòu)
traced_perf的代碼位于AOSP的external/perfetto/src/profiling/perf/?目錄下,可以看出,traced_perf的代碼實際上是perfetto項目的一個子目錄。
此目錄下的代碼如下圖:

可以看出代碼分為三類:
編譯腳本相關: BUILD.gn
單元測試相關:X_unittest.cc
主要代碼邏輯
除了上述的代碼目錄外,在perfetto的主目錄下還存在文件:external/perfetto/traced_perf.rc
此文件是traced_perf可執(zhí)行文件的啟動腳本。
2.2運行時結(jié)構(gòu)
根據(jù)external/perfetto/Android.bp的編譯腳本可以看出,traced_perf最終會被編譯為一個可執(zhí)行文件,并且被install到/system/bin/traced_perf。此可執(zhí)行文件以daemon的形式存在,其啟動和結(jié)束受它對應的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,分別為KILL,DAC_READ_SEARCH。KILL是為了使traced_perf能夠給其他進程發(fā)送信號。DAC_READ_SEARCH是為了使之能夠至少能夠獲取一些文件的權限,而不至于甚至不能夠探測某些文件的存在。這兩個權限都是traced_perf正常運行所必需的。
?task_profiles是為了給traced_perf設置為高capacity(unwinding)的一類cgroup,從而使得調(diào)度器可以給予其更合理的資源分配。
traced_perf的資源
traced_perf申請了一個名為traced_perf的unix_socket,此unix_socket是traced_perf與待profiling的進程間通信的通道,后續(xù)章節(jié)有涉及。
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的架構(gòu)
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之間建立的共享內(nèi)存通道,此共享內(nèi)存通道有兩個作用:
1.進行高效的進程間數(shù)據(jù)傳遞,這里傳遞的主要是結(jié)構(gòu)化的采樣點數(shù)據(jù)。
2.與控制流進行隔離,避免被惡意破解后造成安全隱私風險。
traced_perf本身作為producer端,提供了Data Source,每個producer可以提供多種Data Source。traced_perf本身對外提供的Data Source包含linux.perf 和一個metadata的Data Source。后續(xù)我們對此Data Souce展開詳細的描述。
3.1.2Tracing Service
Tracing Service作為perfetto在手機端的核心服務,其承擔了主控的作用。Tracing Service在手機端主要表現(xiàn)為traced進程,它一方面接收consumer的配置文件的控制,另外一方面將配置文件轉(zhuǎn)化為對Producer的控制;同時還承擔了producer端與consumer端的橋梁。producer端與consumer的數(shù)據(jù)通道采用了trace buffer內(nèi)存,這部分內(nèi)存是沒有進行進程間共享的,從而可以保持數(shù)據(jù)的隔離。

3.1.3Consumer
consumer端是指對perfetto trace類數(shù)據(jù)的消耗端,比如perfetto ui、shell command、traceur等,consumer端還可以進行自定義,在Android中添加客制化的consumer,從而對Data Source進行客制化的處理。
consumer端和Tracing Service的IPC通道主要也是通過unix socket進行連接的。
3.2traced_perf與perfetto的交互
3.2.1整體流程
整體流程示意圖如下,讀者可以根據(jù)此流程理解。交互流程涉及到Perfetto內(nèi)部的眾多類的實現(xiàn),建議讀者優(yōu)先理解涉及到的C++類的聲明與函數(shù)實現(xiàn),而不要剛開始就陷入到調(diào)用流程的跟蹤中,避免陷入到多層嵌套的復雜邏輯中。當把幾個關鍵類的功能和對外關系理清楚之后,再通過調(diào)用關系依次跟蹤調(diào)用流程。

PerfProducer:
調(diào)用ConnectService建立連接流程
實現(xiàn)OnConnect流程
實現(xiàn)OnTracingSetup、StartDataSource等函數(shù)
ProducerEndPoint
創(chuàng)建建立進程間通信的必要對象
實現(xiàn)OnConnect
ClientImpl
建立Socket連接
實現(xiàn)onConnect、onDataAvaliable等
通過上述流程拆分可以看出,每個類的職責都是非常清晰的。
3.2.2IPC通道建立
對于traced_perf來講,建立IPC通道由下面幾個重要流程:
1.實例化task_runner和AndroidRemoteDescriptorGetter。task_runner是traced_perf中使用的一個Looper工具類實例,AndroidRemoteDescriptorGetter是traced_perf為了獲取想要trace的應用的私有進程數(shù)據(jù)而建立的一個類。后續(xù)章節(jié)有相關描述。
2.與Tracing Service建立連接
3.啟動消息循環(huán)

3.2.3IPC通道框架
IPC通道的框架相對來說比較復雜,本小節(jié)進行一個原理剖析。
TaskRunner: 是一個Looper interface,PerfProducer使用的實例是基于unix domain socket實例化的TaskRunner。此task_runner_在各個結(jié)構(gòu)間傳遞,承擔了各類消息的派發(fā)和處理。
ProducerEndPoint: 是Tracing service的producer端的接口類,通過ProducerIPCClientImpl得以實例化。
為了能夠?qū)erfProducer類注冊為Tracing Service的producer,需要執(zhí)行如下操作:

其中,ProducerIPCClient::Connect是一個靜態(tài)方法,其實例化了ProducerIPCClientImpl并將其以unique_ptr的形式返回。


上述流程走完之后,實際上就建立了PerfProducer的事件處理流程。
關注到ConnectService中,ProducerIPCClient::Connect中的第二個參數(shù)是this指針,實際上是把PerfProducer的對象指針傳遞給了ProducerEndpoint對象,它是通過ProducerIPCClientImpl構(gòu)造函數(shù)中的第三個參數(shù)producer傳遞過去的。
3.2.3.1相關概念
DataSource
顧名思義,這個是數(shù)據(jù)源的意思。根據(jù)Perfetto的框架圖,consumer端需要指明從哪個“數(shù)據(jù)源”收集數(shù)據(jù),而Producer可以提供數(shù)據(jù)源。數(shù)據(jù)源在perfetto中的定義以proto的形式進行了規(guī)定,在PerfProducer中,它對數(shù)據(jù)源的定義進行了抽象,通過DataSourceState進行描述。





與DataSource相對應的一個數(shù)據(jù)結(jié)構(gòu)是traced_perf里面的DataSourceState的結(jié)構(gòu),可以看到DataSourceState中維護了一個TraceWriter指針,此TraceWriter提供了寫入Trace數(shù)據(jù)的相關方法。

3.2.3.2TraceWriter
TraceWriter類是為了讓使用者可以在perfetto的共享內(nèi)存中,以零拷貝的形式寫入Trace數(shù)據(jù),方便使用者高效寫入Trace數(shù)據(jù)。
NewTracePacket
創(chuàng)建一個TracePacket并返回一個handle
FinishTracePacket
完成之前創(chuàng)建的TracePacket
Flush
將TracePacket刷入到service端
3.2.3.3IPC消息的接收
ProducerEndPoint對象會通過PerfProducer對象提供的service_sock_name與PerfProducer進行通信,當連接建立之后,就進入了IPC流程,服務端會將消息按照perfetto定義的協(xié)議格式發(fā)送對應的指令。消息協(xié)議如下:


上述消息會被ProducerEndPoint解析,并最終轉(zhuǎn)化為Producer接口類的虛函數(shù)調(diào)用(注意到ProducerEndPoint維護了一個Producer(PerfProducer)實例的指針)。
Producer的實例需要實現(xiàn)如下接口:
OnConnect
當與Tracing Service建立Socket連接后會被調(diào)用
OnDisconnect
當與Tracing Service斷開Socket連接后會被調(diào)用。此時可以銷毀PerfProducer對象了。
OnStartupTracingSetup
當?shù)谝粋€DataSource被創(chuàng)建之前被調(diào)用,可以做一些初始化的工作。
SetupDataSource
設置DataSource時被調(diào)用,其傳遞的參數(shù)包含DataSourceInstanceId以及DataSourceConfig
StartDataSource
啟動DataSource
StopDataSource
停止DataSource
Flush
Tracing Service要求Producer將數(shù)據(jù)寫入到共享內(nèi)存中。
ClearIncrementalState
Producer端應該在此調(diào)用后,停止引用之前寫入到共享內(nèi)存的數(shù)據(jù)。
3.2.3.4IPC消息的發(fā)送
ProducerEndPoint提供如下接口:
Disconnect:
用于與ProducerEndPoint斷開連接,此時不再能收到來自于Service端的回調(diào)消息。
RegisterDataSource
注冊DataSource
UpdateDataSource
更新DataSource
RegisterTraceWriter
注冊TraceWriter
CommitData
通知Tracing Service shared memory中的數(shù)據(jù)已經(jīng)更新。
CreateTraceWriter
創(chuàng)建TraceWriter
其他同步方法
4.traced_perf的事件處理
上一章節(jié)中,我們討論了traced_perf與perfetto的框架的關系,本章節(jié)中著重闡述traced_perf在perfetto producer框架下,如何實現(xiàn)其作為perfetto的一個producer,達到profiling進線程counter信息、獲取調(diào)用棧、分析性能問題的目的的。
上一章節(jié)中,描述了trace_perf 通過IPC通道從tracing service進行事件接收,這些事件最終轉(zhuǎn)化為了Producer的重寫函數(shù),那么traced_perf作為tracing service的producer,需要實現(xiàn)這函數(shù)從而完成整個流程。
圖中方框里面的是PerfProducer的事件處理狀態(tài),連接線上的字是traced_perf中發(fā)生的事件或者通過IPC接收到的命令。
4.1onConnect的實現(xiàn)
onConnect的實現(xiàn)非常簡單,首先設置連接狀態(tài)的狀態(tài)機為“kConnected”狀態(tài),其次實例化了兩個名字分別為“l(fā)inux.perf”與“perfetto.metatrace”的DataSourceDescriptor,然后通過endpoint_指針的RegisterDataSource方法進行DataSource注冊,其中endpoint_即為上一章節(jié)中提到的ProducerEndPoint對象的指針。
4.2StartDataSource的實現(xiàn)
StartDataSource的參數(shù)有兩個,分別是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_智能指針,調(diào)用CreateTraceWriter方法,創(chuàng)建一個TraceWriter對象。同時將此metatrace進行使能,并保存到metatrace_writers_維護的一個map結(jié)構(gòu)中。
4.4tracepoint與id的mapping的lookup操作
tracepoint一般是以名字的方式提供給配置文件的,但是linux kernel中一般使用其對應的id進行API訪問控制,因此這里需要一個映射的提取。一般來說,此id可以通過位于tracefs的events/GROUP/NAME/id文件中可以提取出來。
4.5打開perf event對應的eventfd
首先將pb格式封裝的配置文件轉(zhuǎn)化為perf_event_attr數(shù)據(jù)結(jié)構(gòu),之后調(diào)用linux kernel提供的系統(tǒng)調(diào)用向操作系統(tǒng)注冊。
打開perf event所必須的linux syscall為perf_event_open,此API的參數(shù)比較復雜,詳細介紹可以參考官方文檔:https://man7.org/linux/man-pages/man2/perf_event_open.2.html
這里著重講解一下關鍵的配置信息:
?perf_event_attr
perf_event_attr是一個比較大的結(jié)構(gòu)體,包含了對perf_event配置的各種屬性信息,以比較簡單的tracepoint事件為例,一般來說需要設置以下必須的字段:
type: 設置為PERF_TYPE_TRACEPOINT類型
size: 設置為sizeof(perf_event_attr)
config:設置為上一步中獲取到的mapping的id信息
sample_type: 設置sample中包含的數(shù)據(jù)類型
read_format:設置read返回的數(shù)值中包含的數(shù)據(jù)類型
開關bitmask配置:包含是否包含mmap的數(shù)據(jù),是否包含comm等近30個配置項
pid
獲取哪個pid的perf event事件
cpu
獲取哪個cpu的perf event事件
groud_fd
可以將多個events通過同一個event fd進行返回,可以將其中一個事件傳入-1作為group leader,后續(xù)事件可以將返回的fd傳入此參數(shù)中。
4.6創(chuàng)建TraceWriter并使能perf event
4.7通知Unwinder啟動了DataSource
4.8啟動周期性讀取任務
周期性的讀取任務,主要是從內(nèi)核的共享內(nèi)存中,獲取perf event的數(shù)據(jù)。在后續(xù)的章節(jié)中我們會著重講述獲取的數(shù)據(jù)。
在TickDataSourceRead函數(shù)中的ReadAndParsePerCpuBuffer中,會將從內(nèi)核的共享內(nèi)存中讀取的sample數(shù)據(jù),推送到unwinding_worker的queue中。當調(diào)用PostProcessQueue時,會喚醒unwinding_worker對應的線程,并執(zhí)行unwind操作,直到所有sample都unwind完畢。
若DataSource的狀態(tài)未停止,則需要繼續(xù)抓取更多的samples,因此在這個task中,又繼續(xù)調(diào)用了延遲任務,繼續(xù)讓task_runner調(diào)度本任務。
5.Sample的獲取
Sample事件的獲取是從Linux內(nèi)核中提供的ring buffer共享內(nèi)存中獲取的,這部分操作位于PerfProducer::ReadAndParsePerCpuBuffer中進行的,這部分操作相對來說比較繁瑣,下圖中截取了一部分。其基本的流程是:
循環(huán)通過EventReader的ReadUntilSample獲取解析好的Sample,如果DataSource的config中有配置一些filter項,則篩選掉不感興趣的Sample,直到?jīng)]有Sample產(chǎn)生了或者已經(jīng)獲取到足夠的Sample了。

5.1PerfRingBuffer之環(huán)形緩沖區(qū)數(shù)據(jù)的獲取
回顧一下perf_event_open函數(shù)的原型:
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結(jié)構(gòu)中包含了眾多的配置參數(shù)。跟通過ring buffer獲取sample相關的參數(shù)有以下幾個配置:
sample_period/sample_freq: 指明多久獲取一次sample。
sample_type: 指明什么類型的數(shù)據(jù)會包含在sample中,比如Instruction pointer、TID、Sample的時間、地址信息等
通過perf_event_open返回的文件描述符,可以進而通過mmap系統(tǒng)調(diào)用,返回一個Kernel與Userspace共享的內(nèi)存地址空間,此內(nèi)存地址中的數(shù)據(jù)一般由Kernel寫入,Userspace的程序負責對其進行解析。mmap的共享內(nèi)存地址的分布如下:

metadata頁對應的數(shù)據(jù)結(jié)構(gòu)如下:


data_head: 指向數(shù)據(jù)區(qū)的首地址,這個地址是持續(xù)自增加的,在使用它的時候,需要將其地址與mmap buffer的大小進行一個wrap操作。
data_tail: 此數(shù)據(jù)是需要由userspace寫入,指明userspace最后讀取的數(shù)據(jù)的位置,從而使得內(nèi)核不會降未讀取的數(shù)據(jù)覆蓋。
data_offset: perf_sample的起始位置由此述職來確定。
data_size: 包含了perf_sample區(qū)域大小信息
由Linux內(nèi)核提供的perf sample也包含固定的格式,每個perf sample的數(shù)據(jù)原型如下:


注意到上述結(jié)構(gòu)右邊的if注釋,假如對應的選項沒有在sample_type中配置,則不包含對應的字段,在解析sample的時候,值的注意。
perf_event_header是每個sample的頭信息,它的定義如下:

size: 本perf sample的大小
misc:包含本sample的一些額外的信息
type: 不同的sample類型,只有類型為PERF_RECORD_SAMPLE時,才有上面的perf sample的數(shù)據(jù)結(jié)構(gòu)。比如當其類型為PERF_RECORD_LOST時,對應的perf event的數(shù)據(jù)結(jié)構(gòu)為

5.2EventReader之Sample的解析
5.2.1perf sample的讀取

perf sample的獲取實際上是在對環(huán)形緩沖區(qū)的讀取,環(huán)形緩沖區(qū)的包含一個讀取偏移量以及一個寫入偏移量。其中寫入偏移量是由內(nèi)核負責寫入的,只要讀取偏移量小于寫入偏移量,則說明環(huán)形緩沖區(qū)中仍有數(shù)據(jù)未讀取。
這里注意到環(huán)形緩沖區(qū)實際上是可以出現(xiàn)回卷操作的,假如出現(xiàn)了回卷操作,需要將數(shù)據(jù)進行重組。
5.2.2Perf sample的解析
perf sample本身的解析工作是通過EventReader::ParseSampleRecord進行的。解析后的數(shù)據(jù)結(jié)構(gòu)為ParsedSample,其定義為:


可以看出,traced_perf關注的信息包含:
CommonSampleData: cpu_mode, cpu, pid, tid, timestamp, timebase_count等信息
regs: 用作unwinding的userspace寄存器信息
stack: userspace棧信息
kernel_ips: 內(nèi)核instruction pointer信息
下面是Sample解析的具體流程

上述函數(shù)會返回解析好的Perf sample,即ParsedSample。進行一系列的篩選邏輯后,此sample會被發(fā)送到unwindwing_worker提供的queue中,以便于進行后續(xù)的unwinding操作。

至此,所有從內(nèi)核中所需要的perf event信息已經(jīng)收集并解析完畢,下一步是將之轉(zhuǎn)化為可讀的callstack信息的流程,這離不開unwinding 操作。
6.Unwinding操作
unwinding操作發(fā)生在解析完perf event sample之后,其發(fā)起動作的調(diào)用為:

其主要處理邏輯位于Unwinder::ConsumeAndUnwindReadySamples函數(shù)中。


當unwind成功后,調(diào)用到PerfProducer中的PostEmitSample中,將unwinding之后的數(shù)據(jù)寫入TraceWriter。
6.1內(nèi)核棧的解析
內(nèi)核棧的解析相對簡單,其主要操作函數(shù)再Unwinder::SynbolizeKernelCallchain中。其主要原理是解析"/proc/kallsyms" 中的內(nèi)核地址與符號之間的對應關系。根據(jù)對應關系,將sample中的kernel態(tài)的instruction pointer翻譯成地址信息。kernel 態(tài)的地址信息介紹見之前章節(jié)。

6.2用戶棧的解析
用戶棧的解析相對復雜,用戶棧的解析首先要獲取幾個必要的信息:
Userspace寄存器信息
Userspace棧信息
/proc//mem信息
/proc//maps信息
其中前兩個信息已經(jīng)通過之前的Sample解析操作成功獲取了,那么第3、4個信息怎么獲取呢?
在之前的Android版本中,特權進程是可以直接訪問到對應pid的這兩個信息的,隨著Android對安全隱私的越來越重視,對不同進程的敏感信息進行了比較強的隔離。因此traced_perf為了獲取此信息,必須按照符合Android安全設計的機制,以相對復雜的方式進行實現(xiàn)。
6.2.1traced_perf如何請求目標進程的maps和mem信息
在AndroidRemoteDescriptorGetter類中,實現(xiàn)了獲取/proc//mem以及/proc//maps操作的動作,獲取操作是通過發(fā)起signal操作來完成的,signal的目標是目標進程:

而信息的接收是通過socket來完成的,即traced_perf進程剛啟動時創(chuàng)建的socket:

在此socket的數(shù)據(jù)收取操作中,獲取到上述兩個文件的文件描述符(文件描述符已經(jīng)經(jīng)過內(nèi)核態(tài)轉(zhuǎn)換,可以在traced_perf進程中正常使用)。

上述代碼中的delegate實際上指向的是PerfProducer對象,因此delegate_->onProcDescriptors會將兩個文件描述符發(fā)送給PerfProducer對象。而PerfProducer進而將此文件描述符發(fā)送給了UnwinderHandle對象。
6.2.6目標進程如何將maps和mem信息發(fā)送給traced_perf
如前面所述,traced perf通過signal通知的目標進程,讓目標進程將文件描述符進行了發(fā)送,那么目標進程為什么都會響應這類信息呢?(目標進程可能非常多樣,包括daemon、系統(tǒng)apk、三方apk等),答案在C庫中。
當目標進程接收到信號后,通過unix socket與traced_perf進程建立連接,然后打開兩個文件maps和mem,通過unix socket的sendmsg進行發(fā)送。關于通過unix socket發(fā)送文件描述符,可以參考文檔:https://man7.org/linux/man-pages/man7/unix.7.html,這里不做詳細描述。
上述代碼在android_profiling_dynamic.cpp中,會編譯成C庫的一部分,并被大多數(shù)進程所加載。
到此為止,用戶棧的所有所需信息均已準備完畢。
6.2.3用戶棧的解析
將所有信息準備好后,真正解析用戶??梢灾苯油ㄟ^libunwindstack提供的方法Unwinder::Unwind即可了,反而流程顯得很直接。
7.Sample的寫入
Sample數(shù)據(jù)寫入到trace中的操作也是比較直接的,將前面流程中獲取到的信息,通過TraceWriter返回的TrackPacket protobuf結(jié)構(gòu)進行寫入即可。
至此,整個traced_perf的獲取sample的執(zhí)行流程大概完成了。
?
8.總結(jié)
traced_perf的工作流程主要部分包括:
perfetto流程的嵌入:traced_perf是perfetto的producer
Sample的獲取
Unwinding操作
這三項主要內(nèi)容看似復雜,實際上整體結(jié)構(gòu)也是比較清晰的。Perfetto已經(jīng)將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